はじめに
いよいよ新年度が本格的に始まりましたね。新年度ということで初心に立ち返り、今回は基本的な脆弱性の1つである
今回の脆弱性:CVE-2011-1938
CVE-2011-1938は、PHPに標準的に用意されているsocket_
関数の中に潜んでいた、スタックバッファオーバーフローの脆弱性です。
「PHPに潜む脆弱性って、なんだかイメージが湧きづらい……」socket_
関数の引数として渡して実行することで脆弱性が発現します。
実際にそのようなプログラム
$ php CVE-2011-1938.php *** buffer overflow detected ***: php terminated Aborted (core dumped)
このプログラムの内部では、いったいどのような処理がなされているのか、読者としては気になるところだと思います。ですが、中身の解説を行う前に、その理解を促すためにも、まずは
スタックバッファオーバーフローとは
まずバッファオーバーフローとは、
メモリ領域にもさまざまな種類が存在し、関数内で利用する一時的なローカル変数などを保存する領域は
ここで、

脆弱性を突く手法
脆弱性を悪用する際は、前述の挙動が利用されます。やり方はさまざまあるのですが、代表的な手法としては
とある関数内でさらに別の関数を呼び出すことは、プログラミングをしたことのある方ならほぼ誰もが経験したことがあると思います。一般的に、このような関数呼び出しが行われる際、呼び出し元の関数に戻るための
スタックバッファオーバーフローの脆弱性があった場合、その戻りアドレスを上書きすることが可能になります。図3のように、たとえば戻りアドレスを

CVE-2011-1938.phpの中身
では、スタックバッファオーバーフローの概念について理解できたところで、いよいよPHPを異常終了させたプログラムであるCVE-2011-1938.
リスト1のとおり、CVE-2011-1938.
<?php
$socket = socket_create(AF_UNIX, SOCK_STREAM, 1);
$address = str_repeat("A", 1000);
socket_connect($socket, $address);
?>
ソケット通信(UNIXドメインソケット)とは
そもそも、今回の脆弱性が存在するsocket_
関数は、PHPでソケット通信を行う際に利用するものです。ネットワークプログラミングを行う人には馴染み深いものだと思いますが、ソケットとは、プログラムがネットワーク通信や、ほかのプログラムとの通信を行う際に用います。通信のための接続口だとイメージすればわかりやすいと思います。
ソケットを利用する際には、簡単に言えば接続先や、接続をする際に利用したいプロトコル
「UNIXドメインソケット」

以上までわかったところで、CVE-2011-1938.
プログラムの解説
CVE-2011-1938.socket_
関数を利用してソケットを作成しています。その際、UNIXドメインソケットを利用したいため、第1引数にはAF_
と指定しています。また作成したソケットは、socket
という名の変数に格納しています。
関数名 | 概要 | 第1引数 | 第2引数 | 第3引数 |
---|---|---|---|---|
socket_ |
ソケットを作成する | ソケットが利用するプロトコル |
ソケットが利用する通信方式 | ソケットが利用するプロトコル |
str_ |
文字列を反復する | 繰り返す文字列 | 繰り返す回数 | |
socket_ |
ソケット上の接続を初期化する | socket_ で作成したソケット |
AF_ が指定された場合は、ソケットファイルのパス名 |
(AF_ の場合必要なし) |
次にAを1,000回繰り返した文字列を、str_
関数を利用して生成し、address
という変数に保存します。最後にsocket_
関数にて、ソケット上の接続を初期化しています。その際ソケット名としては、address
に格納されている
何が問題なのか
このプログラムの何が問題かというと、ソケット名が入るべきsocket_
の引数に、大量のAという文字列を入れていることです。後ほど詳しく解説しますが、このソケット名を保存するために、スタック上にて固定サイズのバッファが確保されていました。開発者が想定したようなソケット名であれば、この仕様に何の問題もありません。ですが今回のように、そのバッファを上回るような非常に長いソケット名が指定された場合、バッファオーバーフローが発生してしまうのです

