PHPでウェブの世界と繋がろう!
menu
ホーム > PHPセキュリティー > 7 データベース > 7-1 データベースへの攻撃と防御策

7-1 データベースへの攻撃と防御策

Pocket

説明

データベースのセキュリティーの考え方
POINT

  • データベースはリモートソース
  • データベースとの送受信データを適切にフィルタ・エスケープする

PHPから見るとデータベースはリモートソースとなります。リモートソースを起源とするデータは入力と識別して適切にフィルタを行う必要があります。入力フィルタリングに関してはこちらを参照下さい。また、リモートソースへデータを出力する際も適切にデータをエスケープする必要があります。エスケープに関してはこちらを参照下さいデータベースから取得するデータをフィルタし、データベースへ送信するデータをエスケープすることで、データベースとPHPとの間で安全なやり取りが出来ます

データベースへのアクセス認証ファイル
POINT

  • データベースアクセス認証ファイルはドキュメントルート以外に設置
  • データベースアクセス認証ファイルは config.inc などのファイル名は避ける
  • データベースアクセス認証ファイルは .php にする

データベースへアクセスする認証情報が記載されたファイルは大切な機密情報です。データベースは機密性の高い情報を扱う場合が多いので、認証情報が記載されたファイルの扱いには特に注意が必要です。このような機密性の高いファイルは必ずドキュメントルート(webサーバ上に公開するためのルートディレクトリ)以外に設置しましょう。また、ファイル名やその拡張子を config.inc のような一般的に設定情報を扱うファイルとして認識されてしまうような名前は避けましょう。また、拡張子は .php として、PHPファイルとして扱うようにしましょう

データベースへの攻撃 SQLインジェクション(SQL Injection)
POINT

  • SQLインジェクションはデータベースを不正操作する攻撃

SQLインジェクションとは、アプリケーションのセキュリティ上の脆弱性を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースから不正に情報を引き出したり、データベースを不正に操作したりする攻撃方法のことです。SQLは、データベースを操作するために一般的に使用されている言語です。

アプリケーションに入力されるデータのフィルタリング(入力のフィルタ)とデータベースに送信されるデータのエスケープ(出力のエスケープ)が適切に行われていない場合に、SQLインジェクションを受けます。

SQLインジェクションの例

以下のコードはSQLインジェクションの可能性があります。

//パスワード
$hash = hash($_POST['password']);
//SQLクエリの作成
$sql = "select count(*) from users where username = '{$_POST['username']}' and password = '$hash'";
//SQL文実行
$result = mysql_query($sql);

このコードの問題点は $_POST[‘username’] をエスケープしていない箇所にあります。 $_POST[‘username’] に悪意のある値を渡すことで、アプリケーションが意図しないSQL文が実行されてしまう可能性があり、その場合、本来の機能を変えてしまうことができます。

例えば、$_POST[‘username’] の入力データが「yamada’ —」だった場合、

//パスワード
$hash = hash($_POST['password']);
//SQLクエリの作成
$sql = "select count(*) from users where username = 'yamada' --' and password = '$hash'";
//SQL文実行
$result = mysql_query($sql);

となります。SQL文ではハイフンが2つ続くとそこから先はSQLコメントとして扱われますので、以下と同じ意味になります。

select count(*) from users where username = 'yamada'

「–」以下の文がコメントとして扱われることになり and password = ‘$hash’ が無視されます。

このSQLが実行されるとパスワードを一切しらなくてもログインできることになってしまいます。

SQLインジェクションの例

以下のコードはSQLインジェクションの可能性があります。

//SQLクエリの作成
$sql = "select * from users where userid = '{$_POST['userid']}' and password = '$_POST['password']'";
//SQL文実行
$result = mysql_query($sql);

このコードの問題点は $_POST[‘userid’] $_POST[‘password’] をエスケープしていない箇所にあります。

入力値が以下の場合、

$_POST['userid'] が yamada
$_POST['password'] が ' OR 'A'='A

SQL文は以下のようになります。

//SQLクエリの作成
$sql = "select * from users where userid = 'yamada' and password = '' OR 'A'='A'";
//SQL文実行
$result = mysql_query($sql);

OR の後は常に真 ‘A=A’ なので、パスワードを知らなくても常に認証されてしまいます

セカンドオーダーSQLインジェクション
SQL用の文字列をエスケープしてデータベースに登録し、そのデータをデータベースから呼び出した場合は、エスケープされていない元のデータが呼び出されることになります。このデータをエスケープしないでSQL用の文字列に使用すると、SQLインジェクションが成立してしまいます。これをセカンドオーダーSQLインジェクションを言います。SQL文字列は常にエスケープしましょう。

SQLインジェクション(SQL Injection)の防御策
POINT

  • フィルタとエスケープを適切に行うことで攻撃を防げる
  • エスケープは mysql_real_escape_string()関数を使用
  • mysql_real_escape_string()関数を使用するには、MySQL接続が確立されていること
  • magic_quotes_gpc が有効な場合は、最初に stripslashes() を適用する

※PHP5から mysql_real_escape_string()の代替として、
mysqli_real_escape_string()
PDO::quote()
の使用が推奨されています。

SQLクエリを作るために使用されるデータを完全にフィルタし、フィルタされた汚染リスクのないデータを適切にエスケープすることで、SQLインジェクションのリスクはなくなります。

エスケープには、mysql_real_escape_string()関数が利用できます。この関数は、SQL文中で用いる文字列の特殊文字をエスケープします
以下の文字の前に「\(バックスラッシュ)」を付加します。

