2013年7月，セキュリティ企業のSecurity Explorationsが，Javaサンドボックスを完全に回避可能なセキュリティ上の脆弱性をJava 7u25内に発見した。Oracleは7u40でパッチをリリースしたが，Security Explorationsの今年初めの発表によると，同パッチは概念実証例に対処したのみであって，簡単なコード変更をするだけで，依然として脆弱性は存在している。さらにその後の調査から，この脆弱性が当初考えられていたよりも深刻なものであることが明らかになった。問題が公になった後，Oracleは，8u77の一部としてパッチをリリースしている。

問題の脆弱性は，Java 7で追加された新しいリフレクションライブラリにある。より具体的には，メソッドの動的なアクセスや起動に使用される新たなMethodHandleクラスの，異なるクラスローダによってロードされたクラスを処理する方法に関連する。問題を理解するには，Javaのクラスローダの仕組みに関する基本的な知識が必要だ。クラスのローディングは，Javaの中でも理解されていない部分のひとつなので，問題について解説する前に，この概念について簡単に説明することから始めたい。

Javaクラスローダ

Javaはさまざまなソースから，実行時に動的にコードをロードする機能を備えている。これはクラスローダと呼ばれる，クラスの特別なカテゴリを経由して実現される。標準のJava実装ではファイルシステムやURL，あるいはzipファイルなどからクラスをロードするクラスローダが提供されているが，開発者が自身の要件に合わせて，独自のクラスローダを作ることも可能だ。クラスローダを操作する通常の方法は，loadClass(String)メソッドの呼び出しである。このメソッドは，クラスの名称を引数として取り，それが見つかれば関連するクラスのオブジェクトを返し，そうでなければClassNotFoundExceptionをスローする。Javaアプリケーションのすべてのクラスは，いずれかのクラスローダによって，このような方法でロードされる。

親クラスローダを割り当てることによって，いくつものクラスローダを接続し，階層構造を形成することも可能だ。親が割り当てられていない場合は，そのクラスローダをロードしたものがデフォルトのクラスローダになる(クラスローダもクラスなので，何らかのクラスローダによってロードされる必要がある)。親クラスローダが存在する場合，クラスローダは既定の動作として，要求されたクラスのロード要求を親に転送する。親(あるいはその祖先)がそのクラスをロードできない場合にのみ，このクラスローダ自身が要求されたクラスのロードを試みる仕組みだ。ただし，独自のローダを開発する場合は，このデフォルト動作に制限されることはなく，異なる実装を選択することもできる。

Javaアプリケーションが起動されると，以下のクラスローダが順次動作する。

ブートストラップクラスローダ(Bootstrap ClassLoader): JVM自体の一部であり，したがって各JVMの実装に固有である。親クラスローダを持たず，java.langパッケージ下のコアクラスのロードに使用される。 拡張クラスローダ(Extension ClassLoader): 拡張ライブラリのクラスのロードを担当し，Javaインストレーション毎に異なる可能性がある。拡張クラスローダは，java.ext.dirs変数で示されるパスに配置されたものを対象とする。 アプリケーションクラスローダ(Application ClassLoader): アプリケーションのメインクラス，およびクラスパス(classpath)内のすべてのクラスのロードを行なう。 カスタムクラスローダ(Custom ClassLoader): アプリケーション内で使用される，任意のクラスローダ。 これはオプションであり，アプリケーションによっては存在しない場合もある。

カスタムクラスローダを使って実行時にクラスを動的にロードする機能は，多くのアプリケーションに対して，他の方法では実現できない機会を与えるものである反面，残念なことに，特にクラス偽装に関しては，数多くのセキュリティ上の懸念を生み出す元にもなっている。原始クラスであるjava.lang.Objectの不正実装をロードするカスタムクラスローダを開発して，Javaアプリケーションでこの独自のObjectを使用することも，理論的には可能だ。これは２つの理由で，セキュリティ上問題となる可能性がある - 第1には，この独自のObjectが，java.langパッケージ内のすべての可視性のクラスにアクセス可能であることであり，第2には，この独自のObjectがJVMによって標準Objectクラス，すなわちJava実装の一部として信頼できるクラスのひとつとして扱われることだ。

