9.6. Java

Java を使って安全なプログラムを開発しているなら、まず最初のステップ(Java の 学習後)は、Java のセキュリティについての 2 つの基礎的なテキストを読むこと です。そのテキストは、Gong [1999]と McGraw [1999](後者については、特に セクション 7.1 を見てください)。 また、Sun が投稿した安全なコードのためのガイドライン http://java.sun.com/security/seccodeguide.html も見てください。 Java のセキュリティモデルについてのスライドが、 http://www.dwheeler.com/javasec で自由に見られます。 McGraw [1998]もあわせて見てください。

皆さんが開発しているようなアプリケーションは、明らかにたくさんのものに依存して います。 クライアント側での利用を目的にした Java のコードは、サーバ側のコード以上に まったく異なった環境(と信頼モデル)にあります。 もちろん一般原則は適用できます。たとえば、信頼できないユーザからの入力に 対しては、その入力をチェックしてフィルタをかけなければいけません。 しかし、Java では「隠れた」入力や、下記で論じるような配慮すべき潜在的な入力 が存在します。 Johnathan Nightingale [2000] には、Java プログラミングにおける課題を要約した 記述が興味深く、いろいろと書いてあります。

… Java プログラミングで大事なのは、継承に気をつけることです。 親からのメソッドやインタフェース、親のインタフェースを継承するなら、 コードに穴が開く危険があります。

