PHPのSQL注入技術の実現及び予防措置

9149 ワード

SQL攻撃(SQL injection、台湾ではSQL資料隠し攻撃と呼ばれる)は、注入攻撃と略称され、アプリケーションのデータベース層で発生するセキュリティ・ホールである.簡単に言えば、入力された文字列にSQL命令を注入し、設計不良のプログラムでチェックを無視すると、これらの注入された命令はデータベースサーバが正常なSQL命令と勘違いして動作し、破壊される.
SQLインジェクション攻撃はMicrosoft SQLサーバのみを対象としていると考える人もいるが、SQL命令のバッチ処理をサポートするデータベースサーバであれば、このような手法の攻撃を受ける可能性がある.
1、原因
アプリケーションで次のような状況が発生した場合、アプリケーションはSQL Injectionのリスクの高い状況にさらされている可能性があります.
アプリケーションで文字列結合を使用してSQL命令を結合します.
アプリケーションがデータベースをリンクするときに権限が大きすぎるアカウントを使用します(たとえば、多くの開発者がsa(内蔵された最高権限のシステム管理者アカウント)でMicrosoft SQL Serverデータベースに接続するのが好きです).
MicrosoftSQL Serverデータベースのxp_cmdshellエクステンションプリザーバやOLE Automationプリザーバなど、不要だが権力が大きすぎる機能がデータベースにオープンしています.
ユーザが入力したデータを信頼しすぎ、入力した文字数を制限せず、ユーザが入力したデータに対して潜在的な命令をしていない検査.
2、作用原理
SQLコマンドは、クエリー、挿入、更新、削除など、コマンドの直列接続が可能です.セミコロン文字を異なるコマンドの違いとします.(本来の役割はSubQueryやクエリー、挿入、更新、削除…などとしての条件式)
SQLコマンドは、入力された文字列パラメータに対して単一引用符文字で囲まれています.「ただし、SQLデータベースでは、2つの一重引用符文字が連続している場合は、文字列の一重引用符文字とみなされます」
SQLコマンドでは、「連続する2つのマイナス記号--後の文字が注記、または「/*」と「*/」で囲まれた文字が注記」と入力できます.
したがって、SQLのコマンド文字列を結合する際に、一重引用符文字に対して置換処理を行わないと、その文字変数がコマンド文字列に埋め込まれたときに、本来のSQL構文の役割を悪意的に改竄されることになる.
SQL注入攻撃の主な原因は、次の2つです.
    1. phpプロファイルphp.iniのmagic_quotes_gpcオプションは開いておらずoffに設定されています.
    2. 開発者はデータ型のチェックとエスケープを行わなかった.
しかし、実際には、2つ目が最も重要です.
ユーザーが入力したデータ型をチェックし、MYSQLに正しいデータ型を提出するのは、webプログラマーの最も基本的な素質だと思います.しかし、現実には、小さな白のWeb開発者がそれを忘れ、裏口が大きく開いていることが多い.
なぜ2つ目が一番重要なのか.2つ目の保証がなければmagic_quotes_gpcオプションは、onでもoffでもSQL注入攻撃を引き起こす可能性があります.
次に、テクノロジーの実装について説明します.
一、magic_quotes_gpc=Off時の注入攻撃
      magic_quotes_gpc=Offはphpの中で非常に安全ではないオプションです.新しいphpでは、デフォルトの値をOnに変更しました.しかし、offというサーバの選択肢はまだかなりあります.結局、どんなにアンティークなサーバーでも誰かが使っています.magic_quotes_gpc=Onの場合、コミットされた変数のすべての'(一重引用符)、"(二重記号)、(反斜線)、空白文字が、前に自動的に加算されます.PHPの公式説明は次のとおりです.