このような脆弱性に対してJavaを安全なものにするため，Javaのクラスは3つの属性を使って識別される - クラス名，パッケージ，そしてクラスローダの参照だ。同じ名前とパッケージを持つ２つのクラスが別々のクラスローダによってロードされた場合，Javaはそれらを違うものと判断して，それらを相互に割り当てようとした場合にはClassCastExceptionを発生させる。こうすることで，クラス偽装から環境を保護しているのだ。

部分的修正と新たな脆弱性

Security Explorationsが報告し，CVE-2013-5838として分類された最初の脆弱性では，メソッドハンドルを通じてメソッドを呼び出す場合に，メソッドを呼び出されるクラスのクラスローダがチェックされていないことが指摘されている。このため攻撃者は，まさに前述したような方法でクラスを偽装することができるのだ。



最初の脆弱性を示すコードサンプル，ターゲットクラスのクラスローダがチェックされていない - 出典:Security Explorations

Oracleは2013年9月に，Java 7u40の一部としてこの修正プログラムを提供した。修正内容は，予想される型と指定された型のクラスローダを，以下の手順で比較することによるクラスの可視性チェックで構成されている。

両方のクラスローダが同じであれば，定義上，２つの型は完全に互換なものだと解釈される。

もし一方のクラスローダが他方の親ならば，２つのクラスは通常のクラスローダ階層を通じてロードされたものと解釈されるので，同じものだと仮定しても差し支えない。

簡単な変更を行なえば依然としてエクスプロイトが可能であることをSecurity Explorationsが見つけたのは，この第２のチェックだ。まず，クラスを偽装するために使用するカスタムクロスローダの親に，ターゲットとなるクラスローダを設定することができる。次に示すように，これはAPIを通じて設定可能なパラメータであるからだ。

URLClassLoader lookup_CL = URLClassLoader.newInstance(urlArray, member_CL);



カスタムクラスローダを階層の一部であるように偽装する仕組み - 出典: Security Explorations

次に，クラスローダがローディング処理を上位に委譲するという既定の動作から，攻撃者のカスタムクラスローダが処理を行なうためには，親クラスローダがロードに失敗するようにする必要がある。これについては，ネットワーク経由でクラスをロードするJava機能を利用して実現できることが実証されている。ロードするクラスがURLロケーションに存在するように定義されていると偽装すれば，親クラスローダはコードを取得するためにそのサーバへの接続を試みる。これに対して，404 NOT FOUNDエラーを返す目的のHTTPサーバを用意しておくことで，親クラスのロードを失敗させて，コントロールをカスタムクラスローダに取り返すことが可能になる。



特別なHTTPサーバを通じて親クラスローダがロードに失敗した場合のコードフロー - 出典: Security Explorations

この問題が再浮上した2016年3月，当時の最新バージョンであった8u74に脆弱性のあることが判明し，Oracleがその修正を8u77で提供した。しかしながら，サーバ構成やGoogle App Engine for Javaにも影響することが証明済みであるにも関わらず，8u77のリリースノートにはこの脆弱性について，いまだ“デスクトップ上のWebブラウザ内で動作するJava SEに影響し，サーバ上などのJavaデプロイメントやスタンドアロンのデスクトップアプリケーションには適応されない”と説明されている。

訂正：2016年4月29日

この記事では8u77, 8u91, 8u92の各バージョンに脆弱性が存在するものと説明されているが，これは8u77で修正済みである。8u77のリリースノートではCVE-2016-0636の修正として，“未知のベクタによる [...] 不特定の脆弱性”という説明はあるが，この記事で取り上げたCVE-2013-5838については明示的に述べられていない。しかしSecurity Explorationsでは，元のCVE-2013-5838を同社独自のIssue 69として参照した上で，CVE-2016-0636がIssue 69のCVE番号であることを示している。