因为用户可以输入相似之处value'); DROP TABLE table;--查询的内容变为:& B" ?0 s3 |% f) |: j! u
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') 9 z% a) H3 J( x6 `3 x# D) N0 ~" T6 g
能做些什么来防止这种情况发生?2 d! [" j! ]$ a; E7 B0 T
& J. i# B# I4 q: X' i6 Z 解决方案: ! h9 m, U" ]6 n% D! u 无论使用哪个数据库,都要避免 SQL 注入攻击正确方法是将数据与 SQL 分离,保持数据,永远不会被 SQL 解析器解释命令。可以使用正确格式的数据部分创建 SQL 句子,但如果你不是完全了解细节,要始终如一使用准备好的句子和参数查询。这些都是与任何参数分开发送到数据库服务器并由其分析的 SQL 语句。这样,攻击者就不可能注入恶意 SQL。/ O2 l4 r, S- c2 r' K7 ~
实现这一点基本上有两种选择:0 Z/ e/ }* h( G. r, g0 Z$ @2 w2 w
[ol]使用PDO(任何支持的数据库驱动程序):[/ol]```php ' H; e5 K" D" W8 D; v $stmt = $pdo->prepare(‘SELECT * FROM employees WHERE name = :name’); 9 m& R! t. {' L$stmt->execute([ 'name' => $name ]);foreach ($stmt as $row) / Do something with $row}``` 4 x3 \2 G& D* y/ S {[ol]使用MySQLi(用于 MySQL):[/ol]```php 8 U4 d" A. t8 A2 o $stmt = $dbConnection->prepare(‘SELECT * FROM employees WHERE name = ?’);0 k; Z( M+ G$ e# U9 V, M; Z3 }
$stmt->bind_param(‘s’,$name); // ‘s’ specifies the variable type => ‘string’/ P, |5 t5 F4 x# b, ?$ G) a# H
$stmt->execute();$result = $stmt->get_result();while ($row = $result->fetch_assoc()Do something with $row}``` 0 K' Y: |0 ]# o2 L$ z B若要连接 MySQL 以外的数据库可参考驱动程序的第二个选项(例如,pg_prepare()对于pg_execute()PostgreSQL)。PDO 是通用选项。8 F8 R: g% o/ _! J j7 _
连接设置正确使用时请注意PDO*访问 MySQL 数据库时**,默认情况下不使用实际准备好的句子。要解决这个问题,你必须禁止模拟准备好的句子。使用PDO*创建连接的例子是:$ B O5 s; G, d
$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8','user','password');$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);$dbConnection->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);/ W) @3 U/ H! P2 w2 u' H6 h
在上面的示例中,错误模式不是绝对必要的,但建议添加。这样脚本就不会了Fatal Error当出现问题时停止。它给了开发人员处理问题的机会。n as s 的catch任何错误。throw``PDOException m2 d7 y- e8 M
然而,第一行是强制性setAttribute(),它告诉 PDO 禁用模拟预准备句,使用模拟预准备句真正的准备句子。这确保了句子和值发送到 MySQL 服务器以前不会被 PHP 分析(使可能的攻击者没有机会注入恶意 SQL)。+ J ]# M4 |7 r$ a- B K6 I+ N
尽管您可以charset 设置在构造函数的选项中,但重要的是版 很重要PHP(5.3.在6 之前)默默忽略了 DSN 中的 charset 参数。 - ?/ s; g. h/ n, |2 i' ?解释你传递给你SQL 语句prepare由数据库服务器进行分析和编译。指定参数(如上例中的参数或命名参数:name),您可以告诉数据库引擎您要过滤的位置。然后,当您呼叫 时execute,准备好的句子与您指定的参数值相结合。 , r. d8 I8 m; u' U$ \, a参数值与编译句子相结合,而不是 SQL 字符串。SQL 注入的工作原理是创建要发送到数据库的 SQL 诱导脚本包含恶意字符串。因此,通过实际 SQL 单独发送参数,你可以限制结束你不想要的风险。 ! s$ c8 z7 B7 I8 A1 w/ D0 f您在使用准备好的句子时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,但参数最终可能以数字形式结束)。述示例中,如果$name变量包含'Sarah'; DELETE FROM employees结果只是对 string 的搜索"'Sarah'; DELETE FROM employees",你最终不会得到一个空 table。 . _. W% a1 n0 Z7 w+ A9 V使用准备句子的另一个好处是,如果你在同一个会话中多次执行相同的句子,它只会被分析和编译一次,从而给你带来一些速度提高。5 u1 I) J2 O/ _4 \) s" }% P9 y
哦,既然你问过如何插入,这里有一个例子(使用 PDO):/ r) C6 f. Z# {. {! q: |* ^
& J- G5 u3 P& a- Q4 o4 e
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');$preparedStatement->execute([ 'column' => $unsafeValue);(//);code]准备好的句子能用于动态查询吗?虽然您仍然可以使用准备好的语句来查询参数,但动态查询本身的结构不能参数化,某些查询功能也不能参数化。+ f) ?# D Y, |" g" u
对于这些特定场景,最好的办法是使用限制可能值的白名单过滤器。[code]// Value whitelist// $dir can only be 'DESC',otherwise it will be 'ASC'if (empty($dir) || $dir !== 'DESC { $dir = 'ASC';} 4 E0 Q4 n; n4 Z0 B; F4 |0 G