PHP覚え書きブログ
【2012年02月06日】
SQLインジェクション対策としての文字列や数値の入力に対してのエスケープについては数日前のブログで紹介してきたが、もう一つ方法がある。
通常、SQLの条件式などは文字列としてSQL文に埋め込み、
SQL文という文字列をそのままデータベースに投げ、その結果を得るというものである。
それがため、文字列を操作してアプリの想定外のSQLを実行させるというのがSQLインジェクション。
少し考え方を変え、
ベースとなるSQL分と、条件の値とを別々にデータベースへ投げ、
データベースで明確にSQL文と値を区別させた上でSQL文に値をバインドさせることができるのである。
このSQL文はプリペアドステートメントと呼び、条件の値の部分は変数化しておく。
<?php $sql = " SELECT * FROM m_user WHERE user_id = :user_id AND password = :password "; $pdo = new PDO(・・・); $stmt = $pdo->prepare($sql); $stmt->bindParam(':uer_id', $userId, CPDO::PARAM_STR); $stmt->bindParam(':password', $password, CPDO::PARAM_STR); $ret = $stmt->execute(); ?>
SQL文はこんな感じになる。直接値を埋め込まない。
例えばMySQLならPHPの拡張モジュール、mysqliを使用することでこの仕組みを利用できるのだが、
今やるなら上記の例でも使っているPDOのほうがよい。PDOはPHPの拡張モジュールの一つだが、現在のバージョンでは標準搭載されている。
まずプレースホルダを含んだSQLをDBに対してプリペアする。
プリペアというのはすぐにSQL文を実行するのではなく、とりあえず解釈だけさせる。
その後、プレースホルダに対して値を割り当て、最後に実行。という具合。
仮に$passwordという変数に「' OR 'baka' = 'baka'」
が入っていたとしても、それはそういう値としてテーブルのpasswordフィールドに保存する。
まあ実際には内部的にシングルクォートが適切ににエスケープされるわけだが。
プリペアドステートメントを利用しない場合、
「password = " . $password . "」の$passwordの部分にに埋め込む文字列によっては、
アプリで想定されていない条件文を付加したり、SQL分の終端を表す「;」を入れることによって
別なSQL文を同時に実行させることが可能なわけだが、
プリペアドステートメントではプリペアの時点で、「今回実行するSQL文はこんなのですよ」って教えてるようなもので、
どのテーブルからどんな情報が欲しくて、その条件はこれとこれですよ、ってあらかじめ確定しておくのである。
後から変数部分に何を埋め込もうがそれはかわらない。どんな値がこようが値は値として受け入れる。
なので数値項目に文字列が入っていたら当然エラーになる。
SQLインジェクション対策にこれほど有効なものはないのである。
副次的なメリットとしてPHPに書くSQL文が見やすくなるということも言える。
普通にSQL文に文字列を埋め込もうとすると結合とかが発生してどうしてもゴチャつく。
多少コーディング量は増えるかもしれないが、確実にコードが整理される。
更に文字列項目にはシングルクォーテーションを付ける必要もない。
なぜならプリペアによってどの位置に変数を割り当てるかを指定しているから。
SQL文を、値を含めた文字列として実行する場合、文字列項目には、
どこからどこまでが値なのかの範囲を示す為にクォーテーションで囲う。
しかしプリペアでは変数は後から別で割り当てる為、範囲もクソもない。
基本的にデメリットはないんじゃないかと思うので、データベースアクセスは常にプリペアドステートメントで決まり。


