7-1 データベースへの攻撃と防御策 [PHPセキュリティー]
データベースのセキュリティーの考え方
- データベースはリモートソース
- データベースとの送受信データを適切にフィルタ・エスケープする
PHPから見るとデータベースはリモートソースとなります。リモートソースを起源とするデータは入力と識別して適切にフィルタを行う必要があります。入力フィルタリングに関してはこちらを参照下さい。また、リモートソースへデータを出力する際も適切にデータをエスケープする必要があります。エスケープに関してはこちらを参照下さい。データベースから取得するデータをフィルタし、データベースへ送信するデータをエスケープすることで、データベースとPHPとの間で安全なやり取りが出来ます。
データベースへのアクセス認証ファイル
- データベースアクセス認証ファイルはドキュメントルート以外に設置
- データベースアクセス認証ファイルは config.inc などのファイル名は避ける
- データベースアクセス認証ファイルは .php にする
データベースへアクセスする認証情報が記載されたファイルは大切な機密情報です。データベースは機密性の高い情報を扱う場合が多いので、認証情報が記載されたファイルの扱いには特に注意が必要です。このような機密性の高いファイルは必ずドキュメントルート(webサーバ上に公開するためのルートディレクトリ)以外に設置しましょう。また、ファイル名やその拡張子を config.inc のような一般的に設定情報を扱うファイルとして認識されてしまうような名前は避けましょう。また、拡張子は .php として、PHPファイルとして扱うようにしましょう。
データベースへの攻撃 SQLインジェクション(SQL Injection)
- SQLインジェクションはデータベースを不正操作する攻撃
SQLインジェクションとは、アプリケーションのセキュリティ上の脆弱性を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースから不正に情報を引き出したり、データベースを不正に操作したりする攻撃方法のことです。SQLは、データベースを操作するために一般的に使用されている言語です。
アプリケーションに入力されるデータのフィルタリング(入力のフィルタ)とデータベースに送信されるデータのエスケープ(出力のエスケープ)が適切に行われていない場合に、SQLインジェクションを受けます。
SQLインジェクションの例
以下のコードはSQLインジェクションの可能性があります。
1 2 3 4 5 6 | //パスワード $hash = hash($_POST['password']); //SQLクエリの作成 $sql = "select count(*) from users where username = '{$_POST['username']}' and password = '$hash'"; //SQL文実行 $result = mysqli_query($sql); |
このコードの問題点は $_POST['username'] をエスケープしていない箇所にあります。 $_POST['username'] に悪意のある値を渡すことで、アプリケーションが意図しないSQL文が実行されてしまう可能性があり、その場合、本来の機能を変えてしまうことができます。
例えば、$_POST['username'] の入力データが「yamada' —」だった場合、
1 2 3 4 5 6 | //パスワード $hash = hash($_POST['password']); //SQLクエリの作成 $sql = "select count(*) from users where username = 'yamada' --' and password = '$hash'"; //SQL文実行 $result = mysqli_query($sql); |
となります。SQL文ではハイフンが2つ続くとそこから先はSQLコメントとして扱われますので、以下と同じ意味になります。
1 | select count(*) from users where username = 'yamada' |
「–」以下の文がコメントとして扱われることになり and password = ‘$hash’ が無視されます。
このSQLが実行されるとパスワードを一切しらなくてもログインできることになってしまいます。
SQLインジェクションの例
以下のコードはSQLインジェクションの可能性があります。
1 2 3 4 | //SQLクエリの作成 $sql = "select * from users where userid = '{$_POST['userid']}' and password = '$_POST['password']'"; //SQL文実行 $result = mysqli_query($sql); |
このコードの問題点は $_POST['userid'] $_POST['password'] をエスケープしていない箇所にあります。
入力値が以下の場合、
1 2 | $_POST['userid'] が yamada $_POST['password'] が ' OR 'A'='A |
SQL文は以下のようになります。
1 2 3 4 | //SQLクエリの作成 $sql = "select * from users where userid = 'yamada' and password = '' OR 'A'='A'"; //SQL文実行 $result = mysqli_query($sql); |
OR の後は常に真 'A=A' なので、パスワードを知らなくても常に認証されてしまいます。
セカンドオーダーSQLインジェクション
SQLインジェクション(SQL Injection)の防御策
- フィルタとエスケープを適切に行うことで攻撃を防げる
- エスケープは mysqli_real_escape_string()関数を使用
- mysqli_real_escape_string()関数を使用するには、MySQL接続が確立されていること
- magic_quotes_gpc が有効な場合は、最初に stripslashes() を適用する
※PHP5から mysqli_real_escape_string()の代替として、
mysqli_real_escape_string()
PDO::quote()
の使用が推奨されています。
SQLクエリを作るために使用されるデータを完全にフィルタし、フィルタされた汚染リスクのないデータを適切にエスケープすることで、SQLインジェクションのリスクはなくなります。
エスケープには、mysqli_real_escape_string()関数が利用できます。この関数は、SQL文中で用いる文字列の特殊文字をエスケープします。
以下の文字の前に「\(バックスラッシュ)」を付加します。
1 2 3 4 5 6 7 | \x00 → \\x00 \n → \\n \r → \\r \ → \\ ' → \' " → \" \x1a. → \\x1a. |
mysqli_real_escape_string()関数を使用するには、MySQL接続が確立されていなければなりません。また、magic_quotes_gpc が有効な場合は、最初に stripslashes() を適用する必要があります。そうしないと、エスケープされているデータを更にエスケープすることになります。mysqli_real_escape_string() は % や _ をエスケープしません。 MySQL では、これらの文字を LIKE, GRANT, または REVOKE とともに用いることで、 ワイルドカードを表現します。
MySQLエスケープの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //変数初期化 $clean = array(); //フィルタ $clean['username'] = $_POST['username']; //ハッシュ値へ $hash = hash($_POST['password']); //変数初期化 $mysql = array(); //エスケープ $mysql['username'] = mysqli_real_escape_string($clean['username']); //SQLクエリ作成 $sql = "select count(*) from users where username = '{$mysql['username']}' and password = '$hash'"; //SQL文実行 $result = mysqli_query($sql); |
MySQLエスケープ定義関数の例
1 2 3 4 5 6 7 8 9 10 | function MysqlEscape($index, $maxlength, $dbConnect){ if (isset($index)){ //substr文字列を切り取る 切り取る最初(0)と最後の位置を指定 $input = substr($index, 0, $maxlength); //エスケープ $input = mysqli_real_escape_string(get_magic_quotes_gpc() ? stripslashes($input) : $input, $dbConnect); return ($input); } return NULL; } |
PEAR::DBやPDO
PEAR::DBやPDOなどのバウンドパラメータやプレースホルダをサポートするデータベースライブラリを使えば、さらなるレイヤでSQLインジェクションを防ぐことが出来ます。
バインド変数の利用
- PHPの拡張PDO(PHP Data Objects)でバインド変数の利用
バインド変数を使用することでSQLインジェクションを最適に防ぐことが出来ます。
バインド変数はPHPの拡張PDO(PHP Data Objects)で利用できるようになります。
1 2 3 | $sql = $db->prepare('select count(*) from users where username = :username and password =:hash'); $sql->bindParam(':username',$clean['username'],PDO_PARAM_STRING,32); $sql->bindParam(':hash',$_POST['password'],PDO_PARAM_STRING,32); |
日本語(マルチバイト文字:SHIFT-JIS、EUC-JPなど)とSQLインジェクション対策
- マルチバイト文字の不正によるSQLインジェクションに注意
- UTF-8にしてデータベースへ渡す
マルチバイト文字は2バイト文字などであるSHIFT-JISやEUC-JPなどです。SQLインジェクションを防止するためには「'(シングルクォート)」などの文字に対するエスケープ処理を行う必要があります。その際、日本語のようなマルチバイト文字でエスケープを行う場合、不正なマルチバイト文字に対して注意する必要があります。
以下の例を考えてみましょう。
1 | \x97' OR A=A → 予' OR A=A |
「\x97' OR A=A」文字列をエスケープ処理した場合、「予' OR A=A」と変換されることがあります。これはSHIFT-JISで発生する現象です。
1 | \x97' → \x97\' |
「\x97'」の部分の文字列の「'」をエスケープ処理すると「\'」となり、「\x97\'」となります。
「\'」の部分もエンコードすると、「\x5C\x27」となり、全体では「\x97\x5C\x27」となります。
1 | \x97\x5C\x27 → 予' |
これを文字に戻すと、手前から「\x97\x5C」が2バイト文字として扱われて「予」となり、「\x27」が1バイト文字として扱われてしまい「'」となってしまいます。
1 | 予' OR A=A |
結果として、「'」を「\'」とエスケープしたはずなのに、「'」が残ってしまうという現象が発生します。
SHIFT-JIS文字列のままデータベースに渡すことは避けましょう。
mb_convert_encodingなど使用してUTF-8などの文字列に変換することでリスクを軽減できます。
1 2 | $mysql['username'] = mb_convert_encoding($_POST['username'],'UTF-8','SJIS'); $mysql['username'] = mysqli_real_escape_string($mysql['username']); |
不正にエンコーディングされた文字の有無をチェックする。
1 2 3 | if(!mb_check_encoding($_POST['username'],'SJIS')){ echo '不正な文字が入っている可能性があります。'; } |
データベースの暗号化
SQLインジェクション対策のまとめ
- SQL用文字列を適切にフィルタする
- SQL用文字列を適切にエスケープする
- シフトJISの場合には1バイト文字を整理。UTF-8に変換してデータベースへ渡す
- SQLの記述をなくすためにO/R(Object/Relational)マッピングを活用する
- PHPやデータベースのエラーメッセージを公開しない
- バインド機構(バインドメカニズム)を利用する
- WAF(Web Application Firewall)Webアプリケーションファイアウォール Webアプリケーションのやり取りを管理し、不正浸入を防ぐファイヤーウォール
- IDS(Intrusion Detection System)浸入探知システム ネットワーク上の不正侵入を探知し、ネットワーク管理者に通報する
- IPS(Intrusion Prevention System)浸入防御システム サーバーやネットワークへの不正侵入を探知するだけでなく防ぐ
- アカウントは最小権限で運用する
- データベースのログを適切に取得する
- データベースの内容を暗号化する
タグ(=記事関連ワード)
日付
最終更新日:2017年08月09日