Javaでは、通常、権限チェックはSecurityManagerによって処理されます。信頼できないコードが特権コードを呼び出したり、特権コードのバグを悪用したりしないように、SecurityManagerはコールスタック全体をチェックします。スタックトレース内のいずれかの呼び出し元が特権を持たない場合、デフォルトでは要求は拒否されます。少なくとも、それは 標準のSecurityManagerチェックの機能 です。
ただし、いくつかの特別なJava= APIは異なるルールに従います。これらのAPIは、標準のSecurityManagerチェックをバイパスし、より弱いチェックに置き換えます。特に、コールスタック全体ではなく、直接の呼び出し元のみをチェックします。(詳細は Java Secure Coding Guidelines のガイドライン9-8)を参照してください。特別なAPIには、たとえば、Class.forName()
、Class.getMethod()
、 もっと。)
どうして?これらの特別なAPIが標準チェックをバイパスし、より弱いチェックに置き換えるのはなぜですか?そして、なぜこれは安全ですか?言い換えると、直接の発信者だけをチェックするだけで十分なのはなぜですか。これにより、標準のSecurityManagerチェックが防御するように設計されたすべてのリスクが再導入されませんか?
私は 最近のJavaゼロデイエクスプロイトの分析 (CVE-2012-4681)を読んだときにこれを最初に学びました。その分析は、エクスプロイトがどのように機能するかを分解します。その他、攻撃には、これらの特別なAPIによって行われる弱いチェックを利用することが含まれます。特に、悪意のあるJavaコードは、信頼できるシステムクラスへの参照を取得します(別のバグを通じて)) 、次に、その信頼されたシステムクラスをだまして、これらの特別なAPIの1つを呼び出します。結果の権限チェックは、直接の呼び出し元のみを調べ、直接の呼び出し元が信頼されていることを確認し、操作を許可します。したがって、弱いチェックは攻撃を阻止しませんが、私が見る限り、この攻撃は標準のSecurityManagerチェックを使用することで防止されます(呼び出し元の呼び出し元は信頼されていないため)。つまり、この最近の攻撃は弱いチェックが危険である理由の例のように。
しかし、私はJavaデザイナーは賢い人です。私はJavaデザイナーがこれらの問題を検討し、標準のチェックをバイパスするいくつかの正当な理由があったと思います。これらの特別なAPIをより弱いチェックに置き換えてください-または、少なくとも、これが安全である理由は十分であると考えていました。
誰でもこれに光を当てることができますか? Java設計者はこれらの特別なAPIで失敗しましたか、それとも弱いチェックを代用する正当な理由がありましたか?
編集9/1:私はエクスプロイトがどのように機能するかについては質問していません。エクスプロイトの仕組みを理解していると思います。また、この特定の例で、これらの特別なAPIを呼び出した信頼できるコードにバグがあった理由についても質問していません。むしろ、Class.forName()
、Class.getMethod()
などの特別なAPIがなぜ非標準の弱いパーミッションチェックを使用するように指定および実装されているのかを尋ねています(直接の呼び出し元で)標準のSecurityManager権限チェックの代わりに(呼び出しスタック全体を確認)。この特別なAPIに対してより弱い権限チェックを使用するというこの設計決定により、最近の脆弱性が許容されたため、設計決定を批判することは簡単です。しかし、私はこのように物事を行うにはいくつかの正当な理由があったかもしれないと想像し、それらが何であるかもしれないのかと思っています。
(これは、「なぜ」に関する一般的なコメントであり、言及している特定の攻撃に関するものではありません。)
残念ながら、Java設計者は、構造的に言えば、自分を隅までペイントする可能性が非常に高いことを発見しました。たとえば、Java.io
andにはJava.net
にクラスがあり、両方がI/Oの実行に関与しています。特定のJVMがJava.io.FileDescriptor
に特別なOS相互作用ネイティブコードを持ち、send()
およびreceive()
システムコールを実行できると仮定します(そうではありません) Sun/Oracle JVMの場合ですが、別のJVMで発生する可能性があり、実際、少なくとも私が一度書いたもので発生します。サンドボックスのセマンティクスを適用するために、これらのメソッドはnotpublic
、 もちろん。
Java.net.Socket
の実装では、当然のことながら、これらのメソッドを使用します。ただし、命名規則に従って、Socket
はJava.net
ではなくJava.io
パッケージにあるクラスです。信頼されていないコードから呼び出された場合でもアクセスする必要があることを考慮して、どのようにJava.io.FileDescriptor
の非パブリックメソッドにアクセスできますか(信頼されていないコードはすべての宛先ではないが、ソケットを開くことができます)?主に2つの方法があります。
Java.net.Socket
にいくつかの「ブリッジ」native
メソッドを追加します。これにより、Java.io.FileDescriptor
のメソッドへの呼び出しが転送されます。ネイティブコードはパッケージと可視性を冷やします。ネイティブコードはすべてを行うことができます。
Java.net
のコードが他のパッケージの非公開メソッドにアクセスするためのリフレクションを行えるようにします。 SecurityManager
自体が使用するデータ構造を変更する可能性があるため、それを行うことができるコードはすべてについて行うことができます(多分10年前、Usenetのディスカッションで私にあなたがそれを私に指摘したことを覚えています両方とも本名を使用していた)。したがって、無制限のリフレクションを全員に許可する必要はありませんが、ここで説明する状況では、特定のシステムパッケージ(Java.net
)のコードにthatコードが信頼されていないから呼び出されても許可する必要がありますアプレット。
2番目の方法では、セキュリティモデルを弱める必要があります。実際、それは非常に一般的です:if信頼できないアプレットは、ある時点で有用な処理を行う必要があり、システムにアクセスできなければなりません(計算結果を表示するか、サーバーに送信する場合のみ) )、一種のゲートが必要です。ネイティブコードはそのようなゲートです。セキュリティを弱めるモデルは別の種類のゲートであり、「純粋なJava」の世界にとどまるという追加の利点があります(JVMメンテナンスチームでは、物事がより高価になるため、ネイティブコードはおそらく嫌われます)。悪い面は、セキュリティモデルを弱めることで、攻撃対象が大幅に拡大することです。今では、packages-with-privilegesのすべてのJavaコードが重要になっています。@ Hendrikのアナロジーを再利用するには、ルートsetuid
ビットを(Java.*
パッケージの)コードのかなりの部分に与えることです。
特に、Javaセキュリティモデルを弱める方法は、Class.findClass()
、Class.newInstance()
、Class.getMethod()
などの特別なAPIを指定することです。およびその他のリフレクション関連API-弱い権限チェックを使用する場合と同様。これにより、Java.net.Socket
の信頼できるシステムコードは、これらの特別なAPIを使用してJava.io.FileDescriptor
の非パブリックメソッドへの参照を取得し、それを反映して呼び出すことができます。
たとえば、Java.net.Socket
コードはClass.getMethod()
を使用してJava.io.FileDescriptor
の非パブリックメソッドへの参照を取得できます(Class.getMethod()
の直接の呼び出し元はJava.net.Socket()
であるため、これは許可されます。は信頼できるコードです)、それを呼び出します。
この実装戦略は、Class.getMethod()
に依存して、より弱い権限チェックを使用していることに注意してください。 Class.getMethod()
が標準の権限チェックを使用している場合は、機能しません。信頼できないコードがJava.net.Socket
を呼び出した後、Java.net.Socket
コードがClass.getMethod()
を呼び出すと、呼び出しスタックのどこかに信頼できないコードがあり、Java.net.Socket
が正しく機能しないため、標準の権限チェックはこの呼び出しを拒否します。 -攻撃シナリオ)。対照的に、弱められたセキュリティモデルで使用されるより弱い許可チェックはそれを可能にします。
したがって、より弱い権限チェックは、Javaデザイナーが自分たちを自分が描いたコーナーから抜け出すのに役立ちます。
おそらく、システムコードの「完全な」構造と命名規則があれば、少数のネイティブメソッドで十分ですが、JVMシステムライブラリコードに特定のis-immediate-caller-from-a-trusted-package呼び出しが存在します。上記のコードの構造が完全ではないことを示しています。
一部の信頼できるメソッドは、タスクを実行するために内部でより多くの権限を必要とします。ただし、これらのメソッドにバグがある場合、信頼できない攻撃者が特権レベルを上げて望ましくないアクションを実行する可能性があります。
オペレーティングシステムレベルで非常に類似した状況があります。 Unixには、setuid/setgidフラグとSudoコマンドがあります。これにより、権限のないユーザーがタスクを実行できるようになります。これには、内部でより高いレベルの権限が必要です。
たとえば、通常のユーザーは_/etc/shadow
_を変更できません。ただし、ユーザーが自分のパスワードを変更できるようにする必要があります。したがって、passwd
コマンドには信頼済み(setuid root)のフラグが付けられ、そのファイルの変更が許可されます。もちろん、独自のセキュリティチェックを実行する必要があります。
同じ状況がJavaサンドボックスに適用されます。たとえば、権限のないコードはメソッドを動的に呼び出すことができません。しかし、システムの一部は内部でそれを行う必要があります。
passwd
コマンドを使用して通常のユーザーが自分のパスワードのみを変更できるように、MethodFinder.findMethod()
は信頼できるコードが任意のメソッドを呼び出すことのみを許可することになっています。
これまでのところ、すべてが順調です。
ClassFinder.findClass()
は、このような信頼できるメソッドです。呼び出しコードの特権を持つ追加のクラスをロードします。 passwd
と同じように、自分のパスワードを変更できます。
ただし、エラーが発生した場合は、完全な権限でクラスをロードすることで回復を試みました。 passwd
について考えると、同様のバグにより、現在のユーザーではなくpasswd
がrootのパスワードを変更することになります。
Class.forName()
がClassFinder.findClass()
が誤ったセキュリティコンテキストでクラスをロードできるように、オペレーティングシステムはpasswd
がルートパスワードを変更できるようにします。
これはもう少し複雑です。信頼できるメソッドはMethod.invoke()
です。
しかし、MethodFinder.findMethod()
と呼ばれる2番目の信頼できるメソッドが含まれていました。オペレーティングシステムの類似性を維持するには、Sudoを介してroot権限で実行されるシェルスクリプトと考えてください。
このメソッド/プログラムはパラメーターを検証せず、passwd
に渡しただけです。これで、passwd
が信頼できるコンテキストで呼び出されます。そのため、誰でも楽しくパスワードを変更できます。