個人情報や機密データなど、センシティブなデータを含むクラスはコピーしないのが一番である。コピーされないクラスを定義したい場合、コピーコンストラクタなどのようなコピー機能を持たないクラスにするだけでは不十分である。
オブジェクトのクローンを生成するJavaの仕組みを使うことで、攻撃者は、コンストラクタを呼び出さずに、既存のオブジェクトのメモリイメージをコピーし、クラスの新規インスタンスを生成できる。このようなオブジェクトの生成方法は、通常許されるべきではないが、攻撃者はクローンを生成する機能を悪用し、以下を行うことができる。
- シングルトンクラスのインスタンスを複数生成
- サブクラス化とそのクローン生成により、スレッド安全性を脅かす
- コンストラクタで行われるセキュリティチェックを回避
- 重要なデータの「不変条件」を破壊
コンストラクタでセキュリティチェックを行うクラスでは、「OBJ11-J. コンストラクタが例外をスローする場合には細心の注意を払う」で説明するファイナライザ攻撃にも注意しなければならない。
センシティブでないクラスであっても、なんらかの不変条件を保つクラスは、サブクラスによってデータをアクセスされ不変条件を破壊される危険に注意しなければならない。詳細は「OBJ04-J. 信頼できないコードにインスタンスを安全に渡すため、可変クラスにはコピー機能を実装する」を参照。
違反コード
以下の違反コード例では、クラスSensitiveClassを定義している。このクラスは内部に、ファイル名を保持するための文字配列と、初期値falseのBoolean変数sharedを持っている。これらのデータはコピーされることを想定していない。そのため、SensitiveClassにはコピーコンストラクタが定義されていない。
class SensitiveClass {
private char[] filename;
private Boolean shared = false;
SensitiveClass(String filename) {
this.filename = filename.toCharArray();
}
final void replace() {
if (!shared) {
for(int i = 0; i < filename.length; i++) {
filename[i]= 'x' ;}
}
}
final String get() {
if (!shared) {
shared = true;
return String.valueOf(filename);
} else {
throw new IllegalStateException("Failed to get instance");
}
}
final void printFilename() {
System.out.println(String.valueOf(filename));
}
}
クライアントがget()メソッドを使ってStringインスタンスをリクエストすると、sharedフラグがセットされる。リクエストに対して返したStringオブジェクトとクラスのメンバとして保持している文字配列との一貫性を確保するため、文字配列を変更する操作は禁止されている。つまり、文字配列のすべての要素を'x'に置き換えるreplace()メソッドは、sharedフラグがセットされている場合には実行できない。しかし、SensitiveClassがCloneableインタフェースを実装していなかったとしても、Javaのクローン生成機能を使ってこの制約を回避することができる。
以下のコードに示すようなサブクラスを使うことで攻撃が可能である。publicなclone()メソッドを持つfinal宣言されていないSensitiveClassのサブクラスを生成している。
class MaliciousSubclass extends SensitiveClass implements Cloneable {
protected MaliciousSubclass(String filename) {
super(filename);
}
@Override public MaliciousSubclass clone() { // clone() メソッドをオーバーライド
MaliciousSubclass s = null;
try {
s = (MaliciousSubclass)super.clone();
} catch(Exception e) {
System.out.println("not cloneable");
}
return s;
}
public static void main(String[] args) {
MaliciousSubclass ms1 = new MaliciousSubclass("file.txt");
MaliciousSubclass ms2 = ms1.clone(); // コピーを作る
String s = ms1.get(); // filename を返す
System.out.println(s); // filename は "file.txt"
ms2.replace(); // すべての文字を 'x' に置き換える
// ms1.get() と ms2.get() の両方が filename = 'xxxxxxxx' を返す
ms1.printFilename(); // filename は 'xxxxxxxx' になっている
ms2.printFilename(); // filename は 'xxxxxxxx' になっている
}
}
攻撃者が用意したクラスは、インスタンスms1を生成し、さらにそのクローンをms2として生成する。次にget()メソッドを使ってms1のfilenameオブジェクトを取得する。これによりms1のsharedフラグの値はtrueになる。しかし、ms2のsharedフラグはtrueになっていないので、ms2のreplace()メソッドを使ってms1の持つ文字配列を変更することが可能である。これにより、クラスに実装されたセキュリティ対策が回避され、クラスの不変条件が破壊される。
適合コード (クラスをfinal宣言する)
前述のサブクラスを使った攻撃を防ぐには、SensitiveClassをfinal宣言する。
final class SensitiveClass {
// ...
}
適合コード (clone()メソッドをfinal宣言する)
センシティブなクラスはCloneableインタフェースを実装してはならない。また、コピーコンストラクタも実装してはならない。Cloneableインタフェースを実装している(つまりクローン作成可能な)スーパークラスを継承しているセンシティブなクラスは、CloneNotSupportedException例外をスローするようなclone()メソッドを実装しなければならない。そして例外CloneNotSupportedExceptionは、クライアントコード側でキャッチして処理しなければならない。また、Cloneableインタフェースを実装していないセンシティブなクラスも、このアドバイスに従うべきである。Objectクラスに由来するclone()メソッドを継承しているからである。実行に必ず失敗するfinal宣言したclone()メソッドをクラスに定義しておくことで、サブクラスのクローン作成を防ぐことができる。
class SensitiveClass {
// ...
public final SensitiveClass clone()
throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
このコードのSensitiveClassクラスは、悪意あるサブクラスの作成を防ぐことはできないが、内部に持つデータは守ることができる。メソッドは、final宣言することでサブクラスによるオーバーライドから保護することができる。詳しくは「OBJ04-J. 信頼できないコードにインスタンスを安全に渡すため、可変クラスにはコピー機能を実装する」を参照。
リスク評価
センシティブなクラスをコピーできないようにしないと、デフォルトのセキュリティマネージャが存在したとしても(独自にセキュリティチェックを実装しなければ)、クラスデータの不変条件を破壊され、悪意あるサブクラスによって新たなオブジェクトを生成するような攻撃が行われる可能性がある。
| ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| OBJ07-J | 中 | 中 | 中 | P8 | L2 |
参考文献
| [McGraw 1998] | Twelve rules for developing more secure Java code |
| [MITRE 2009] | CWE-498. Cloneable class containing sensitive information; CWE-491. Public cloneable() method without final (aka "object hijack") |
| [Wheeler 2003] | 10.6, Java |
翻訳元
これは以下のページを翻訳したものです。
OBJ07-J. Sensitive classes must not let themselves be copied (revision 107)
