まえがき
データベースのパラメータ化されたクエリの手法がますます一般的になるにつれて、SQL インジェクションの脆弱性は以前に比べて大幅に減少しており、PDO は最も典型的な脆弱性です。 PHP: プリコンパイルされたクエリ方法は、ますます広く使用されるようになってきています。
ご存知のとおり、PDO は PHP で SQL インジェクションを防ぐ最良の方法ですが、SQL インジェクションを 100% 排除できるわけではありません。重要なのは PDO の使用方法によって異なります。
以前の記事で、PDOシナリオで制御可能なパラメータによってマルチセンテンスが実行されるなどの問題があることを知りました(https://xz.aliyun.com/t/3950)ので、 PDO シナリオでの SQL インジェクションについて説明しましたが、再度検討されました。
PDO クエリ ステートメントは、既存のセキュリティ問題を制御できます。
まず、ローカルに新しいライブラリとテーブルを作成し、何気なく何かを書きます。
次に、test.php を作成し、PDO を使用して簡単なクエリを実行します。
<?php try{ $db = new PDO('mysql:host=localhost;dbname=pdotest','root',''); } catch(Exception $e) { echo $e->getMessage(); }if(isset($_GET['id'])) { $id = $_GET['id']; }else{ $id=1; } $query = "select balabala from table1 where 1=?";echo "id:".$id."</br>"; $row = $db->prepare($query); $row->bindParam(1,$id); $row->execute(); $result = $row->fetch(PDO::FETCH_ASSOC);if($result) { echo "结果为:"; print_r($result); echo "</br>"; }
入力コンテンツとページ上で取得された結果を出力します。
#PDO には、セキュリティ問題に関連する次の 3 つの主な設定があります。PDO::ATTR_EMULATE_PREPARES PDO::ATTR_ERRMODE PDO::MYSQL_ATTR_MULTI_STATEMENTS
$query = "select balabala from table1 where 1={$id}"; $row = $db->query($query);
$query = "select balabala from table1 where 1=?"; $row = $db->prepare($query); $row->bindParam(1,$_GET[‘id’]); $row->execute();
#一重引用符がエスケープされていることがわかりました。このとき、gbk エンコーディングが設定されている場合に何が起こるかを考えずにはいられませんでした。 select * from table1 が正常に実行されたことがわかります。PDO は結果を返すだけですが、実際に実行されています。
つまり、クエリ ステートメントに制御可能なパラメータがなく、? や :id などのバインドされたパラメータのみがある場合でも、スタック インジェクションを実行できます。
複数文の実行をオフにするとどうなりますか?
PDO::MYSQL_ATTR_MULTI_STATEMENTS を false に設定し、上記の操作を繰り返しました。これが機能しなくなっていることがわかりました。
実際には、gbk を設定するステートメントだけが実行されました。
しかし、これで終わりでしょうか?
ユニオンインジェクションなどの他の方法を試してみてはいかがでしょうか?
#試してみたところ、ユニオンインジェクションもできることが分かりました!複数の文を実行する必要はまったくありません。实际上,在模拟预编译的情况下,PDO对于SQL注入的防范(PDO::queto()),无非就是将数字型的注入转变为字符型的注入,又用类似mysql_real_escape_string()的方法将单引号、双引号、反斜杠等字符进行了转义。
这种防范方法在GBK编码的情况下便可用宽字节进行绕过,而在非GBK编码的情况下,若存在二次注入的情况,是否能利用呢?
答案是否定的。
二次注入是由于对添加进数据库中的数据没有再次处理和转义而导致的,而预编译对每次查询都进行转义,则不存在二次注入的情况。
上述安全隐患,是由于未正确设置PDO造成的,在PDO的默认设置中,PDO::ATTR_EMULATE_PREPARES和PDO::MYSQL_ATTR_MULTI_STATEMENTS都是true,意味着模拟预编译和多句执行是默认开启的。
而在非模拟预编译的情况下,若语句中没有可控参数,是否还能这样做呢?
答案是否定的。
我们将PDO::ATTR_EMULATE_PREPARES设为false,来看看sql语句到底执行了什么:
它对每一句sql语句都进行了预编译和执行两个操作,在执行select balabala from table1 where 1=?这句时,如果是GBK编码,那么它将会把?绑定的参数转化成16进制,这样无论输入什么样的东西都无法再进行注入了。
如果不是GBK编码,如上面所说,也不存在二次注入的情况,故可以避免SQL注入漏洞。
相同原理的Prepare Statement方法
PDO的原理,与Mysql中prepare语句是一样的。上面PDO所执行的SQL语句,用如下的方式可以等效替代:
Set @x=0x31 Prepare a from “select balabala from table1 where 1=?” Execute a using @x
我们可以手动将输入的参数设置为@x,并将其转化为16进制,随后预编译,再执行
也就是说,不用PDO也可以仿照其原理手动设置预编译:
$db = new mysqli('localhost','root','','pdotest');if(isset($_GET['id'])) { $id = "0x".bin2hex($_GET['id']); }else{ $id=1; }echo "id:".$id."</br>"; $db->query("set names gbk"); $db->query("set @x={$id}"); $db->query("prepare a from 'select balabala from table1 where 1=?'"); $row = $db->query("execute a using @x"); $result = $row->fetch_assoc();if($result) { echo "结果为:"; print_r($result); echo "</br>"; }
得到的结果和使用PDO是一样的:
这样设置不用担心没有合理地设置PDO,或是用了GBK编码等情况。
Prepare Statement在SQL注入中的利用
Prepare语句在防范SQL注入方面起到了非常大的作用,但是对于SQL注入攻击却也提供了新的手段。
Prepare语句最大的特点就是它可以将16进制串转为语句字符串并执行。如果我们发现了一个存在堆叠注入的场景,但过滤非常严格,便可以使用prepare语句进行绕过。
例如我们将createtable table2 like table1转化成16进制,然后执行:
我们发现数据库中已经多了一个表table2。则语句成功执行了。
总结
对于此类问题的防范,主要有以下三个方面:
1. 合理、安全地使用gbk编码。即使采用PDO预编译的方式,如若配置不当,依然可造成宽字节注入
2. 使用PDO时,一定要将模拟预编译设为false
3. 可采用使用Prepare Statement手动预编译,杜绝SQL注入
相关文章教程推荐:网站安全教程
以上がPDOの原理と正しい使用方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。