技術者ブログ

クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

2017年10月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
  • お問い合わせはこちら
Scutum開発者/エンジニアによる技術ブログ WAF Tech Blog

PHP環境で発生するMongoDB Request Injection攻撃

2011年8月 3日
市川 (@cakephper)

はじめに

SaaS型WAF「Scutum(スキュータム)」では、最近バックエンドのDBにMongoDBの導入を進めています。私も個人的にPHPのフレームワークであるCakePHP向けにMongoDBを扱うプラグインCakePHP-MongoDB Datasourceを開発し、公開しています。
https://github.com/ichikaway/cakephp-mongodb/

今回は、PHPからMongoDBを扱う際の注意点として、RequestInjection攻撃に関して書きたいと思います。この問題は、phpマニュアルのMongoドライバーの章でも扱われています。
この記事では、はじめにPHPでMongoを操作する方法、PHPの基本を書き、最後にInjection攻撃の仕組みと対応方法を書きます。

PHPでMongoDBを扱うには

PHPでMongoDBを操作する場合、10gen社が開発しているpecl mongoドライバーを利用します。データをusersコレクション(Table)から取得する場合は、下記のようにします。

$mongo = new Mongo('mongodb://localhost:27017');
$collection = $mongo->selectDB("foo")->selectCollection("users");
$cursor = $collection->find(array('status' => array('$gte' => 1)));

findの検索条件は、連想配列で記述します。MongoDBのオペレータは配列のKeyに文字列として '$gt' というように記述します。ここでは、usersコレクションのstatusカラムに、1以上がセットされているユーザを取得する条件がセットされています。

PHPの基本

PHPではGet/Postリクエストで送信したデータが$_GET/$_POSTというグローバル変数に配列として格納されます。例えば、
index.php?foo=bar というURLにアクセスすると、$_GET['foo']にbarという文字列が入ります。
さらに、クエリストリングのKeyを配列のように記述すると、サーバ側では配列として格納されます。
例えば、 index.php?foo[foo2][foo3]=bar
というURLにアクセスすると、$_GET['foo']['foo2']['foo3']にbarという文字列が入ります。

PHPへのMongoDB Request Injection攻撃

ここで説明するInjection攻撃は、外部からMongoDBオペレータを注入できるというものです。例えば、usersコレクションから該当ユーザをID/PWで引き当てる場合、

$cursor = $collection->find(array(
   "username" => $_GET['username'],
   "passwd" => $_GET['passwd']
));

というような処理があったとしましょう。
そのphpスクリプトに対して、下記のようなリクエストを送ると、adminユーザが引き当てられてしまいます。

login.php?username=admin&passwd[$ne]=1

これは「PHPの基本」の章で書いたように、PHPはKeyの記述によってそのまま配列に展開する機能があるためです。実際のプログラムでは下記のように実行されてしまいます。

$cursor = $collection->find(array(
   "username" => "admin",
   "passwd" => array("$ne" => 1)
));

PHPが送信データのKeyの文字列を解析して配列に展開してしまうことと、MongoDBのオペレータが$gtのような文字列で指定できることが組み合わさり、外部からMongoDBオペレータを注入できるというのが、この問題の原因です。

※今回はGETで説明していますが、POSTも同様の問題があります。

対応方法

この問題を回避するために、文字列で値を指定する箇所は下記のようにします。配列で値が渡ってきたとしても、username/passwdの値はArrayという文字列になるため問題を回避できます。

 $cursor = $collection->find(array(
   "username" => (string)$_GET['username'],
   "passwd" =>  (string)$_GET['passwd']
 ));

上記の対応方法は、アプリケーションを実装する上で常に注意する必要があります。面倒な場合は$_POST/$_GETなどのユーザ入力値を全てチェックして、Keyに不正な文字(ここでは$文字)が含まれている場合は、エラーやunset()で値を消すという方法もあります。この方法は毎回ユーザ入力データをスキャンするため多少負荷がかかりますのでご注意ください。

この方法を手軽に実現するために、私が開発している下記のツールがあります。
https://github.com/ichikaway/SecurePHP

このツールをダウンロードし、下記のようにbootstrap.phpをアプリケーションの最初に読み込むと、Keyの文字チェック、値のnullバイトチェックなどを行います。

require_once(YOUR_LIB_PATH . 'SecurePHP/config/bootstrap.php');

配列のKeyは、英数字と:_./-のみを許すようにしていますので、$が含まれていると値がunsetされます。これは$allowKeyNameRegexというクラス変数で定義しているので、アプリケーションにフィットしなければ変更してご利用ください。

結論

PHPでMongoDBを扱うのはPeclMongoドライバーのおかげで非常に簡単です。ただし、このような言語の特性によって起こる問題もあるため、常に最新のPHPマニュアルを参照すると良いと思います。英語ドキュメントが一番更新が早いため、そちらをチェックすることをお勧めします。

参考資料

「Do I Have to Worry About SQL Injection」
http://www.mongodb.org/display/DOCS/Do+I+Have+to+Worry+About+SQL+Injection

「PHPマニュアル」
http://www.php.net/manual/en/mongo.manual.php#mongo.security

「Mongodb is vulnerable to SQL injection in PHP at least」
http://www.idontplaydarts.com/2010/07/mongodb-is-vulnerable-to-sql-injection-in-php-at-least/