コードに署名するのは、タスクの実行に際して昇格した権限が必要とされる場合のみにすべきである。詳細については、「ENV00-J. 特権の必要ない動作のみを行うコードを署名しない」を参照のこと。
たとえばアプレットは、自分が置かれていたホスト以外のホストに HTTP 接続する権限を持たない。アプレットがプラグインや拡張モジュールをダウンロードするために、外部のホストへの接続を必要とする場合、アプレットが必要とする権限をユーザに割り当てさせるのではなく、アプレット提供者が署名付きコードを提供する場合がある。しかし、権限の昇格した署名付きコードを実行するのは極めて危険であるため、そのコードの出自が信頼できるものであることを確認することが重要になる。
java をベースとする技術は通常、プラットフォームに依存しない java アプリケーションの導入を実現するために Java Archive (JAR) を使ったファイルのパッケージ化を行う。たとえば、Enterprise Java Beans (EJB)、MIDlets (J2ME)、Weblogic Server J2EE アプリケーションなどの配布形式として、JAR ファイルが推奨されている。Java Web Start が提供するポインティングデバイス操作によるインストールにおいても、JAR 形式が活用されている。JAR ファイル形式を使うことで、ファイルの出自を確認することができる。しかし、コードのセキュリティまでは保証しないことに注意。
Java チュートリアルには以下のような説明がある[Tutorials 2008]。
アプレットコードに署名を付けようと思うなら、そのコードは JAR ファイルにまとめる必要がある。セキュリティマネージャによる制限の元で実行するアプリケーションコードを開発する場合も同様である。JAR ファイルが必要となる理由は、ポリシーファイルで、あるエンティティによって署名されたコードに対し何らかの操作(たとえば特定のファイルの読取りや書き込み)を許可する場合、コードは署名付き JAR ファイルから来たものと想定されているからである。(「署名付きコード」とは、「署名付き JAR ファイルに含まれているクラスファイルのコード」の省略形である。)
クライアントコードでは、コード署名の検証を行う処理を組み込んでいないかもしれない。たとえば、URLClassLoader のインスタンスやサブクラス、java.util.jar は、署名付き JAR ファイルの署名を自動的に検証する。しかし開発者が実装した独自のクラスローダは署名検証を行わないかもしれない。URLClassLoader にしても、自動的に行われる検証は完全性チェックのみであり、ロードしたクラスを認証するわけではない。なぜならば、検証には JAR ファイルに含まれている公開鍵を(その鍵が信頼できるものかどうかは検証せずに)使うだけだからである。そのため、攻撃者によって、正しい JAR ファイルが、別の公開鍵とそれにあわせてダイジェスト値も変更された JAR ファイルに置き換えられてしまっているかもしれない。
デフォルトの自動署名検証を使ってもよいが、それだけでは不十分である。デフォルトの自動署名検証を使うシステムでは、署名が正しいものであることを確認するために、デフォルトの検証機能に加えて、更なる確認作業(たとえば、署名データを別の信頼できるデータと比較するなど)を行わなければならない。
違反コード
以下の違反コード例に、JAR ファイルのなかの特定のクラスを実行する機能を持つアプリケーション JarRunner を示す(The Java Tutorials にあるクラスの縮小版である[Tutorials 2008])。このコードでは、JarClassLoader のインスタンスを生成しており、アプリケーションのアップデート、プラグイン、パッチなどを、インターネットのような信頼できないネットワークからロードする。コードを取得するための URL は1つ目の引数として指定され(たとえば http://www.securecoding.cert.org/softwre-updates.jar)、残りの引数はロードされたクラスに引数として渡される。JarRunner はリフレクションを使って、ロードしたクラスの main() メソッドを呼び出す。残念ながらデフォルトでは、JarClassLoader は JAR ファイルに含まれている公開鍵を使って署名の検証を行う。
public class JarRunner { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { URL url = new URL(args[0]); // アプリケーション jar ファイルに対するクラスローダを生成 JarClassLoader cl = new JarClassLoader(url); // アプリケーションのメインクラス名を取得 String name = cl.getMainClassName(); // アプリケーションの引数を取得 String[] newArgs = new String[args.length - 1]; System.arraycopy(args, 1, newArgs, 0, newArgs.length); // アプリケーションのメインクラスを呼び出す cl.invokeClass(name, newArgs); } } final class JarClassLoader extends URLClassLoader { private URL url; public JarClassLoader(URL url) { super(new URL[] { url }); this.url = url; } public String getMainClassName() throws IOException { URL u = new URL("jar", "", url + "!/"); JarURLConnection uc = (JarURLConnection) u.openConnection(); Attributes attr = uc.getMainAttributes(); return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null; } public void invokeClass(String name, String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException { Class c = loadClass(name); Method m = c.getMethod("main", new Class[] { args.getClass() }); m.setAccessible(true); int mods = m.getModifiers(); if (m.getReturnType() != void.class || !Modifier.isStatic(mods) || !Modifier.isPublic(mods)) { throw new NoSuchMethodException("main"); } try { m.invoke(null, new Object[] { args }); } catch (IllegalAccessException e) { System.out.println("Access denied"); } } }
適合コード (jarsigner)
ユーザは JAR ファイルの署名をコマンドラインから確認することができるが、これを行うことはまれであろう。JAR ファイルを手作業でインストールする必要があるプログラムでは、この作業を行うのが適切である。jarsigner ツールを -verify オプションを指定して呼び出した場合、JAR ファイルが差し替えられていることを検知すると、SecurityException がスローされる。
jarsigner -verify signed-updates-jar-file.jar
適合コード (証明書チェーン)
ローカルシステムで署名の検証が適切に行えない場合、実行中のプログラムは、ロードしたクラスの CodeSource から証明書チェーンを取り出し、個々の証明書が信頼できる署名者によるものかどうかを検証しなければならない。なお、信頼できる署名者の証明書は、別途安全な方法でローカルに保存する必要がある。以下のコードでは、このような処理を行うために invokeClass() メソッドに必要な変更を行っている。
public void invokeClass(String name, String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, GeneralSecurityException, IOException { Class c = loadClass(name); Certificate[] certs = c.getProtectionDomain().getCodeSource().getCertificates(); if (certs == null) { // 署名されてなければ実行せずにリターン System.out.println("No signature!"); return; } KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(System.getProperty( "user.home"+ File.separator + "keystore.jks")), "loadkeystorepassword".toCharArray()); // "user" は証明書を表す別名 Certificate pubCert = ks.getCertificate("user"); // 信頼する公開鍵で検証する。検証に失敗したら例外をスロー certs[0].verify(pubCert.getPublicKey()); }
invokeClass() メソッドの throws 節に例外を2つ追加しているため、main() メソッド中の catch ブロックもそれにあわせて変更しなければならない。
URLClassLoader とそのすべてのサブクラスには、オブジェクトが作られるときに指定された URL とやりとりするために必要となるパーミッションだけが、初期設定として与えられる。つまり、ロードされたコードは、指定されたホストとだけ通信できるということである。しかし、これによって危険が完全に排除されるわけではない。なぜならば、ロードされたコードには、ローカルな JAR ファイルを更新するようなセンシティブな操作が許可されているかもしれないからである。
リスク評価
手動であるいはプログラムの中でデジタル署名の検証を行わないと、悪意あるコードを実行してしまう危険がある。
ルール | 脅威度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SEC06-J | 高 | 中 | 中 | P12 | L1 |
自動検出
一般的な状況における自動検出は、現実的には不可能である。しかし、Design Fragments と類似のアプローチは、プログラマや静的解析ツールの助けになるかもしれない[Fairbanks 2007]。
関連ガイドライン
ISO/IEC TR 24772:2010 | Improperly Verified Signature [XZR] |
MITRE CWE | CWE-300. Channel accessible by non-endpoint (aka "man-in-the-middle") |
CWE-319. Cleartext transmission of sensitive information | |
CWE-494. Download of code without integrity check | |
CWE-347. Improper verification of cryptographic signature |
参考文献
[API 2006] | |
[Bea 2008] | |
[Eclipse 2008] | JAR Signing and Signed bundles and protecting against malicious code |
[Fairbanks 2007] | |
[Flanagan 2005] | Chapter 24, The java.util.jar Package |
[Gong 2003] | 12.8.3, jarsigner |
[Halloway 2001] | JDC Tech Tips 2001年1月30日 |
[JarSpec 2008] | Signature Validation |
[Oaks 2001] | Chapter 12, Digital Signatures, Signed Classes |
[Muchow 2001] | |
[Tutorials 2008] | The JarRunner Class, Lesson: API and Tools Use for Secure Code and File Exchanges and Verifying Signed JAR Files |
翻訳元
これは以下のページを翻訳したものです。
SEC06-J. Do not rely on the default automatic signature verification provided by URLClassLoader and java.util.jar (revision 70)