マジッククオート機能には頼らない仕事で使える魔法のLAMP(38)

今回は、リクエストデータの扱い関係する設定項目について解説します。特に「マジッククオート」という機能の成り立ちと存在意義について説明します(編集部)

» 2012年01月13日 00時00分 公開
[山口晴広株式会社イメージズ・アンド・ワーズ]

前回の補足

 前回から、PHPの実行時設定の中でも設定を変更した方がよいものを取り上げて解説しています。実行時設定の項目は多岐にわたりますが、中でもサーバ全体にかかわるものを紹介しています。

 本連載のテーマはインフラ構築ですので、運用のしやすさやセキュリティ面での安全性を高めることができる設定について解説しています。今回は、Webブラウザが送信してくるデータ(以下リクエストデータ)の取り扱いにかかわる設定について解説します。

 早速本題に入るべきところですが、前回の内容についてご指摘をいただきましたので補足させていただきます。PHPであることを非公開にするという趣旨の設定のところで、URLを見れば、PHPスクリプトファイルの拡張子からPHPであることが判明してしまう状況になっていたということについてです。

 この点については第34回の末尾でも解説しているのですが、スクリプトファイルの拡張子を別のものにするなどの対策が必要です。

 さらに第24回と前回で取り上げたApache HTTP Server(以下Apache)の設定、今後解説するエラー表示についても対策を打たなければ、完全にPHPであることを隠すことはできませんのでご注意ください(図1)。

図1 PHPスクリプトがエラーで停止し、その結果表示されるメッセージからもスクリプトがPHPであることを読み取れてしまう。 図1 PHPスクリプトがエラーで停止し、その結果表示されるメッセージからもスクリプトがPHPであることを読み取れてしまう。クリックすると拡大

 PHPの設定についての解説をするときに、スクリプトの拡張子をほかのものにしたままでは、解説がややこしくなります。今後の解説でもスクリプトの拡張子には「.php」を使います。運用上、開発上の理由から拡張子を変えられないことも多いかと思いますが、その場合でもApacheのURL書き換えなどでも対応できます。次回以降で解説したいと思います。

 そもそも、こういった情報を非公開にするのは、攻撃の手掛かりになることを懸念してのことです。PHPであること自体を非公開にすることは、なにか別の理由がない限りは、強くこだわらなくても良いと思います。

 しかし、PHPのバージョンは隠した方がよいでしょう。特定のバージョンに脆弱性が見つかったとき、攻撃者がそのバージョンを狙うことも考えられるからです。