\x00 → \\x00
\n → \\n
\r → \\r
\ → \'
' → \'
" → \"
\x1a. → \\x1a.

mysql_real_escape_string()関数を使用するには、MySQL接続が確立されていなければなりません。また、magic_quotes_gpc が有効な場合は、最初に stripslashes() を適用する必要があります。そうしないと、エスケープされているデータを更にエスケープすることになります。mysql_real_escape_string() は % や _ をエスケープしません。 MySQL では、これらの文字を LIKE, GRANT, または REVOKE とともに用いることで、 ワイルドカードを表現します。

MySQLエスケープの例
//変数初期化
$clean = array();
//フィルタ
$clean['username'] = $_POST['username'];
//ハッシュ値へ
$hash = hash($_POST['password']);
//変数初期化
$mysql = array();
//エスケープ
$mysql['username'] = mysql_real_escape_string($clean['username']);
//SQLクエリ作成
$sql = "select count(*) from users where username = '{$mysql['username']}' and password = '$hash'";
//SQL文実行
$result = mysql_query($sql);
MySQLエスケープ定義関数の例
function MysqlEscape($index, $maxlength, $dbConnect){
	if (isset($index)){
		//substr文字列を切り取る 切り取る最初(0)と最後の位置を指定
		$input = substr($index, 0, $maxlength);
		//エスケープ
		$input = mysql_real_escape_string(get_magic_quotes_gpc() ? stripslashes($input) : $input, $dbConnect);
		return ($input);
	}
	return NULL;
}

PEAR::DBやPDO

PEAR::DBやPDOなどのバウンドパラメータやプレースホルダをサポートするデータベースライブラリを使えば、さらなるレイヤでSQLインジェクションを防ぐことが出来ます。

バインド変数の利用
POINT

  • PHPの拡張PDO(PHP Data Objects)でバインド変数の利用

バインド変数を使用することでSQLインジェクションを最適に防ぐことが出来ます。
バインド変数はPHPの拡張PDO(PHP Data Objects)で利用できるようになります。

$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インジェクション対策
POINT

  • マルチバイト文字の不正によるSQLインジェクションに注意
  • UTF-8にしてデータベースへ渡す

マルチバイト文字は2バイト文字などであるSHIFT-JISやEUC-JPなどです。SQLインジェクションを防止するためには「'(シングルクォート)」などの文字に対するエスケープ処理を行う必要があります。その際、日本語のようなマルチバイト文字でエスケープを行う場合、不正なマルチバイト文字に対して注意する必要があります

以下の例を考えてみましょう。

\x97' OR A=A → 予' OR A=A

「\x97' OR A=A」文字列をエスケープ処理した場合、「予' OR A=A」と変換されることがあります。これはSHIFT-JISで発生する現象です。

\x97' → \x97\'

「\x97'」の部分の文字列の「'」をエスケープ処理すると「\'」となり、「\x97\'」となります。
「\'」の部分もエンコードすると、「\x5C\x27」となり、全体では「\x97\x5C\x27」となります。

\x97\x5C\x27 → 予'

これを文字に戻すと、手前から「\x97\x5C」が2バイト文字として扱われて「予」となり、「\x27」が1バイト文字として扱われてしまい「'」となってしまいます。

予' OR A=A

結果として、「'」を「\'」とエスケープしたはずなのに、「'」が残ってしまうという現象が発生します。

SHIFT-JIS文字列のままデータベースに渡すことは避けましょう。
mb_convert_encodingなど使用してUTF-8などの文字列に変換することでリスクを軽減できます。

$mysql['username'] = mb_convert_encoding($_POST['username'],'UTF-8','SJIS');
$mysql['username'] = mysql_real_escape_string($mysql['username']);

不正にエンコーディングされた文字の有無をチェックする。

if(!mb_check_encoding($_POST['username'],'SJIS')){
	echo '不正な文字が入っている可能性があります。';
}

データベースの暗号化
万が一データベースへ不正浸入された場合を考慮し、データ流出被害を最小限にするために機密性の高いデータは暗号化しましょう。また復号化の鍵を安全に保つ必要があります。暗号化しても秘密鍵が流出してしまえば暗号化の意味がなくなりまs。暗号のためのPHP標準拡張機能は mcrypt です。

SQLインジェクション対策のまとめ
POINT Webアプリケーションの対策

  • SQL用文字列を適切にフィルタする
  • SQL用文字列を適切にエスケープする
  • シフトJISの場合には1バイト文字を整理。UTF-8に変換してデータベースへ渡す
  • SQLの記述をなくすためにO/R(Object/Relational)マッピングを活用する
  • PHPやデータベースのエラーメッセージを公開しない
  • バインド機構(バインドメカニズム)を利用する

 

POINT Webアプリケーション周りの対策

  • WAF(Web Application Firewall)Webアプリケーションファイアウォール Webアプリケーションのやり取りを管理し、不正浸入を防ぐファイヤーウォール
  • IDS(Intrusion Detection System)浸入探知システム ネットワーク上の不正侵入を探知し、ネットワーク管理者に通報する
  • IPS(Intrusion Prevention System)浸入防御システム サーバーやネットワークへの不正侵入を探知するだけでなく防ぐ

 

POINT データベースの対策

  • アカウントは最小権限で運用する
  • データベースのログを適切に取得する
  • データベースの内容を暗号化する

タグ(=記事関連ワード)

日付

投稿日:2012年7月21日
最終更新日:2012年08月29日

関連記事

このカテゴリの他のページ

この記事へのコメント

トラックバックurl

http://www.tryphp.net/phpsecurity-sql/trackback/