1. PHP チュートリアル設定ファイル php.ini の magic_quotes_gpc オプションがオンになっておらず、オフに設定されています。 2. 開発者はデータ型をチェックしてエスケープしませんでした
しかし、実際には 2 番目の点が最も重要です。ユーザーが入力したデータ型をチェックし、正しいデータ型を mysql チュートリアルに送信することは、Web プログラマーの最も基本的な資質であるべきだと思います。しかし実際には、多くの初心者 Web 開発者はこれを忘れて、バックドアが大きく開いたままになっていることがよくあります。
なぜ 2 番目のポイントが最も重要なのでしょうか? 2 番目の保証がないと、magic_quotes_gpc オプションがオンかオフかに関係なく、SQL インジェクション攻撃を引き起こす可能性があるためです。技術的な実装を見てみましょう:
1. magic_quotes_gpc = offの場合のインジェクション攻撃
magic_quotes_gpc = off は、php の非常に安全でないオプションです。新しいバージョンの php では、デフォルト値が on に変更されました。しかし、オプションがオフになっているサーバーがまだかなりの数あります。結局のところ、サーバーがどんなに古いものであっても、まだそれを使用している人がいます。
magic_quotes_gpc = on の場合、送信された変数の前にあるすべての '(一重引用符)、"(二重記号)、(バックスラッシュ)、および空白文字が自動的に追加されます。以下は PHP 公式の説明です:
ソースプリントを表示しますか?
magic_quotes_gpc ブール値
gpc (get/post/cookie) 操作の magic_quotes 状態を設定します。magic_quotes がオンの場合、すべての ' (一重引用符)、" (二重引用符)、(バックスラッシュ)、および null はバックスラッシュで自動的にエスケープされます。
逃げ場がない場合、つまりオフの場合、攻撃者に付け入る機会を与えてしまいます。次のテスト スクリプトを例として取り上げます:
2if ( isset($_post["f_login"] ) )
3{
4 // データベースへの接続に関するチュートリアル...
5 // ...コードは省略されています...
6
7 // ユーザーが存在するかどうかを確認します
8 $t_struname = $_post["f_uname"];
9 $t_strpwd = $_post["f_pwd"];
10 $t_strsql = "select * from tbl_users where username='$t_struname'、password = '$t_strpwd' 制限 0,1";
11
12 if ( $t_hres = mysql_query($t_strsql) )
13
14 // クエリが成功した後の処理。少し...
15 }
16}
17?>
18
19
このスクリプトでは、ユーザーが通常のユーザー名とパスワードを入力すると、値がそれぞれ zhang3 と abc123 であると仮定すると、送信される SQL ステートメントは次のようになります。
tbl_users から * を 1 つ選択
2 ユーザー名 = 'zhang3' およびパスワード = 'abc123' 制限 0,1
攻撃者がユーザー名フィールドに zhang3' または 1=1 # を入力し、パスワード フィールドに abc123 を入力すると、送信される SQL ステートメントは次のようになります:
2 ここで、ユーザー名 = 'zhang3' または 1=1 #' およびパスワード = 'abc123' 制限 0,1
# は mysql のコメント文字であるため、# の後のステートメントは実行されません。この行のステートメントを実装するには、次のようになります。
tbl_users から * を 1 つ選択
これにより、攻撃者は認証をバイパスできます。攻撃者がデータベース構造を知っている場合、ユニオン選択を構築しますが、これはさらに危険です:
ユーザー名に zhang3 ' または 1 =1 Union select cola,colb,cold from tbl_b #
パスワードを入力してください: abc123,
送信された SQL ステートメントは次のようになります:
tbl_users から * を 1 つ選択
2 ユーザー名 = 'zhang3 '
3 または 1 =1 ユニオンは tbl_b #' から cola、colb、cold を選択し、パスワード = 'abc123' 制限 0,1
これはかなり危険です。 agic_quotes_gpc オプションがオンで引用符がエスケープされている場合、上記の攻撃者が作成した攻撃ステートメントは次のようになり、その目的を達成できません:
tbl_users から * を 1 つ選択
3 とパスワード = 'abc123'
4 リミット 0,1
5
6 tbl_users から * を選択します
7 where username='zhang3 ' または 1 =1 Union select cola、colb、cold from tbl_b #'
8 およびパスワード = 'abc123' 制限 0,1
2. magic_quotes_gpc = on の場合のインジェクション攻撃
magic_quotes_gpc = on の場合、攻撃者は文字フィールドに対して SQL インジェクションを実行できません。だからといって安全というわけではありません。現時点では、数値フィールドを通じて SQL インジェクションを実行できます。
最新バージョンの mysql 5.x では、データ型の入力が制限され、自動型変換がデフォルトでオフになっています。数値フィールドを引用符でマークされた文字タイプにすることはできません。言い換えれば、uid が数値であると仮定すると、以前の mysql バージョンでは、このようなステートメントは正当です:
1 tbl_user set uid="1" に挿入します;
2 select * from tbl_user where uid="1";
最新の mysql 5.x では、上記のステートメントは合法ではないため、次のように記述する必要があります:
1 tbl_user set uid=1 に挿入;
2 select * from tbl_user where uid=1;
これは正しいと思います。開発者として、ルールに準拠した正しいデータ型をデータベースに送信することが最も基本的な要件だからです。
では、magic_quotes_gpc = on の場合、攻撃者はどのように攻撃するのでしょうか?非常に簡単で、数値フィールドに対して SQL インジェクションを実行するだけです。次の php スクリプトを例として取り上げます:
1
2 if ( isset($_post["f_login"] ) )
3{
4 // データベースに接続します...
5 // ...コードは省略されています...
6
7 // ユーザーが存在するかどうかを確認します
8 $t_struid = $_post["f_uid"];
9 $t_strpwd = $_post["f_pwd"];
10 $t_strsql = "select * from tbl_users where uid=$t_struid、password = '$t_strpwd' 制限 0,1";
11 if ( $t_hres = mysql_query($t_strsql) )
12{
13 // クエリが成功した後の処理中です...
14 }
15
16}
17 ?>
18
上記のスクリプトでは、ユーザーはログインするためにユーザー ID とパスワードを入力する必要があります。通常のステートメントでは、ユーザーは 1001 と abc123 を入力し、送信された SQL ステートメントは次のとおりです:
select * from tbl_users where userid = 1001、password = 'abc123' 制限 0,1
攻撃者が userid に「1001 or 1 =1 #」と入力した場合、挿入される SQL ステートメントは次のようになります。
select * from tbl_users where userid=1001 or 1 =1 # andpassword = 'abc123' 制限 0,1
攻撃者は目標を達成しました。
3. PHP SQL インジェクション攻撃を防ぐ方法
PHP SQL インジェクション攻撃を防ぐには?最も重要な点は、データ型をチェックしてエスケープすることだと思います。いくつかのルールを要約すると次のとおりです:
php.iniのdisplay_errorsオプションはdisplay_errors = offに設定する必要があります。これにより、php スクリプトでエラーが発生した後は、Web ページにエラーが出力されなくなり、攻撃者による有用な情報の分析が防止されます。
mysql_query などの mysql 関数を呼び出すときは、mysql エラーが出力されないように、先頭に @ を追加する必要があります。つまり @mysql_query(...) です。攻撃者が有益な情報を分析できないようにする場合も同様です。さらに、一部のプログラマは、開発時に mysql_query エラーが発生したときにエラーと SQL ステートメントを出力することに慣れています。たとえば、次のようになります。 1 $t_strsql = "select a from b....";
2 if (mysql_query($t_strsql) )
3{
4 // 正しい取り扱い
5}
他6個
7{
8 echo "エラー! SQL ステートメント: $t_strsql rn エラー メッセージ".mysql_query();
9 出口;
10}
1 //全局配置文件中:
2 define("debug_mode",0); // 1: debug mode; 0: release mode
3
4 //调用脚本中:
5 $t_strsql = "select a from b....";
6 if ( mysql_query($t_strsql) )
7 {
8 // 正确的处理
9 }
10 else
11 {
12 if (debug_mode)
13 echo "错误! sql 语句:$t_strsql rn错误信息".mysql_query();
14 exit;
15 }
对提交的 sql 语句,进行转义和类型检查。
四. 我写的一个安全参数获取函数
为了防止用户的错误数据和 php + mysql 注入 ,我写了一个函数 papi_getsafeparam(),用来获取安全的参数值:
1 define("xh_param_int",0);
2 define("xh_param_txt",1);
3 function papi_getsafeparam($pi_strname, $pi_def = "", $pi_itype = xh_param_txt)
4 {
5 if ( isset($_get[$pi_strname]) )
6 $t_val = trim($_get[$pi_strname]);
7 else if ( isset($_post[$pi_strname]))
8 $t_val = trim($_post[$pi_strname]);
9 else
10 return $pi_def;
11
12 // int
13 if ( xh_param_int == $pi_itype)
14 {
15 if (is_numeric($t_val))
16 return $t_val;
17 else
18 return $pi_def;
19 }
20
21 // string
22 $t_val = str_replace("&", "&",$t_val);
23 $t_val = str_replace("<", "<",$t_val);
24 $t_val = str_replace(">", ">",$t_val);
25 if ( get_magic_quotes_gpc() )
26 {
27 $t_val = str_replace(""", """,$t_val);
28 $t_val = str_replace("''", "'",$t_val);
29 }
30 else
31 {
32 $t_val = str_replace(""", """,$t_val);
33 $t_val = str_replace("'", "'",$t_val);
34 }
35 return $t_val;
36 }
在这个函数中,有三个参数:
$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 选项是否打开。
刚才第二节的示例,代码可以这样调用:
1
2 if ( isset($_post["f_login"] ) )
3 {
4 // 连接数据库...
5 // ...代码略...
6
7 // 检查用户是否存在
8 $t_struid = papi_getsafeparam("f_uid", 0, xh_param_int);
9 $t_strpwd = papi_getsafeparam("f_pwd", "", xh_param_txt);
10 $t_strsql = "select * from tbl_users where uid=$t_struid and password = '$t_strpwd' limit 0,1";
11 if ( $t_hres = mysql_query($t_strsql) )
12 {
13 // 成功查询之后的处理. 略...
14 }
15 }
16 ?>