magic_quotes_gpc boolean   Sets the magic_quotes state for GPC (Get/Post/Cookie) operations.  When magic_quotes are on, all ' (single-quote), "(double quote), (backslash)  and NUL's are escaped with a backslash automatically.
エスケープ、すなわちoffがなければ、攻撃者にチャンスを与える.次のテストスクリプトを例に挙げます.
<? if (isset($_POST["f_login"])) { //      ... // ...   ... //          $t_strUname = $_POST["f_uname"]; $t_strPwd = $_POST["f_pwd"]; $t_strSQL = "SELECT * FROM tbl_users WHERE username='$t_strUname' AND password = '$t_strPwd' LIMIT 0,1"; if ($t_hRes = mysql_query($t_strSQL)) { //          .  ... } } ?>
<html><head><title>test</title></head>
<body>
<form method="post" action="">
Username: <input type="text" name="f_uname" size=30><br>
Password: <input type=text name="f_pwd" size=30><br>
<input type="submit" name="f_login" value="  ">
</form>
</body>

このスクリプトでは、ユーザーが通常のユーザー名とパスワードを入力し、値がzhang 3、abc 123であると仮定すると、コミットされたSQL文は次のようになります.
SELECT * FROM tbl_users WHERE username='zhang3' AND password = 'abc123' LIMIT 0,1

攻撃者がusernameフィールドにzhang 3'OR 1=1を入力し、passwordにabc 123を入力すると、コミットされたSQL文は次のようになります.
SELECT * FROM tbl_users WHERE username='zhang3' OR 1=1 #' AND password = 'abc123' LIMIT 0,1

#はmysqlのコメントであるため、#の後の文は実行されず、この行の文を実装すると次のようになります.
SELECT * FROM tbl_users WHERE username='zhang3' OR 1=1

これで攻撃者は認証を迂回することができます.攻撃者がデータベース構造を知っている場合、UNION SELECTを構築すると、usernameに入力するとさらに危険になります.
zhang3 ' OR 1 =1 UNION select cola,  colb,cold FROM tbl_b #

passwordにabc 123と入力すると、コミットされたSQL文は次のようになります.
INSERT INTO tbl_user SET uid="1";
SELECT * FROM tbl_user WHERE uid="1";

これでかなり危険です.
二、magic_quotes_gpc=On時の注入攻撃
magic_quotes_gpc=Onの場合、攻撃者は文字型のフィールドにSQLを注入できません.これはこれが安全だという意味ではありません.この場合、SQL注入は数値型のフィールドで行えます.
最新版のMYSQL 5.xでは、データ型の入力が厳格になり、自動型変換がデフォルトでオフになっています.数値型のフィールドは、引用符でマークされた文字型ではありません.
すなわちuidが数値型であると仮定すると、以前のmysqlバージョンでは、このような文が合法的であった.
INSERT INTO tbl_user SET uid="1";
SELECT * FROM tbl_user WHERE uid="1";

最新のMYSQL 5.xでは、上記の文は合法的ではありません.このように書かなければなりません.
INSERT INTO tbl_user SET uid=1;
SELECT * FROM tbl_user WHERE uid=1;

これは正しいと思います.開発者として、ルールに合った正しいデータ型をデータベースに提出することが基本です
では攻撃者はmagic_quotes_gpc=Onの時、彼らはどのように攻撃しますか?簡単ですが、数値型のフィールドにSQLを注入します.次のphpスクリプトを例に挙げます.
<? if (isset($_POST["f_login"])) { //      ... // ...   ... //          $t_strUid = $_POST["f_uid"]; $t_strPwd = $_POST["f_pwd"]; $t    _strSQL = "SELECT * FROM tbl_users WHERE uid=$t_strUid AND password = '$t_strPwd' LIMIT 0,1"; if ($t_hRes = mysql_query($t_strSQL)) { //          .  ... } } ?>
<html><head><title>test</title></head>
<body>
<form method="post" action="">
User ID: <input type="text" name="f_uid" size=30><br>
Password: <input type=text name="f_pwd" size=30><br>
<input type="submit" name="f_login" value="  ">
</form>
</body>
</html>

上記のスクリプトでは、ユーザーにuseridとpasswordのログインを入力する必要があります.通常の文で、ユーザーは1001とabc 123を入力し、コミットされたsql文は以下の通りです.
SELECT * FROM tbl_users WHERE userid=1001 AND password = 'abc123' LIMIT 0,1

攻撃者がuseridで1001 OR 1=1#と入力した場合、注入されたsql文は次のようになります.
SELECT * FROM tbl_users WHERE userid=1001 OR 1 =1 # AND password = 'abc123' LIMIT 0,1

攻撃者は目的を達成した.
三、PHPのSQL注入攻撃を防止する方法
php sql注入攻撃を防ぐにはどうすればいいですか?最も重要なのは、データ型のチェックとエスケープだと思います.まとめたいくつかのルールは以下の通りです.
    1. php.iniのdisplay_errorsオプション、display_に設定する必要がありますerrors = off.このようにphpスクリプトがエラーになった後、攻撃者に犯行情報を分析させないように、webページにエラーが出力されません.
    2. mysqlを呼び出すqueryなどmysql関数の場合、前に@を付けるべきです.すなわち@mysql_query(...),これでmysqlエラーは出力されません.同じ理屈で攻撃者に役に立つ情報を分析させないようにする.
また、開発中のプログラマーの中にはmysql_queryエラーの場合、エラーおよびsql文の出力に慣れます.たとえば、次のようにします.
<php
$t_strSQL = "SELECT a from b....";
if (mysql_query($t_strSQL)) {
//      
} else {
echo "  ! SQL   :$t_strSQL     " . mysql_query();
exit;
}
?>

このやり方はかなり危険で愚かだ.そうする必要がある場合は、Webサイトのプロファイルで、グローバル変数を設定するか、マクロを定義してdebugフラグを設定したほうがいいです.
<?php
//       :
define("DEBUG_MODE", 0);    // 1: DEBUG MODE; 0: RELEASE MODE
//     :
$t_strSQL = "SELECT a from b....";
if (mysql_query($t_strSQL)) {
//      
} else {
if (DEBUG_MODE) {
echo "  ! SQL   :$t_strSQL    " . mysql_query();
}
exit;
}
?>

    3. コミットされたsql文に対して、エスケープとタイプチェックを行います.
四、安全パラメータ取得関数を書く
ユーザーのエラーデータとphp+mysql注入を防ぐために、関数PAPI_を書きました.GetSafeParam()は、安全なパラメータ値を取得するために使用されます.
<?php
define("XH_PARAM_INT", 0);
define("XH_PARAM_TXT", 1);
function PAPI_GetSafeParam($pi_strName, $pi_Def = "", $pi_iType = XH_PARAM_TXT) {
if (isset($_GET[$pi_strName])) {
$t_Val = trim($_GET[$pi_strName]);
} else if (isset($_POST[$pi_strName])) {
$t_Val = trim($_POST[$pi_strName]);
} else {
return $pi_Def;
}
// INT
if (XH_PARAM_INT == $pi_iType) {
if (is_numeric($t_Val)) {
return $t_Val;
} else {
return $pi_Def;
}
}
// String
$t_Val = str_replace("&", "&", $t_Val);
$t_Val = str_replace("<", "<", $t_Val);
$t_Val = str_replace(">", ">", $t_Val);
if (get_magic_quotes_gpc()) {
$t_Val = str_replace(""", """, $t_Val); $t_Val = str_replace("''", "'", $t_Val); } else { $t_Val = str_replace(""", """, $t_Val); $t_Val = str_replace("'", "'", $t_Val); } return $t_Val; } ?>

この関数には、次の3つのパラメータがあります.
$pi_strName:変数名
$pi_Def:デフォルト
$pi_iType:データ型.値をXH_とするPARAM_INT,XH_PARAM_TXTは、それぞれ数値型とテキスト型を表す.
リクエストが数値型の場合、is_を呼び出すnumeric()は、数値であるか否かを判断する.そうでない場合は、プログラムが指定したデフォルト値を返します.
簡単に言えば、テキスト列について、ユーザーが入力したすべての危険文字(HTMLコードを含む)をすべてエスケープします.
php関数addslashes()に脆弱性があるためstr_replace()を直接置き換えます.get_magic_quotes_gpc()関数はphpの関数でありmagic_を判断するために用いられるquotes_gpcオプションが開いているかどうか.
先ほどのセクション2の例では、コードを呼び出すことができます.
<?php
 if (isset($_POST["f_login"])) {
 //      ...
 // ...   ...
 //         
 $t_strUid = PAPI_GetSafeParam("f_uid", 0, XH_PARAM_INT);
 $t_strPwd = PAPI_GetSafeParam("f_pwd", "", XH_PARAM_TXT);
 $t_strSQL = "SELECT * FROM tbl_users WHERE uid=$t_strUid AND password = '$t_strPwd' LIMIT 0,1";
 if ($t_hRes = mysql_query($t_strSQL)) {
 //          .  ...
 }
 }
 ?>

これなら、かなり安全です.