$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");6 C3 L3 |' q: }* q- |4 `& o& w$ E
因为用户可以输入相似之处value'); DROP TABLE table;--查询的内容变为: 0 K7 m" u0 \" G* T
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') 6 ]7 X/ [2 u9 C h* o' ^; t
能做些什么来防止这种情况发生? ! M/ C; \; L' }& H( c% c ; n0 S7 ?+ K: D0 O; h解决方案: : f% P7 u8 c/ b6 X3 q1 B, w( T 无论使用哪个数据库,都要避免 SQL 注入攻击正确方法是将数据与 SQL 分离,保持数据,永远不会被 SQL 解析器解释命令。可以使用正确格式的数据部分创建 SQL 句子,但如果你不是完全了解细节,要始终如一使用准备好的句子和参数查询。这些都是与任何参数分开发送到数据库服务器并由其分析的 SQL 语句。这样,攻击者就不可能注入恶意 SQL。( N9 S% e$ r) w6 y# \) l2 d
实现这一点基本上有两种选择:8 o9 A! t( k0 z7 r; G% P6 B
[ol]使用PDO(任何支持的数据库驱动程序):[/ol]```php - R/ E! p. V; N& Y7 Y0 q' [3 V $stmt = $pdo->prepare(‘SELECT * FROM employees WHERE name = :name’);! Q' Z9 |+ H: O9 m# \
$stmt->execute([ 'name' => $name ]);foreach ($stmt as $row) / Do something with $row}```" I$ X4 @' S ^2 S1 }
[ol]使用MySQLi(用于 MySQL):[/ol]```php ( Q0 D+ O; J6 n $stmt = $dbConnection->prepare(‘SELECT * FROM employees WHERE name = ?’);& n+ J5 f. [0 h4 U
$stmt->bind_param(‘s’,$name); // ‘s’ specifies the variable type => ‘string’ ! m& t9 F+ o$ b/ R$stmt->execute();$result = $stmt->get_result();while ($row = $result->fetch_assoc()Do something with $row}``` ) s6 H( ^6 S- h- }; t9 V若要连接 MySQL 以外的数据库可参考驱动程序的第二个选项(例如,pg_prepare()对于pg_execute()PostgreSQL)。PDO 是通用选项。9 h9 \: I' ~1 l! B! ^
连接设置正确使用时请注意PDO*访问 MySQL 数据库时**,默认情况下不使用实际准备好的句子。要解决这个问题,你必须禁止模拟准备好的句子。使用PDO*创建连接的例子是: 5 M6 R5 O7 y# w
$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);0 l! T" u A/ d; \; c# V
在上面的示例中,错误模式不是绝对必要的,但建议添加。这样脚本就不会了Fatal Error当出现问题时停止。它给了开发人员处理问题的机会。n as s 的catch任何错误。throw``PDOException% X7 v [1 C% _6 h6 I
然而,第一行是强制性setAttribute(),它告诉 PDO 禁用模拟预准备句,使用模拟预准备句真正的准备句子。这确保了句子和值发送到 MySQL 服务器以前不会被 PHP 分析(使可能的攻击者没有机会注入恶意 SQL)。$ `( r5 W7 { e3 L3 a
尽管您可以charset 设置在构造函数的选项中,但重要的是版 很重要PHP(5.3.在6 之前)默默忽略了 DSN 中的 charset 参数。 ! ~& w* t b5 L% f8 h) W6 M# L5 s解释你传递给你SQL 语句prepare由数据库服务器进行分析和编译。指定参数(如上例中的参数或命名参数:name),您可以告诉数据库引擎您要过滤的位置。然后,当您呼叫 时execute,准备好的句子与您指定的参数值相结合。 % V" U4 R; F4 e5 t& ?参数值与编译句子相结合,而不是 SQL 字符串。SQL 注入的工作原理是创建要发送到数据库的 SQL 诱导脚本包含恶意字符串。因此,通过实际 SQL 单独发送参数,你可以限制结束你不想要的风险。 2 R- O6 A; C' u您在使用准备好的句子时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,但参数最终可能以数字形式结束)。述示例中,如果$name变量包含'Sarah'; DELETE FROM employees结果只是对 string 的搜索"'Sarah'; DELETE FROM employees",你最终不会得到一个空 table。$ B! o6 o7 S2 L+ p: t1 O
使用准备句子的另一个好处是,如果你在同一个会话中多次执行相同的句子,它只会被分析和编译一次,从而给你带来一些速度提高。1 H9 R9 e! \5 e1 k) E+ T9 {
哦,既然你问过如何插入,这里有一个例子(使用 PDO): , N" [# _3 |0 t' ^( r
$ T4 r5 ^ R! {1 Y& l- O
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');$preparedStatement->execute([ 'column' => $unsafeValue);(//);code]准备好的句子能用于动态查询吗?虽然您仍然可以使用准备好的语句来查询参数,但动态查询本身的结构不能参数化,某些查询功能也不能参数化。 4 b8 _! |; f# `8 g( x6 m
对于这些特定场景,最好的办法是使用限制可能值的白名单过滤器。[code]// Value whitelist// $dir can only be 'DESC',otherwise it will be 'ASC'if (empty($dir) || $dir !== 'DESC { $dir = 'ASC';}: N7 ?. F# Q0 e