マジッククオートとは

 それでは、リクエストデータの取り扱いに関する設定について解説していきます。まずはマジッククオートという機能についてです。マジッククオートとは、リクエストデータに入っているシングルクオート(')やダブルクオート(")、バックスラッシュ(\)を自動的にバックスラッシュでエスケープする機能です。まずはこの機能がどう動くのか確認してみましょう。

 次のスクリプトを、print_get.phpという名前で保存し、PHPスクリプトが実行できる公開ディレクトリに配置します。「print_r」は変数の内容を出力する関数で、配列の中身も分かりやすく表示します。$_GETは、WebブラウザやcurlなどのクライアントソフトウェアがHTTP(HyperText Transfer Protocol)のGETメソッドで送信してきたリクエストデータを格納する変数です。Webブラウザやcurlコマンドを使ってWebサーバと通信するとき、URLの後ろに「?パラメータ名=値」と付け加えることで、GETメソッドを使ってデータを送信できます。

<?php
print_r($_GET);
?>

 それではこのスクリプトに値を送信してみましょう。Webブラウザを使っても良いのですが、今回はcurlコマンドを使います。本連載ではこれまでも何度か使っていますが、指定URLにアクセスして結果を表示するコマンドです。まずは次のようにして、値と一緒にシングルクオートを送信してみます。

$ curl "http://www3026ub.sakura.ne.jp/print_get.php?foo=bar'baz"
Array
(
    [foo] => bar\'baz
)

 送信結果を見ると「bar\'baz」となっています。シングルクオートがエスケープしてあります。

マジッククオートはSQLインジェクション対策?

 そもそもなぜ、こういったエスケープ機能が標準で備わっているのでしょうか。Webアプリケーションではデータベースを利用することが多いのですが、データベースからデータを取り出すときはSQLという言語を使います。SQLで検索の条件などを指定すると、指定したデータを取り出せます。

 ブラウザが送信してきた文字列をそのまま使ってSQLを組み立てるようなプログラムがサーバで動作しているとします。このとき、Webブラウザが送信してきた値にクオートが入っているとちょっと困ったことになります。SQLでは、文字列を指定するときはシングルクオートやダブルクオートで文字列を囲みます。Webブラウザやcurlが送信してきた値を文字列として扱うとき、その中にクオートが入っていると、そこで文字列が途切れることになってしまうのです。

 次のようなSQL文を実行したとします。ユーザー名とパスワードを条件に検索するようなSQL文だと考えてください。このとき、$usernameや$passwordにWebブラウザやcurlが送信してきた値をそのまま使っているとします。

SELECT * FROM user WHERE username='$username' AND password = '$password'

 もし$passwordに「' OR 'x' = 'x」という文字列が入るとどうなるでしょうか。そのまま置き換えると、次のようになります。

SELECT * FROM user WHERE username='$username' AND password = '' OR 'x' = 'x'

 パスワードが、空文字列あるいは、'x'と'x'が等しいとき(つまり常に真)ということになります。こうなると、攻撃者はパスワードを知らなくても検索ができてしまうのです。こういった攻撃を、SQLインジェクションと呼びます。マジッククオートを使えば、スクリプト実行時にPHPがクオートをエスケープしてくれるので、こういった攻撃は通じなくなります。

マジッククオートの悪影響

 マジッククオートは、SQLインジェクションを防ぐために導入された機能ですが、実際のところはかなり問題があります。本来、データベース操作のロジックで対処するべき課題なのに、リクエストデータをすべてエスケープするというのは安直であり、よい方法とは言えません。また、クオートをエスケープするだけですべてのSQLインジェクションを防げるわけでもありません。

 このような事情から、マジッククオートは今後削除されることになっています。いまのところは互換性維持のため、機能は残っており、また標準で有効になっています。すでに使うべきではないという評価が定まってしまった機能ですから、これは設定でオフにすべきでしょう。

 また、この機能はリクエストデータを無差別に書き換えてしまうため、PHPプログラムの互換性にも影響します。スクリプトをほかのサーバに持って行っても、サーバの設定が異なるとスクリプトが正常に動作しなくなるということが考えられるのです。

 プログラム側でマジッククオート機能の有無を確認して動作を変えることもできます。こうなっていないプログラムは、マジッククオートの設定が有効か無効かのどちらかを前提にしたプログラムということになります。最近ではマジッククオートがオフであるという前提になっているものが多いと思います。

マジッククオートをオフにする

 それではマジッククオートをオフにしましょう。この機能は「magic_quotes_gpc」というディレクティブで制御します。末尾の「_gpc」は、GET、POSTというHTTPの送信メソッドと、クッキーの頭文字からきています。つまり、これらの手段で送信されたデータが対象ということです。

 マジッククオートをオフにするには、php.iniファイルに、次の行を追加します。

magic_quotes_gpc = Off

 サーバを再起動して、先ほどと同じコマンドを実行してみましょう。

$ curl "http://www3026ub.sakura.ne.jp/print_get.php?foo=bar'baz"
Array
(
    [foo] => bar'baz
)

 URLで指定した値がそのまま配列に入っています。エスケープがなくなっていますね。

 なお、マジッククオート機能には、リクエストデータだけでなくファイル読み込みなどにも影響する「magic_quotes_runtime」というものもありますが、こちらは標準でオフになっています。

PHP 5.3.9リリース! 一刻も早くアップデート!

 PHP 5.3.9が先日リリースされました。修正項目を見ると、サービスを不能にするいわゆるDoS攻撃を許す脆弱性をつぶすという項目があります。速やかにアップデートした方が良いでしょう。この攻撃手法は昨年末に明らかになったもので、hashdosなどと呼ばれているようです。PHPに限らず、さまざまな言語で有効な手法です。

 今回、GETメソッドで送信されたデータを表示するプログラムを例にしましたが、これからも分かるように、リクエストデータはパラメータ名と値のペアから構成されます。こういったデータはいわゆるハッシュ型の変数に格納されます。

 ハッシュ型の変数は、パラメータ名から「高速に」目的の値を取り出し可能にする仕組みを持っています。通常であればパラメータの数が増えてもその変数を処理する速度は変わらないのですが、「ある特徴」を持ったパラメータ名のデータを多数格納すると、意図的に処理速度を下げることができてしまうのです。格納する数が増えれば増えるだけプロセッサ時間を消費するようになってしまい、DoS攻撃が成立します。

 この「ある特徴」を持ったデータは、作り出すのも簡単ですし、すでにサンプルが出回っています。これを送信するだけでサービス不能になるのですから、この攻撃はかなり簡単に成功してしまいます。それだけ影響範囲は大きくなる恐れがあるのです。

 これに対処するには、リクエストデータの数を現実的な数に制限することです。PHP 5.3.9では「max_input_vars」というリクエストデータの数を制限するディレクティブが新たに使えるようになりました(図2)。

図2 新たに導入された「max_input_vars」ディレクティブの設定値を確認したところ。マニュアルにはまだこのディレクティブに関する記載はない。 図2 新たに導入された「max_input_vars」ディレクティブの設定値を確認したところ。マニュアルにはまだこのディレクティブに関する記載はない。クリックすると拡大

 初期設定値は1000です。1000個以上のパラメータは送信できないということになります。1000個に限定するだけでもかなり効果があるということです。通常、1000個もパラメータを送信することはありませんので、これ以上増やす必要はありません。筆者の経験から考えると、100個のパラメータを送信することすらほとんどありません。100〜200に制限しても良いと思います。

 PHPのアップデートが難しいときは、ほかの方法でパラメータ数を制限することを考えましょう。例えば、Webアプリケーションファイアウォール(WAF)で制限することもできます。hashdos攻撃の特徴として、大量のデータを送信するという点が挙げられます。つまり、パラメータ数ではなく、合計のデータ量を制限することも効果的です。ただし、気付いている方も多いと思いますが、合計のデータ量を制限するという方法は、大きなデータを受け付けること(ファイルのアップロードなど)を前提としないサイトでないと使えないということに注意してください。

 次回も引き続き、リクエストデータに関する設定項目について解説します。

著者紹介

株式会社イメージズ・アンド・ワーズ
代表取締役
山口晴広(やまぐち はるひろ)



「仕事で使える魔法のLAMP」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。