PHPでのSQLインジェクション対策 - プレースホルダ編
第四企画 坂井 潔
SQLインジェクションの脅威からシステムを守るために、プログラミング言語/スクリプトからSQLを発行するときには、パラメータを適切に処理しなくてはなりません。今回はプレースホルダ編と題し、SQLインジェクション対策として最も簡単で効果的な方法を、PHPで説明します。
SQLインジェクションとは?
まず「SQLインジェクション」とは何かおさらいしましょう。SQLインジェクションとは、アプリケーション(この場合はPHPスクリプト)に渡すパラメータの値を操作することで、元々は意図されていない処理をSQLとして実行させてしまうことです。
簡単な例をあげてみます。ユーザーから文字列「山田」が渡されたとき、以下のようなSELECT文を発行することにします。
SELECT * FROM users WHERE username LIKE '%山田太郎%';
ユーザーに入力された文字列を「単にシングルクォートで囲む」というのが一番危険で間違ったエスケープ方法です。pg_queryを例にしてみます。
$res = pg_query( $dbconn, "SELECT * FROM users WHERE username LIKE '%$_REQUEST[username]%'" );
ユーザーが入力した文字列が「山田太郎」であれば、上記のSELECT文は意図した通りの動きをしてくれます。しかしユーザーが 「x'; SELECT password FROM users -- 」と入力した場合にはどうなるでしょう? $usernameの部分にこの文字列を埋めこんでみると、全体では、
SELECT * FROM users WHERE username LIKE '%x'; SELECT password FROM users -- %'
という2つのSELECT文(とコメントアウト)を実行するSQLになり、非常に危険な結果を返すことになってしまいます。これがSQLインジェクションと呼ばれるものの、ひとつの例です。
今回はユーザーが悪意を持って文字列を入力した例をあげましたが、仮に悪意がなくても文字列にシングルクォートやセミコロンを入力することは十分にありえます。「O'Reilly」という名前のユーザーはごく普通に存在していますし、SQL的にはコメントアウトを意味する文字列「--」や「/*」、「*/」にしても、例えばこれらを自己紹介の文字列として使いたい場合もあるかもしれません。文字エンコーディング的に正当な文字列は、全て値として扱いたいわけです。
ですので、可変値、特にユーザーから入力された文字列に関しては、可能な限りプレースホルダを用いてください。どうしてもそれが無理な状況であれば、エスケープを適切に施すことでSQLインジェクションのリスクをある程度軽減することができます。
プレースホルダとは?
それではプレースホルダとはなんでしょうか? プレースホルダを使った例をひとつあげてみましょう。
$res = pg_query_params( $dbconn, 'UPDATE users SET profile = $1 WHERE userid = $2', array($_REQUEST['profile'], $_SESSION['me']['userid']) );
この「$1」と「$2」が今回のプレースホルダです。
この例では、自分自身のuseridに合致する行のprofileを、ユーザーから入力されたデータに変更しろという命令ですので、UPDATE文にはprofileとuseridに対応する二つのパラメータを埋め込みます。SQLを見れば分かるように「$1」にはprofileを、「$2」にはuseridを渡したいので、これらの値を配列で渡します。
SQL中に、後に可変値を埋め込みたい場所を「$1」「$2」あるいは「?」などの特別な文字列、すなわちプレースホルダで確保しておき、ここに埋め込む値はSQL本体とは分離して渡す、というのが根本的な考え方です。これらを別々に渡すことで、SQLサーバは、パラメータの内容はあくまでも「値」として解釈し、たとえその値にSQLとして解釈できる文字列が記述されていても、それをSQLとして実行することはしない、という仕組みです。
これはプリペアドステートメントと呼ばれるテクニックとほぼ同意ですが、後述する「pg_query_params」を使うと、前もってSQLを準備をしておく形には見かけ上はならないことと、SQLインジェクション対策につながる本質的な部分はプレースホルダの利用によるものなので、今回は「プレースホルダ」といっています。
逆に、いくらプリペアドステートメントを使用しても、間違ったプレースホルダの記述をしてしまえば、SQLインジェクション対策としては無効になってしまいますので注意してください。また、PostgreSQLでプレースホルダが使えるのはバージョンが7.4以降です。万が一使用しているPostgreSQLのバージョンがそれよりも古いようでしたら、できるだけ早く、またできるだけ新しいバージョンのものにアップグレードしてください。
それでは、PHPでのプレースホルダの例を見てみましょう。