PHPのsocket_connect()関数における *つまらない* 脆弱性の話

Scan Tech Report(無償版)を読んでいたら、PHP には、socket_connect() 関数の脆弱性CVE-2011-1938)があると紹介されていました。調査の結果、この脆弱性の影響を受けるPHPバージョンとして公開されている情報は間違っているようなので報告します。

概要

Scan Tech Reportには以下のように説明されています。

PHP には、socket_connect() 関数の処理に起因してバッファオーバーフローを引き起こしてしまう脆弱性が報告されました。

http://scan.netsecurity.ne.jp/archives/51983960.html

バッファオーバーフローというと影響が大きそうでびっくりしますが、この脆弱性はあまり話題になっていません。その理由は、この脆弱性が悪用されるシナリオがほとんどあり得ないからです。
もう少し、詳しい脆弱性の条件を読んでみると、次のように書いてあります。

PHP のソケット接続を行う socket_connect() 関数 (ext/sockets/sockets.c) には、UNIX ドメインソケット (AF_UNIX) を利用して通信を確立する際のアドレスの長さを適切にチェックしない不備が存在します。【中略】また、PHP アプリケーションで、AF_INET/AF_INET6 ソケットを利用する場合はこの脆弱性の影響を受けず、AF_UNIX ソケットを利用する場合のみ影響を受けます。

http://scan.netsecurity.ne.jp/archives/51983960.html

UNIXドメインソケットとは、IPアドレスの代わりに特殊なファイルを指定するソケット通信です。ローカルマシン上のプロセス間の通信に用いられます。AF_INET/AF_INET6ソケットとは、IPアドレスを指定する(我々にとって身近な)ソケットですが、こちらは影響を受けません。
該当箇所のソースは次のようになっています。

struct sockaddr_un	s_un;
// ...
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;

memcpy関数でUNIXドメインソケットのソケット名をコピーしていますが、addr_lenのチェックをしていません。絵に描いたようなバッファオーバーフローですね。

影響

この脆弱性(CVE-2011-1938)の影響については以下のように説明されています。

この脆弱性を利用することでローカルの攻撃者は、PHP アプリケーションをクラッシュさせ、サービス不能状態にする、あるいは PHP アプリケーションの権限で任意のコードが実行可能となります。
なお、PHP アプリケーションを外部に公開する環境において、当該関数への入力を第三者が指定可能である場合には、リモートの攻撃者に悪用される可能性があります。

http://scan.netsecurity.ne.jp/archives/51983960.html

ローカルの攻撃者というのは、なんらかの脆弱性を悪用して、任意のPHPスクリプトを実行できる攻撃者を指します。PHPで、任意のサーバースクリプトを実行できる脆弱性の例としては、以下があります。括弧内は、「体系的に学ぶ 安全なWebアプリケーションの作り方」の節番号です。

  • アップロードファイルによるサーバースクリプト実行(4.12.2)
  • ファイルインクルード攻撃(4.13.1)
  • evalインジェクション(4.14.1)

これらの脆弱性を悪用してsocket_connect()関数を含むスクリプトを実行することによりCVE-2011-1938脆弱性をつくわけですが、引用部分にあるように管理者権限がとれるわけではなく、CVE-2011-1938が悪用できる状況にいるローカルの攻撃者(任意のPHPスクリプトを実行可能)は、CVE-2011-1938を悪用しなくてもPHP アプリケーションの権限で任意のコードが実行」などが可能です(な〜んだ)。
次に、「PHP アプリケーションを外部に公開する環境において、当該関数への入力を第三者が指定可能である場合」は、「リモートの攻撃者に悪用される可能性があります」と控えめな書き方がしてありますが、そもそもUNIXドメインのソケット名を外部から指定できるというシナリオが「あり得ない」状況です。UNIXドメインのソケット名を外部に公開する必要がなく、任意のソケット名を外部から指定できることによる脆弱性も考えられるからです。しかし、広い世の中には、そのような愚かな実装が絶対にないとは言い切れません。

この脆弱性の影響を受けるPHPバージョン

CVE-2011-1938の影響を受けるPHPバージョンは、以下のように説明されています。

3.影響を受けるソフトウェア
PHP 5.3.3 - 5.3.6

