なぜPHPアプリにセキュリティホールが多いのか?

第34回MOPS:コードの品質とソースコード監査の重要性

第32回 PHPセキュリティ月間(Month of PHP Sercurity)で「PHPセキュリティ月間」(MOPS - Month of PHP Security)について簡単に紹介しました。

今回もMOPS関連の話題です。MOPSでmysqlnd(MySQL Native Driver)に発見された脆弱性を例に、コードの監査の重要性を解説します。

MySQL Native Driverとは?

MySQL Native Driver(以下mysqlnd)とは、MySQL ABが提供するMySQL用のライブラリlibmysqlがPHPライセンスと互換性のないライセンスで配布されていた問題に対応するために開発された新しいMySQLサーバ接続(クライアント)ライブラリです。PHP 5.3から利用できます。

mysqlndはlibmysqlとは異なり、クライアント側でのキャッシュを有効にするなど、PHPに最適化されたライブラリになっています。PHPのモジュールとして実装されていますが、モジュール関数を提供しない特殊なモジュールです。mysqlndはmysqliモジュールやPDOのMySQLドライバを介して利用します。

MOPSで報告されたmysqlndの脆弱性

MOPSでは以下のmysqlnd関連の脆弱性が報告されました。

  1. php_mysqlnd_ok_read関数のヒープメモリ情報リーク
  2. php_mysqlnd_rset_header_read関数のバッファオーバーフロー
  3. php_mysqlnd_read_error_from_line関数のバッファオーバーフロー
  4. php_mysqlnd_auth_write関数のスタックオーバーフロー

これらの脆弱性はMOPSの主催者でもあるStefan Esser氏によって発見された脆弱性です。これらの脆弱性の中身を紹介します。

php_mysqlnd_ok_read関数のヒープメモリ情報リーク

この脆弱性は、ネットワークを越えたデータを信頼することによって発生しました。ネットワーク経由のデータを信頼してはならないことはセキュリティ対策の基本ですが、この基本が守られていませんでした。

以下が問題のコードです。

mysqlnd_wireprotocol.c
/* There is a message */
if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) {
    packet->message = mnd_pestrndup((char *)p, MIN(i, buf_len - (p - begin)), FALSE);
    packet->message_len = i;
} else {
    packet->message = NULL;
}

この packet->message_len は php_mysqlnd_net_field_length(&p) から取得されています。しかし、この値はパケットに記載されている長さです。つまりネットワーク経由でサーバから送信された値です。従って、細工したパケットを送ることによりmessageを利用する関数でメモリ情報を不正に参照することが可能となってしまいます。

php_mysqlnd_rset_header_read関数のバッファオーバーフロー

このバグは先ほどの脆弱性で問題点を指摘されている php_mysqlnd_net_field_length(&p) が利用されています。

mysqlnd_wireprotocol.c
if (packet->header.size  > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) {
    packet->info_or_local_file = mnd_emalloc(len + 1);
    memcpy(packet->info_or_local_file, p, len);
    packet->info_or_local_file[len] = '\0';
    packet->info_or_local_file_len = len;
}

lenはsingned intとして定義されています。64bit/32bit環境の両方に対応したプログラムを書く人で、セキュリティの基本を理解している方には問題がすぐに分かったと思います。このコードではlenが-1となり、バッファにメモリが割り当てられていないにも関わらず、4GBまでも書き込みを許してしまうことがあります。signed intは64bit環境では32bitの整数として定義されていますが、memcpyなどの関数パラメータはsize_tと定義され、64bit環境では64bitの整数です。

php_mysqlnd_read_error_from_line関数のバッファオーバーフロー

php_mysqlnd_read_error_from_line関数はMYSQLND_SQLSTATE_LENGTHが設定されない場合を考慮していません。このためバッファオーバーフローの可能性があります。

mysqlnd_wireprotocol.c
f (buf_len > 2) {
    *error_no = uint2korr(p);
    p+= 2;
    /* sqlstate is following */
    if (*p == '#') {
        memcpy(sqlstate, ++p, MYSQLND_SQLSTATE_LENGTH);
        p+= MYSQLND_SQLSTATE_LENGTH;
    }
    error_msg_len = buf_len - (p - buf);
    error_msg_len = MIN(error_msg_len, error_buf_len - 1);
    memcpy(error, p, error_msg_len);
} else {
    *error_no = CR_UNKNOWN_ERROR;
    memcpy(sqlstate, unknown_sqlstate, MYSQLND_SQLSTATE_LENGTH);
}

error_msg_lenは buf_len > 2 で buf_len < 2 + MYSQLND_SQLSTATE_LENGTH の条件が成り立つ場合、error_msg_lenが負の整数となり、memcpyで4GBまでの書き込みを許してしまいます。

これも前の問題と同様に64bit/32bit環境の両方に対応したコードを書くプログラマであれば理解しておくべき問題です。

php_mysqlnd_auth_write関数のスタックオーバーフロー

典型的なスタックオーバーフロー脆弱性です。

mysqlnd_wireprotocol.c
char buffer[AUTH_WRITE_BUFFER_LEN];
register char *p= buffer + MYSQLND_HEADER_SIZE; /* start after the header */
(省略)
memset(p, 0, 23); /* filler */
p+= 23;

if (!packet->send_half_packet) {
    len = strlen(packet->user);
    memcpy(p, packet->user, len);
    ...

スタック上に作られたバッファpに対して、ネットワーク上から読み取ったユーザ名とその長さを利用してメモリに書き込んでいます。

ユーザ名のほかにもデータベース名にも同様のバッファオーバーフロー脆弱性があります。Stefan Esser氏によると、最近のgccの保護機能によりユーザ名のスタックオーバーフローは不可能だが、データベース名のほうはスタックオーバーフローが可能であるとしています。

オープンソースとセキュリティ

いくつかのオープンソースプロジェクトに関わってみれば分かることですが、オープンソースモデルの開発であっても複数の開発者がセキュリティ検査を目的としたソースコードレビューを日常的に行っている訳ではありません。

PHPのソースコードをコミット(ソースコードレポジトリへの書き込み)を行える開発者のスキルレベルの問題であるとも言えますが、Microsoft、Apple、Adobeなどの大手ソフトウェア会社の製品にもこの種のバッファーオーバーフロー脆弱性が後を絶ちません。一概にPHP開発者のスキルレベルが低いという仮定は成り立たないでしょう。

オープンソースモデルで開発しているプロジェクトであっても、ソースコードの監査は欠かせません。多くの開発者がソースコードを目にする機会があるはずのオープンソースモデルの製品でさえ、ソースコード検査で脆弱性が見つかってしまうのが現状です。クローズドソースである場合はなおさらソースコード検査は重要です。

まとめ

今回はMOPSでレポートされたmysqlndの脆弱性が基本的なソースコード検査で見つかることを紹介しました。この脆弱性はPHP本体の脆弱性ですが、PHPアプリケーションやほかの言語のアプリケーションであってもソースコード監査の重要性を理解して頂けたのではないでしょうか?

mysqlndのコードはネットワーク経由のデータを不用意に信用してしまうことによって脆弱にとなっていました。Webアプリケーションもまったく同じです。ネットワーク経由で送られたデータはもちろん、データソースが信頼できないデータは例えローカルファイルであっても信用してはなりません。

データを利用する場合は常にデータソースとデータの利用のされ方に注意しなければならないことは言語やアプリケーションを問いません。

おすすめ記事

記事・ニュース一覧