可変入力はその性質上、参照するたびその値が変わることがある。つまり、参照するたびその値が異なって見えるかもしれない。この性質は競合状態を悪用する攻撃を可能にする。たとえば、フィールドの値が、値の検証やセキュリティチェックを通過するが、その後使われるときには変更されている場合には、time-of-check-time-of-use (TOCTOU) 脆弱性が発生しうる。
オブジェクト内部の可変コンポーネントへの参照を返すと、オブジェクトの状態を破壊する機会を攻撃者に与えてしまう。したがって、アクセサメソッドでは、可変オブジェクトのディフェンシブコピーを返さなくてはならない(「OBJ05-J. privateかつ可変なクラスメンバへの参照を返す前にそのディフェンシブコピーを作成する」を参照)。
違反コード
以下の違反コードには TOCTOU 脆弱性が存在する。cookie は可変入力なので、攻撃者は、最初のチェック(hasExpired() の呼出し)と実際の使用時(doLogic() の呼び出し)の間に期限切れにすることができる。
public final class MutableDemo { // java.net.HttpCookie は可変 public void useMutableInput(HttpCookie cookie) { if (cookie == null) { throw new NullPointerException(); } // cookie が期限切れかどうかチェックする if (cookie.hasExpired()) { // cookie が期限切れの場合。例外をスロー } // cookie は期限切れにされているかもしれない doLogic(cookie); } }
適合コード
以下の適合コードでは、可変入力のコピーを作成しすべての操作をコピーに対して行うことで、TOCTOU 脆弱性を防いでいる。よって、攻撃者が可変入力に変更を加えたとしても、コピーに影響はない。コピーコンストラクタを使ったり、java.lang.Cloneable インタフェースを実装して (final 宣言されていないクラスに対する) public 属性のクローンメソッドを宣言する手法もよいであろう。HttpCookie のように可変クラスが final 宣言されている、つまりコピーメソッドを提供できない場合には、呼び出し元でオブジェクトの状態のコピーを作成すべきである。詳細は「OBJ04-J. 信頼できないコードにインスタンスを安全に渡すため、可変クラスにはコピー機能を実装する」を参照。入力値検査は元のオブジェクトにではなくコピーに対して行うことに注意。
public final class MutableDemo { // java.net.HttpCookie は可変 public void useMutableInput(HttpCookie cookie) { if (cookie == null) { throw new NullPointerException(); } // コピーを作成 cookie = (HttpCookie)cookie.clone(); // cookie が期限切れかどうかチェックする if (cookie.hasExpired()) { // cookie が期限切れの場合。例外をスロー } doLogic(cookie); } }
適合コード
一部のコピーコンストラクタと clone() メソッドは元のインスタンスの浅いコピー(shallow copy)を行う。たとえば、配列に対して clone() を適用すると、各要素が元のインスタンスと同じ値を持った配列が作られる。プリミティブ型の配列であればこのような浅いコピーで十分であるが、クッキーの配列のように配列の要素が可変オブジェクトへの参照である場合、TOCTOU 脆弱性を防ぐことはできない。その場合、参照されている要素自体もコピーする深いコピー(deep copy)を行う必要がある。
以下の適合コードは(int配列に対する)浅いコピーと(cookie 配列に対する)深いコピーの正しい使用例を示している。
public void deepCopy(int[] ints, HttpCookie[] cookies) { if (ints == null || cookies == null) { throw new NullPointerException(); } // 浅いコピー int[] intsCopy = ints.clone(); // 深いコピー HttpCookie[] cookiesCopy = new HttpCookie[cookies.length]; for (int i = 0; i < cookies.length; i++) { // 配列の各要素のコピーを作成する cookiesCopy[i] = (HttpCookie)cookies[i].clone(); } doLogic(intsCopy, cookiesCopy); }
違反コード
可変入力であるクラスが final 宣言されていないクラスである場合やインタフェースである場合、攻撃者はサブクラスを作成し、親クラスの clone() メソッドを上書きすることが可能である。攻撃者が用意する clone() メソッドはディフェンシブコピーの作成を回避することができる。以下にこのような問題を抱えたコード例を示す。
// java.util.Collection はインタフェース public void copyInterfaceInput(Collection<String> collection) { doLogic(collection.clone()); }
適合コード
以下の適合コードでは、final 宣言されていない可変オブジェクトのコピーを作る際に、信頼できないオブジェクトのクラスではなく想定しているクラスを明記することで、悪意あるオーバーライドを防いでいる。コピーによって新たに生成したインスタンスは、渡されたオブジェクトを変更するコードに与えても問題ない。
public void copyInterfaceInput(Collection<String> collection) { // 入力を信頼できる実装に変換する collection = new ArrayList(collection); doLogic(collection); }
内部状態を変更するメソッドを持たないオブジェクトは不変であるかのように見える。たとえば、java.lang.CharSequence インタフェースは不変文字列を記述するものである。しかし、CharSequence 型変数は、CharSequence インタフェースを実装するなんらかのクラスのオブジェクトへの参照であることに注意しなければならない。そのクラスは可変である可能性がある。その場合、参照されているオブジェクトが変更されると、CharSequence 型のオブジェクトとして見ても変更されていることになる。java.lang.CharSequence インタフェースは、オブジェクトの変更を行うメソッドを提供しないことで、このインタフェースを通じてのオブジェクト変更は不可能にするが、真のイミュータビリティを保証するものではない。このようなオブジェクトについては、使用前にディフェンシブコピーを作成しなければならない。java.lang.CharSequence インタフェースの場合は、toString() メソッドを使って文字列の不変なコピーを作るという方法がある。可変フィールドは静的変数に代入してはならない。ほかに方法がなければ、信頼できないコードに直接アクセスされないように、ディフェンシブコピーを作るべきである。
リスク評価
可変入力のコピーを作成しないと、TOCTOU 脆弱性が作り込まれたり、内部の可変コンポーネントを信頼できないコードにさらす可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
OBJ06-J | 中 | 中 | 高 | P4 | L3 |
関連ガイドライン
Secure Coding Guidelines for the Java Programming Language, Version 3.0 | Guideline 2-2. Create copies of mutable outputs |
参考文献
[Bloch 2008] | Item 39. Make defensive copies when needed |
[Pugh 2009] | Returning References to Internal Mutable State |
翻訳元
これは以下のページを翻訳したものです。
OBJ06-J. Defensively copy mutable inputs and mutable internal components (revision 98)