クラス内部の可変メンバへの参照を返すと、アプリケーションのセキュリティが侵害される可能性がある。カプセル化の破綻を招き、クラスの内部状態を(偶然あるいは悪意を持って)変更する機会を与えてしまうからである。したがって、プログラムは、クラスの可変メンバへの参照を返してはならない。
クラスの可変な内部状態のディフェンシブコピーへの参照を返すならば、コピーが変更可能であることに変わりはないが、呼出し元はコピー元の内部状態を変更できなくなる。
違反コード
以下の違反コードのgetDate()アクセッサメソッドは、privateなDateオブジェクトのインスタンスをそのまま返している。
class MutableClass { private Date d; public MutableClass() { d = new Date(); } public Date getDate() { return d; } }
信頼できない呼出し元はprivateなDateオブジェクトを操作できる。なぜなら、オブジェクトへの参照を呼出し元へ返すと、MutableClassの信頼境界を越えて、クラス内部の可変要素をクラス外に公開してしまうからである。
適合コード (clone())
以下の適合コードでは、getDate()アクセッサメソッドが、Dateオブジェクトのクローンを返している。この方法が安全である理由は、Dateが攻撃者によって拡張可能である一方で、getDate()が返すDateオブジェクトはMutableClassの制御下にあり、攻撃者によって拡張可能ではないからだ。
public Date getDate() { return (Date)d.clone(); }
クラスが信頼できないコードによってサブクラス化されうるのであれば、コンストラクタの実行中に行うディフェンシブコピーの作成にclone()メソッドを使用すべきではない。これは、悪意ある攻撃者によってオーバーライドされたclone()メソッドを呼び出さないためである。詳しくは「OBJ07-J. センシティブなクラスはコピーさせない」を参照。
クラスのフィールドを変更することが目的であるpublicなセッターメソッドを持つクラスは、「OBJ06-J. 可変入力や可変な内部コンポーネントはディフェンシブコピーを作成する」にあるアドバイスに従うこと。セッターメソッドは、内部フィールドを設定する前に、入力値検査とデータの無害化を行うことができ、また、通常そうすべきである。
違反コード (可変な配列メンバ)
以下の違反コード例では、getDateアクセッサメソッドは、Dateオブジェクトの配列を返す。このメソッドは、配列を返す前にそのディフェンシブコピーを作成していない。配列は可変なDateオブジェクトへの参照を持つため、配列の浅いコピーを作成するだけでは不十分である。攻撃者は配列のDateオブジェクトを変更できてしまう。
class MutableClass { private Date[] date; public MutableClass() { date = new Date[20]; for (int i = 0; i < date.length; i++){ date[i] = new Date(); } } public Date[] getDate() { return date; // あるいは date.clone() を返す } }
適合コード (深いコピー)
以下の適合コードでは、date配列の深いコピーを作成しそのコピーを返すことで、date配列と各Dateオブジェクトの両方を保護している。
class MutableClass { private Date[] date; public MutableClass() { date = new Date[20]; for(int i = 0; i < date.length; i++) { date[i] = new Date(); } } public Date[] getDate() { Date[] dates = new Date[date.length]; for (int i = 0; i < date.length; i++) { dates[i] = (Date) date[i].clone(); } return dates; } }
違反コード (不変オブジェクトを持つ可変メンバ)
以下の違反コード例において、ReturnRefクラスはprivateなHashtableインスタンスフィールドを持つ。ハッシュテーブルには、不変なセンシティブデータ(社会保障番号)が格納されている。ゲッターメソッドgetValues()が返す参照を介し、呼出し元はハッシュテーブルにアクセスできる。信頼できない呼出し元は、ゲッターメソッドを使ってハッシュテーブルへアクセスし、テーブルのエントリの追加や削除、置き換えを不正に行うことができる。さらに、これらの変更が複数のスレッドによって実行される可能性があり、そうなると競合が発生するのは必至であろう。
class ReturnRef { // 内部状態。センシティブなデータが含まれているかもしれない private Hashtable<Integer,String> ht = new Hashtable<Integer,String>(); private ReturnRef() { ht.put(1, "123-45-6666"); } public Hashtable<Integer,String> getValues(){ return ht; } public static void main(String[] args) { ReturnRef rr = new ReturnRef(); // センシティブなデータである 123-45-6666 を出力 Hashtable<Integer, String> ht1 = rr.getValues(); // 信頼できない呼び出し元はエントリを削除できる ht1.remove(1); // 元のエントリは削除され、null を出力 Hashtable<Integer, String> ht2 = rr.getValues(); } }
このコード例はさらに、ハッシュテーブルhtへの参照を返しており、ガーベージコレクションを妨害している。
適合コード (浅いコピー)
privateかつ可変なオブジェクトの内部状態は、ディフェンシブコピーを作成すること。不変データを含む可変フィールドは、浅いコピーで十分である。一般に、可変データを参照するフィールドは、深いコピーを作成する必要がある。
以下の適合コードでは、不変データである社会保障番号を含むハッシュテーブルの浅いコピーを作成して返している。コピー元のハッシュテーブルはprivateに保たれ、変更されない。
class ReturnRef { // ... private Hashtable<Integer,String> getValues(){ return (Hashtable<Integer, String>) ht.clone(); // 浅いコピーを作成 } public static void main(String[] args) { ReturnRef rr = new ReturnRef(); // センシティブでないデータを出力 Hashtable<Integer,String> ht1 = rr.getValues(); // 信頼できない呼出し元はコピーに対してのみ変更を加えることができる ht1.remove(1); // センシティブでないデータを出力 Hashtable<Integer,String> ht2 = rr.getValues(); } }
ハッシュテーブルにDateオブジェクトなど可変オブジェクトへの参照が含まれる場合、これらの各オブジェクトはコピーコンストラクタやメソッドを使用してコピーしなくてはならない。詳しくは「OBJ06-J. 可変入力や可変な内部コンポーネントはディフェンシブコピーを作成する」および 「OBJ04-J. 信頼できないコードにインスタンスを安全に渡すため、可変クラスにはコピー機能を実装する」を参照。
ハッシュテーブルのキーについては、深いコピーを作成する必要はなく、浅いコピーを作成するだけで十分である。ハッシュテーブルの構造上、キーは常に一貫した結果をequals()メソッドおよびhashCode()メソッドに返さなくてはならないからである。equals()メソッドやhashCode()メソッドの結果を改変できるような可変オブジェクトは、ハッシュキーには適さない。
例外
OBJ05-EX0: オブジェクトの変更が不可能なビューメソッドのみが呼び出される場合、メソッドはディフェンシブコピーを作成せず自由にビューを使用することができる。このような判断はAPIを設計する初期の段階で行うべきである。このようなメソッドを呼び出す新規の呼出し元は、変更不可能なビューのみを外部に公開すべきである。
リスク評価
オブジェクトの内部状態(可変、不変を問わず)への参照を返してしまうと、情報漏えいやオブジェクトの状態の破壊につながり、ひいてはクラスの不変条件を壊す可能性がある。場合によってはプログラムの制御フローも影響を受ける。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
OBJ05-J | 高 | 中 | 中 | P12 | L1 |
自動検出
適切な自動検出は不可能である。ヒューリスティックな検出は役に立つだろう。
関連する脆弱性
JDK 1.7 の初期のベータ版において、sun.security.x509.InvalidityDateExtension クラスの publicアクセッサメソッドは、ディフェンシブコピーを作成することなく Date インスタンスを返していた。Pugh は、静的解析ツール Findbugs によって発見されたこの脆弱性を [Pugh 2009] の中で引用している。
関連ガイドライン
CERT C++ Secure Coding Standard | OOP35-CPP. Do not return references to private data. |
MITRE CWE | CWE-375. Returning a mutable object to an untrusted caller |
参考文献
[API 2006] | Method clone() |
[Bloch 2008] | Item 39. Make defensive copies when needed |
[Goetz 2006] | 3.2, Publication and Escape: Allowing Internal Mutable State to Escape |
[Gong 2003] | 9.4, Private Object State and Object Immutability |
[Haggar 2000] | Practical Java Praxis 64. Use clone for immutable objects when passing or receiving object references to mutable objects |
[Security 2006] |
翻訳元
これは以下のページを翻訳したものです。
OBJ05-J. Defensively copy private mutable class members before returning their references (revision 98)