キーとなるガイドラインを Gong [1999]や McGraw [1999]、Sun のガイダンス、 そして私自身の経験からいくらか書いてみます。

  1. public なフィールドもしくは変数は、使わないでください。private で宣言して、 それらへのアクセス機能を提供し、制限をかけてから利用してください。

  2. メソッドは他に理由がない限り、private にしてください(もし private にしない なら、ドキュメントに理由を記載してください)。 private でないメソッドは、自分自身で防御しなければいけません。なぜなら、 汚染されたデータを受け取るかもしれないからです(どうにかして防御する手はず を整えない限りは)。

  3. JVM(Java Virtual Machine)は、アクセシビリティ修飾子(たとえば、「private」) をアプリケーション(アプレットとは対照的に)実行時に実際には実施しない可能性 があります。 この点を「Secure Programming」メーリングリストで 2000 年 11 月 7 日に 指摘してくれた John Steven 氏(Cigital Inc.)に感謝します。 この問題は、どのクラスのローダーが、アクセスを要求するクラスをロードするかに 左右される点にあります。 クラスが信頼できるクラスのローダー(null もしくは基底クラスのローダーを含む) からロードされれば、アクセスチェックは「真」を返します(アクセスを許可)。 たとえば、下記のように動作します。 (少なくとも、Sun の 1.2.2 VM では。他の実装では動作しないかもしれません)。

    1. public フィールドを持つ犠牲者クラス(V)を書き、コンパイルします。

    2. 先程のフィールドにアクセスする「攻撃者」クラス(A)を書き、 コンパイルします。

    3. V の public フィールドを private に変更して、再コンパイル します。

    4. A を動かします。A は V の(現在は private な)フィールドに アクセスします。

    しかし、アプレットでは状況が変わります。 A をアプレットに変換し、アプレットとして動作させると(たとえば、アプレット・ ビューアーやブラウザで)、クラスローダーは、もはや信頼された(もしくは null) クラスのローダーになっています。 つまり、コードは、java.lang.IllegalAccessError に投げられ、クラス A から V.secret フィールドにアクセスしようとしている、というメッセージを出します。

  4. static フィールドの変数は使わないでください。そのような変数はクラス に属します(クラス・インスタンスではなく)。そして、クラスは他のクラスによって 位置づけられて、static フィールドの変数は他のどのクラスからも見られるように なります。こうなってしまうと、安全にするのがますます難しくなります。

  5. コードに悪意があるかもしれない場合、mutable オブジェクトを決して返さ ないようにしてください(コードが mutable オブジェクトを変更してしまうかも しれないため)。 配列は mutable であることに注意してください(配列の中身が mutable でなくても です)。 したがって、機密データが入った内部配列への参照は返さないでください。

  6. ユーザの既知の mutable なオブジェクト(オブジェクトの配列を含む)を決して そのまま保存しないでください。 さもないと、ユーザがオブジェクトを安全が必要となるコードに渡してしまうかもしれ ません。安全が必要なコードは、そのオブジェクトを「チェック」し、そのデータを 使いながら、変更してください。 配列は内部で保存する前にコピーして、注意深く扱ってください(たとえば、ユーザ が作成したコピー・ルーチンには注意してください)。

  7. 初期化に頼らないでください。 初期化していないオブジェクトを割り当てる方法は、いくつかあります。

  8. 特に理由がなければ、すべてを final としてください。 クラスもしくはメソッドが final でないと、攻撃者が危険かつ思いがけない方法で 拡張しようとするかもしれません。 こうすると、セキュリティと引き換えに拡張性が犠牲になることを忘れないで ください。

  9. セキュリティは、パッケージのスコープに頼らないようにしてください。 デフォルトで閉じている java.lang のようなクラスは少数です。Java Virtual Machine(JVM)には、他のパッケージを閉じさせようとするものもあります。 そうでなければ、Java のクラスは閉じていません。 つまり、攻撃者は新しいクラスをパッケージ内部に導入することで、この新しい クラスを使って、防御していると思っているオブジェクトにアクセスできて しまいます。

  10. inner クラスを使用しないでください。 inner クラスがバイトコードに変換されると、inner クラスはパッケージ の中のあらゆるクラスからアクセスできるクラスへと変換されてしまいます。 さらに悪いことに、クラスのプライベートなフィールドを取り囲むと暗黙に private ではなくなり、なんと inner クラスからのアクセスを認めてしまいます。

  11. 特権を最低限にしてください。 できるだけ、特別なパーミッションをまったく必要としないようにしてください。 McGraw 氏は、さらに踏み込んで、どんなコードにも署名しないように推奨して います。私はあえてコードに署名しています(そうすると、ユーザが「(ある特定の )送り手の一覧にある人が署名したコードだけを実行可能」という選択ができます)。 しかし、特権を設定するサンドボックスそのものを必要とするよう、プログラムを 書いてください。 さらに特権を持つ必要があるなら、とりわけ厳しくそのコードを監査してください。

  12. コードに署名しなければいけないなら、1 つのアーカイブファイルに すべてを納めてください。 McGraw [1999]から引用するのが適切ですので、下記に載せます。

    この規定の目的は、異なる手段を組み合わせた攻撃を防ぐことにあります。 この攻撃は、攻撃者が悪意あるコードと署名済みのクラスを一緒にリンクしたり、 意図的に絶対一緒に使うことがないように作られた署名済みクラスとリンクしたり して、新しいアプレットやライブラリを構築しようとします。 クラスのグループにもあわせて署名すると、この攻撃がさらに困難になります。 コードに署名を行う既存のシステムでは、十分に異質なものを組み合わせた攻撃に 対処できません。したがってこの規定は、そのような攻撃を完全には防ぎ切れません。 しかし、アーカイブを 1 つにしておけば、打撃は受けません。

  13. クラスが複製できないようにしてください。 Java がオブジェクトを複製する仕組みは、コンストラクタが何も動かなくても、 攻撃者がクラスのインスタンスを作成できてしまいます。クラスを複製できない ようにするために、クラスそれぞれで下記のメソッドを定義してください。
    public final void clone() throws java.lang.CloneNotSupportedException {
       throw new java.lang.CloneNotSupportedException();
       }

    どうしてもクラスを複製可能にする必要があるなら、攻撃者が複製したメソッドを 再定義できないようにする方法がいくつかあります。 自分で複製したメソッドを定義しているなら、final にしてください。定義して いないなら、少なくとも下記を追加して、悪意あるオーバーライドを防げます。
    public final void clone() throws java.lang.CloneNotSupportedException {
      super.clone();
      }

  14. クラスはシリアライズできないようにしてください。 シリアライズすると、攻撃者が private な部分であっても、オブジェクトの内部状態 を見られるようになります。 これを防ぐには、このメソッドをクラスに追加してください。
    private final void writeObject(ObjectOutputStream out)
      throws java.io.IOException {
         throw new java.io.IOException("Object cannot be serialized");
      }

    シリアライズが可能な場合、フィールドがシステムのリソースを直接扱っていたり、 アドレス空間に関連する情報を含んでいたりするなら、そのフィールドに transient を設定するようにしてください。 そうしないと、クラスをデシリアライズすると不適切なアクセスを認めることになる かもしれません。 機密の情報は transient であると見なしても良いと思います。

    自分でクラスにシリアライズするメソッドを定義するなら、どんな DataInput や DataOutput メソッドにも内部配列を渡すべきではありません。 根本的な理由は、DataInput や DataOutput メソッドが、オーバーライド可能だから です。 シリアライズできるクラスが private な配列を直接 DataOutput(write(byte [] b)) メソッドに渡したとすると、攻撃者が ObjectOutputStream をサブクラス化して write(byte [] b)メソッドをオーバーライドし、private な配列にアクセスしたり、 修正したりできるようにしてしまいます。 デフォルトのシリアライズは、private なバイト配列フィールドを DataInput や DataOutput のバイト配列メソッドに公開しません。

  15. クラスをデシリアライズしないでください。 クラスをシリアライズしていなくても、デシリアライズできるかもしれません。 攻撃者が好みの値を入れたバイトシーケンスを作って、クラスのインスタンス としてデシリアライズできます。 見方を変えると、デシリアライズは一種の public なコンストラクタで、攻撃者が オブジェクトの状態を選択できるようにします。この操作は明らかに危険です。 これを防ぐには、下記のメソッドをクラスに追加してください。
    private final void readObject(ObjectInputStream in)
      throws java.io.IOException {
        throw new java.io.IOException("Class cannot be deserialized");
      }

  16. 名前でクラスを比較しないでください。 攻撃者は、どのみち同じ名前でクラスを定義できます。注意しないと、これら のクラスに望ましくない特権を与えてしまい、混乱が生じます。 ここでは、間違った例として、オブジェクトが既知のクラス かどうかを判断する例を載せておきます。
      if (obj.getClass().getName().equals("Foo")) {

    2 つのオブジェクトが、間違いなく同じクラスであると判断する必要があれば、 getClass()を両者にかけてから、== 演算子で比較してください。 つまりこのようになります。
      if (a.getClass() == b.getClass()) {
    オブジェクトが既知のクラス名を本当に持っているかどうかを決める必要があるなら、 杓子定規に、必ず現在の名前空間(今使っているクラスのクラスローダー)で使うよう にしてください。 下記のような形式を使ってください。
      if (obj.getClass() == this.getClassLoader().loadClass("Foo")) {

    このガイドラインは McGraw 氏と Felten 氏のドキュメントからの引用です。この ガイドラインは優れています。 私はこれに加筆して、クラスの値による比較はできるだけ避けるのが得策であると しました。 今必要性がまったくないとしても、クラスメソッドやインタフェースを設計する 方が適切です。 しかしいつも設計するのは無理なので、これらのテクニックを知っていると役に 立ちます。

  17. 機密情報(暗号鍵やパスワード、アルゴリズム)をコードやデータに保存しないで ください。 JVM には簡単にこのデータを見せてしまう好ましくないものがあります。 コードを複雑にしても、実力がある攻撃者にはコードを隠しておけません。