Web システムの脆弱性を開発者の視点に立ってわかりやすく説明する、その4です。
今回解説するのは以下の内容となります。
・不正なメモリ領域へのアクセスによる脆弱性
・境界外の読み取り・書き込み
・解放済みメモリの使用
・整数の桁あふれ
・NULL ポインタの逆参照
参考: 不正なリクエストデータに対する脆弱性
参考: 認証の欠如による脆弱性
参考: 機密情報・認証情報の漏えいによる脆弱性・不適切なパーミッションによる脆弱性
参考: Web システムの脆弱性の分類
不正なメモリ領域へのアクセスによる脆弱性
昨今の Web システムの開発では、本ページで解説しているような動的なメモリの確保やメモリアドレスを指定できるようなプログラミング言語はあまり利用されておらず、そういう意味では本ページで解説する脆弱性の内容は、「Web システム」という観点に限って言えばそれほど重要ではないかもしれません。
しかし、CWE TOP25 においては非常にランクの高い(危険度の高い)ものが多かったので、こちらも解説したいと思います。
境界外の読み取り・書き込み
・境界外書き込み(CWE-787 [jvndb])
・境界外読み取り(CWE-125 [jvndb])
・バッファエラー(CWE-119 [jvndb])
[対象となるシステム]
メモリアドレスの直接指定が可能なプログラミング言語で記述されたシステム。
[脆弱性・攻撃方法]
ポインタ変数などのメモリアドレスの直接指定が可能なプログラミング言語では、基本的に指定されたアドレスの妥当性はチェックされないため、意図せず安全なメモリ領域の境界外にあるアドレスへアクセスできてしまいます。
これを狙った攻撃を受けると、メモリ領域のデータ破壊や最悪の場合はシステムがクラッシュするなどの問題が発生します。
特に攻撃者がアクセス可能なメモリを制御できる場合、任意のコードを実行されたり機密情報を読み取られたりする可能性があります。
例えば関数ポインタの1つを有効なシェルコードへのアドレスで上書きし、任意のコードを実行するといった攻撃方法が考えられます。
以下は特定のインデックス位置にある配列から値を取得する C 言語のプログラム例です。
この関数はindex
に負の値が指定されることを想定しておらず、領域範囲の境界外へのメモリ参照が発生する可能性があります。
int getValueFromArray(int *array, int len, int index){
int value;
if(index < len){
value = array[index];
}else{
printf("Value is: %d\n", array[index]);
value = -1;
}
return value;
}
以下はユーザの IP アドレスからホスト名を調べ、バッファ内にコピーを作成する C 言語のプログラム例です。
この関数はホスト名を保存するためのバッファとして 64 バイトを割り当てていますが、ホストネームは必ずしも 64 バイト以内とは限りません。
攻撃者により非常に長いホスト名を解決するアドレスが指定された場合、意図しないメモリ領域の上書きによるデータ破壊が起きる可能性があります。
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/* routine that ensures user_supplied_addr is in the right format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr(addr, sizeof(struct in_addr), AF_INET);
strcpy(hostname, hp->h_name);
}
[対処方法]
ポインタ変数の扱いについては、その変数が指すメモリ領域の有効な範囲がどこからどこまでなのかをしっかりと認識することが非常に重要となります。
特に入力値がポインタ変数の参照位置や書き込みサイズに影響を及ぼす場合には、有効範囲に収まるよう十分な入力チェックを行う必要があります。
解放済みメモリの使用
・解放済みメモリの使用(CWE-416 [jvndb])
[対象となるシステム]
C 言語のmalloc()
、free()
といった動的なメモリ確保・解放が可能なプログラミング言語で記述されたシステム。
[脆弱性・攻撃方法]
動的にメモリ空間を確保した後にその領域を解放したとき、解放されたメモリアドレスを参照・更新すると、メモリ領域のデータ破壊や最悪の場合はシステムがクラッシュするなどの問題が発生します。
本ケースは基本的には開発者が作りこんだバグですが、例えば異常系の例外処理などちょっとした不注意で容易に作りこんでしまう類いの脆弱性なので細心の注意を払いましょう。
以下はエラーが発生したときの異常系処理とログ出力部分のプログラムの抜粋です。
エラー処理としてfree()
しているにもかかわらず、後のログ出力で解放されたメモリを参照しようとしています。
char* ptr = (char*)malloc(SIZE);
if(err){
abrt = 1;
free(ptr);
}
...
if(abrt){
logError("operation aborted before commit", ptr);
}
[対処方法]
プログラムのどの部分がメモリの解放を担当するのかをしっかりと設計しておく必要があります。
基本的にはメモリの確保・解放は同じレイヤーで行うべきです。
上記の例ではバグ原因をコード上から見つけやすいですが、確保と解放が別々の場所に存在する、あるいは別々の契機で実行される場合にはデバッグの難易度も上がります。
整数の桁あふれ
・整数オーバーフローまたはラップアラウンド(CWE-190 [jvndb])
[対象となるシステム]
すべてのシステム。
[脆弱性・攻撃方法]
整数値が大きすぎてオーバーフローを起こす、あるいはラップアラウンド(値が折り返され非常に小さい数または負の数になる)が発生すると、データの破損、予期しない動作、無限ループ、システムクラッシュにつながる可能性があります。
以下はかつて OpenSSH 3.3 に存在していた脆弱性(CVE-2002-0639 [JVNDB-2002-000138])のコードの抜粋です。nresp
が1073741824
以上の値である場合にラップアラウンドが発生し、作業バッファresponse
は非常に小さな領域になります。
この結果、ヒープオーバーフローが発生し、これを利用して関数ポインタの書き換えを許してしまいます。
OpenSSH 2.9.9 ~ 3.3 ではこの仕組みを利用して遠隔から第三者がsshd
プロセスの権限で任意のコードを実行できてしまうという脆弱性を抱えていました。
nresp = packet_get_int();
if(nresp > 0){
response = xmalloc(nresp * sizeof(char*));
for(i=0; i<nresp; i++) response[i] = packet_get_string(NULL);
}
[対処方法]
こちらも基本的にはバグなので、テスト工程でしっかり検証しましょう。
特に入力値が影響を及ぼす場合には、有効範囲に収まるよう十分な入力チェックを行う必要があります。
NULL ポインタの逆参照
・NULL ポインタデリファレンス(CWE-476 [jvndb])
[対象となるシステム]
すべてのシステム。
[脆弱性・攻撃方法]
アプリケーションが有効であると期待しているにもかかわらず NULL であったポインタを参照するときに発生し、最悪の場合システムのクラッシュや終了を引き起こします。
以下はプロパティ値を取得する Java のプログラム例です。
指定されたプロパティ名が存在しない場合、getProperty()
の呼び出しは NULL を返します。
この結果、trim()
メソッドを呼び出そうとしたときに NULL ポインタ例外をスローします。
String cmd = System.getProperty("cmd");
cmd = cmd.trim();
[対処方法]
こちらも基本的にはバグなので、テスト工程でしっかり検証しましょう。
Web システムの脆弱性を開発者の視点に立ってわかりやすく説明する その1
Web システムの脆弱性を開発者の視点に立ってわかりやすく説明する その2
Web システムの脆弱性を開発者の視点に立ってわかりやすく説明する その3
コメント