http://scan.netsecurity.ne.jp/archives/51983960.html

私はこれを見て疑問に思いました。PHP5.3.3以降というのはどういうことでしょうか。普通に考えると、PHP5.3.3の変更で脆弱性が作り込まれたように見えますが、先のソースの状況から見て、あり得ないような気がします。試みに、PHP5.3.2とPHP5.3.3でsockets.cを比較したところ、変更はありませんでした。
このため、手元のPHP5.2.16(CentOS5.6上のyumでインストールしたもの)で、以下のコードを実行してみました*1

<?php
$socket = socket_create(AF_UNIX, SOCK_STREAM, 1);
$addr = str_repeat("A", 169);  // 'A'を169個並べた文字列
socket_connect($socket, $addr);

実行結果は、「セグメンテーション違反です」となります。どの程度の悪用が可能となるかはともかく、PHPをクラッシュさせられるので脆弱性があることは間違いありません。
以下は推測ですが、影響を受けるバージョンを「PHP 5.3.3 - 5.3.6」としたのは、脆弱性を確認したバージョンであって、これらのパージョンだけに脆弱性があるという意味ではないと思われます。

対策

Scan Tech Reportには以下のように書かれています。

現時点 (2011/6/6) では、この脆弱性を解消する PHP バージョンは公開されていないため、解消バージョンが提供されるまでの間、以下に記載する対策を実施することを推奨します。

http://scan.netsecurity.ne.jp/archives/51983960.html

そして、後ろの方を見ると、「5.対策(Web非公開)」とあります。公開されているレポートは、有償メールマガジンの無償版、つまりは宣伝ですから、肝心なところが読めないのは仕方ないですね。このレポートを書いた株式会社ラック コンピュータセキュリティ研究所に成り代わって、対策(回避策)を検討してみましょう。

1.socket_connect()関数の呼び出しを禁止する

まず、socket_connect()関数をアプリケーションで使っていない場合は、実質的な影響がないため、「なにもしない」という選択はありだと思います。しかし、念を入れるためには、socket_connect()関数を*使えなくしてしまう*指定が可能です。php.iniに以下の指定を追加します。

disable_functions = "socket_connect"

この指定により、socket_connect()関数の実行を禁止することで、同関数の脆弱性が悪用されることを防止できます。
また、socket_connect()関数を利用している場合でも、プロセス間通信を他の関数で実現するようにアプリケーションを書き換えた上で、socket_connect()関数を禁止することは可能です。

2.socket_connect()関数の呼び出しは許容し、パラメータを固定にする

次に、socket_connect()関数をアプリケーションで使っていて、他の関数による書き換えをしない場合は、socket_connect()関数を禁止することはできません。この場合、socket_connect関数に渡すソケット名が外部から指定できない(固定の場合など)は、何もしなくて良いと思います。socket_connect()関数の脆弱性により、リスクが増加することは実質的にはないからです。
ソケット名を外部から指定できる場合は、大きな影響があります。この場合、任意のソケット名を外部から指摘できること自体が問題なので、外部からソケット名を渡す仕様を見直してください。

まとめ

CVE-2011-1938について調査した結果をまとめました。CVE-2011-1938により実害があるケースは少なそうですが、一方、対象となるPHPバージョンは間違って公表されているようなので、socket_connect()関数を利用しているサイトは注意が必要です。

  • CVE-2011-1938はバッファオーバーフローとはいえ悪用されるシナリオはあまりない
  • socket_connect()関数を禁止すると安心
  • ソケット名を外部から渡す仕様を見直す
  • 影響を受けるPHPバージョン情報はおそらく間違いなので気をつけよう

追記(2011/06/09 9:30)

この脆弱性の対象バージョンとしてPHP5.2.xが言及されていない理由は、PHP5.2が既にサポート終了しているからとも考えられます。PHP5.2の最後のバージョンは5.2.17(2011/1/06)です。早めのPHP5.3への移行を推奨します。

追記(2014/05/03 14:35)

ソースコードを確認したところ、この脆弱性は、PHP 5.2.7にて混入していることが分かりました。

*1:もっとちゃんとしたPoCはhttp://seclists.org/fulldisclosure/2011/May/472を参照してください