実際の脆弱性箇所について
脆弱性の概要が理解できたところで、実際の脆弱性箇所を見ていきます。脆弱性自体はsocket.
というファイルの中のリスト2の部分に存在していました。この部分は、socket_
関数が呼び出された際、AF_
case AF_UNIX:
memset(&s_un, 0, sizeof(struct sockaddr_un));
s_un.sun_family = AF_UNIX;
memcpy(&s_un.sun_path, addr, addr_len);
retval = connect(php_sock->bsd_socket, (struct sockaddr *) &s_un, (socklen_t) XtOffsetOf(struct sockaddr_un, sun_path) + addr_len);
break;
今回、脆弱性の種類がスタックバッファオーバーフローとわかっているので、
バッファオーバーフローが引き起こされる箇所
解析した結果、リスト2の中でもmemcpy
関数が呼ばれている次の箇所で、バッファオーバーフローが引き起こされていました。
memcpy(&s_un.sun_path, add, addr_len);
memcpy
自体は、指定バイト数分のメモリをコピーする、C言語を利用する方には馴染み深い関数で、表2のような3つの引数を受け取ります。
memcpy
関数の引数について引数 | 引数の定義 | 意味 | 今回の場合 |
---|---|---|---|
第1引数 | void *dest |
コピー先のメモリ領域 | &s_ |
第2引数 | const void *src |
コピー元のメモリ領域 | addr |
第3引数 | size_ |
コピーするデータのサイズ | addr_ |
今回の場合、コピー先の領域としてはs_
が指定されており、これがソケット名を格納するためのバッファにあたります。
そしてコピー元の領域であるaddr
に、ユーザーが指定したソケット名が格納されています。最後にaddr_
には、ユーザーが指定したソケット名の文字列の長さが格納されています。
今回の脆弱性は、ソケット名を格納するための領域s_
)s_
を詳しく解析していきます。
オーバーフローするバッファ
脆弱性箇所から上にさかのぼってソースコードを読んでいくと、s_
は、sockaddr_
という構造体の変数であることが判明しました。
struct sockaddr_un s_un;
この構造体はいったい何者なのでしょうか。
調べてみると、これはUNIXドメインソケットを利用した場合に、ソケットの情報を格納するための構造体であることが判明しました。リスト3がこの構造体の定義です。構造体のメンバの1つとして、char型の配列sun_
が定義されているのがわかります。そしてsun_
は、配列サイズとしてはUNIX_
が指定されています。では、このUNIX_
は何かというと、ソケットファイル名の最大長を定義したものであり、Linuxでは108であることがわかります。
sockaddr_un
構造体の定義(Linuxのmanページより抜粋。ページ名は「unix」)#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
まとめると、s_
はソケット名を格納するために用意されたchar型の配列であり、そのサイズは108バイトであるということです。また一般的に、関数内で宣言/s_
も同様です。そのため、108バイトを超える文字がソケット名として指定されると、図5のようなスタックバッファオーバーフローにつながってしまうのです。
脆弱性の修正方法
では、この脆弱性が実際にどのように修正されたかを見ていきます[3]。リスト4が修正後のソースコードです。読んでみると、脆弱性箇所の直前に、新たにif文が追加されたのがわかります。
case AF_UNIX:
+ if (addr_len >= sizeof(s_un.sun_path)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Path too long", php_sock->type);
+ RETURN_FALSE;
+ }
+
memset(&s_un, 0, sizeof(struct sockaddr_un));
s_un.sun_family = AF_UNIX;
memcpy(&s_un.sun_path, addr, addr_len);
このif文では、ユーザーから入力されたソケット名の長さaddr_
)sizeof(s_
)
最後になりましたが、この脆弱性はPHPのバージョン5.
さらに勉強したい人向け
さらに勉強したい方に向けて、関連する書籍などを紹介します。まず、スタックバッファオーバーフローの原理や対策についてさらに詳しく書いた書籍として、筆者の著書
それではみなさん、また次回お会いしましょう!