特権を持ったコンテキストでオブジェクトの復元を無制限に行うことができる場合、攻撃者は、細工した入力を与えることで、攻撃者の権限では通常構築できないはずのオブジェクトを構築することが可能になる。一つの例は、独自のクラスローダのような、取り扱いに注意しなければならないオブジェクトの構築である。それゆえ、特権を持ったコンテキストでオブジェクトの復元を行ってはいけない。オブジェクトの復元に一定の権限が必要な場合、必要最小限の権限のみを使い、その他の不要な権限はすべて落として処理を行うこと。
違反コード (CVE-2008-5353: ZoneInfo)
2008年8月に Sami Koivu によって発見された Java の脆弱性について CVE-2008-5353 に記述されている[CVE 2008]。その後 Julien Tinnes は、脆弱なバージョンの Java 処理系が動作する複数のプラットフォームにおいて任意のコードを実行できる攻撃コードを作成している。この問題は、シリアライズした信頼できない入力を、特権を持った状態で復元したことが原因で生じたものである。具体的には、シリアライズ可能な sun.util.Calendar.ZoneInfo クラスが、ObjectInputStream クラスの readObject() メソッドによって復元される際の処理に脆弱性が存在した。
デフォルトのセキュリティモデルでは、アプレットは sun.util.calendar.ZoneInfo にアクセスできない。アプレットが sun パッケージ内にあるクラスのメソッドを呼び出すことは許されていないからである。そのため、JDK 1.6 u11 より前では、署名されていないアプレットが ZoneInfo オブジェクトを復元するには、doPrivileged() ブロックのような特権を持ったコンテキストで復元を行う必要があった。これが脆弱性につながった理由は、シリアライズされたストリームに含まれているのが、攻撃者によって細工されたクラスなのか、それとも本物の ZoneInfo オブジェクトなのかを見分ける方法がなかったからである。脆弱なコードは、復元したオブジェクトを ZoneInfo 型にキャストする。実際に復元されたオブジェクトが ZoneInfo のオブジェクトでなければ、通常は ClassCastException 例外がスローされる。しかし、この例外はなんの役にも立たない。生成されたオブジェクトへの参照を、ガベージコレクタの対象にならない static コンテキストに置くことができるからである。
シリアライズ可能でないクラスを拡張してシリアライズ可能なサブクラスを作ることができる。また、シリアライズ可能なクラスのサブクラスは自動的にシリアライズ可能となる。サブクラスの復元時に JVM は、java.io.Serializable を直接的にも間接的にも実装していないスーパークラスの中で、クラス階層における直近のスーパークラスの引数なしのコンストラクタを呼び出す。これにより、このスーパークラスの状態を固定することが可能になる。以下のコードでは、クラス C が復元されるときに、クラス A の引数なしのコンストラクタが呼び出される。なぜならば、クラス A は Serializable を実装していないからである。クラス A のコンストラクタの呼出しに続き、Object のコンストラクタが呼び出される。この一連の処理をあらかじめプログラムしておくことはできないので、JVM はこの処理を行うバイトコードを実行時に生成する。スーパークラスのコンストラクタがサブクラスから呼び出される場合、一般に、サブクラスはスタック上に残っている。しかし復元処理の際には、スタック上にサブクラスはまだ存在しない。検証されていないバイトコードがあるだけである。そのため、スーパークラスのコンストラクタの中で行われるセキュリティチェックは回避され、実行フロー全体は検証されないことになる。
class A { // スーパークラスは Object A(int x) { } A() { } } class B extends A implements Serializable { B(int x) { super(x); } } class C extends B { C(int x) { super(x); } }
この時点で、スタック上にサブクラスのコードは存在せず、スーパークラスのコンストラクタはなんら制限されることなく実行される。doPrivileged() は、直近の呼出し元に許可されているすべての権限を使うことができるからである。この例における直近の呼出し元 java.util.Calendar は信頼でき、完全なシステム特権を持っている。
独自のクラスローダーを使うことで、この脆弱性を悪用することができる。クラスローダーオブジェクトを構築するには、SecurityManager が管理するセキュリティポリシーで用意されている特別なパーミッションが必要となる。初期設定では、署名されていないアプレットはこのような権限を得ることはできない。しかし、独自のクラスローダーのコンストラクタを呼び出すことができれば、すべてのセキュリティチェックを回避することができる。問題の脆弱性によって、このようなコンストラクタ呼出しが可能になっているのである。独自のクラスローダーでは、システムクラスローダーの拡張、セキュリティチェックの回避、ファイルシステム上のファイルの読取りや削除といった、本来禁止されている操作を行うことができる。さらに、コードにはすべての権限が与えられるため、コンストラクタで行われるセキュリティチェックが無意味になる。このような脆弱性を作り込んでいるコード例を以下に示す。
try { ZoneInfo zi = (ZoneInfo) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws Exception { return input.readObject(); } }); if (zi != null) { zone = zi; } } catch (Exception e) { // エラー処理 }
適合コード (CVE-2008-5353: Zoneinfo)
この脆弱性は、JDK v1.6 u11 で、新たに ProtectionDomain と AccessControlContext 型のフィールド INSTANCE を定義することで修正された。以下のコードでは、RuntimePermission クラスのパーミッション accessClassInPackage.sun.util.calendar を ProtectionDomain オブジェクトでカプセル化しており、sun.util.calendar クラスのアクセスに必要となる最小限のパーミッションが割り当てられている。このようなホワイトリスト的アプローチにより、想定されていない不正なアクセスに対してはセキュリティ例外が発生する。また、このコードでは2引数の doPrivileged() を使っており、ProtectionDomain に指定されている以外のパーミッションはすべて落とされる。
private static class CalendarAccessControlContext { private static final AccessControlContext INSTANCE; static { RuntimePermission perm = new RuntimePermission("accessClassInPackage.sun.util.calendar"); PermissionCollection perms = perm.newPermissionCollection(); perms.add(perm); INSTANCE = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) }); } } // ... try { zi = AccessController.doPrivileged( new PrivilegedExceptionAction<ZoneInfo>() { public ZoneInfo run() throws Exception { return (ZoneInfo) input.readObject(); } }, CalendarAccessControlContext.INSTANCE); } catch (PrivilegedActionException pae) { /* ... */ } if (zi != null) { zone = zi; }
リスク評価
権限を制限されていないコンテキストでオブジェクトの復元を行うと、任意のコード実行につながる危険がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER08-J | 高 | 高 | 中 | P18 | L1 |
関連ガイドライン
MITRE CWE | CWE-250. Execution with unnecessary privileges |
参考文献
[API 2006] | |
[CVE 2011] | CVE-2008-5353 |
翻訳元
これは以下のページを翻訳したものです。
SER08-J. Minimize privileges before deserializing from a privileged context (revision 84)