如果将用户输入未经修改地插入到SQL查询中,则应用程序容易受到SQL注入的攻击,如以下示例所示:
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
这是因为用户可以输入类似的内容value’); DROP TABLE table;–,并且查询变为:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
可以采取什么措施来防止这种情况的发生?
Use prepared statements and parameterized queries. These are SQL statements that are sent to and parsed by the database server separately from any parameters. This way it is impossible for an attacker to inject malicious SQL.
您基本上有两种选择可以实现此目的:
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // Do something with $row }
如果你连接到MySQL之外的数据库,有一个特定的驱动程序,第二个选项,你可以参考一下(例如,pg_prepare()和pg_execute()PostgreSQL的)。PDO是通用选项。
pg_prepare()
pg_execute()
注意,当PDO用于访问MySQL数据库时 ,默认情况下不使用* 真实的 预处理语句。要解决此问题,您必须禁用对准备好的语句的仿真。使用PDO创建连接的示例如下: *
PDO
$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);
在上面的示例中,错误模式不是严格必需的, 但建议添加它 。这样,Fatal Error当出现问题时,脚本不会以a停止。并且它为开发人员提供了解决catch任何thrown为PDOExceptions的错误的机会。
Fatal Error
catch
throw
PDOException
但是,第一行是 强制性的 ,setAttribute()它告诉PDO禁用模拟的预备语句并使用 实际的 预备语句。这样可以确保在将语句和值发送到MySQL服务器之前,不会对PHP进行解析(这样可能会使攻击者没有机会注入恶意SQL)。
setAttribute()
尽管可以charset在构造函数的选项中设置,但必须注意,PHP的“较旧”版本(5.3.6之前的版本)静默忽略了DSN中的charset参数。
charset
传递给您的SQL语句prepare由数据库服务器解析和编译。通过指定参数(如上例中的?参数或命名参数:name),您可以告诉数据库引擎要在何处进行过滤。然后,当您调用时execute,准备好的语句将与您指定的参数值组合在一起。
prepare
?
:name
execute
这里重要的是参数值与已编译的语句组合,而不是与SQL字符串组合。SQL注入通过在创建要发送到数据库的SQL时欺骗脚本使其包含恶意字符串来起作用。因此,通过将实际的SQL与参数分开发送,可以降低因意外获得最终结果的风险。
使用预处理语句发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能以数字结尾)。在上面的示例中,如果$name变量包含'Sarah'; DELETE FROM employees结果,则仅是搜索字符串"'Sarah'; DELETE FROMemployees",并且最终不会得到空表。
$name
'Sarah'; DELETE FROM employees
"'Sarah'; DELETE FROMemployees"
使用准备好的语句的另一个好处是,如果您在同一会话中多次执行同一条语句,则该语句仅被解析和编译一次,从而可以提高速度。
哦,既然您询问了如何进行插入,这是一个示例(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute([ 'column' => $unsafeValue ]);
尽管您仍可以对查询参数使用准备好的语句,但是无法对动态查询本身的结构进行参数化,并且无法对某些查询功能进行参数化。
对于这些特定方案,最好的办法是使用白名单过滤器来限制可能的值。
// Value whitelist // $dir can only be 'DESC', otherwise it will be 'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; }