如何在PHP中阻止SQL注入?

2019-05-22 0 条评论 42 次阅读 2 人点赞

使用预准备语句和参数化查询。这些是由数据库服务器与任何参数分开发送和解析的SQL语句。这样攻击者就无法注入恶意SQL

你基本上有两个选择来实现这个目标:

使用PDO(适用于任何支持的数据库驱动程序):

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');

$stmt->execute(array('name' => $name));

foreach ($stmt as $row) {
    // do something with $row
}

使用MySQLi(用于MySQL):

$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是通用选项。

正确设置连接

请注意,在使用PDO访问MySQL数据库时,默认情况下不使用实际准备好的语句。要解决此问题,您必须禁用预准备语句的模拟。使用PDO创建连接的示例是:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

在上面的例子中,错误模式并不是绝对必要的,但建议添加它。这样,Fatal Error当出现问题时,脚本不会停止。并且它为开发人员提供了catch任何thrown为PDOExceptions的错误的机会。

什么是强制性的,但是,是第一setAttribute()线,它告诉PDO禁用模拟预处理语句和使用真正准备好的语句。这可以确保PHP在将语句和值发送到MySQL服务器之前不会解析它(使攻击者可能无法注入恶意SQL)。

虽然您可以charset在构造函数的选项中进行设置,但重要的是要注意PHP的旧版本(<5.3.6)默认忽略 DSN中的charset参数。

说明

会发生什么是您传递给的SQL语句prepare由数据库服务器解析和编译。通过指定参数(如上例中的a ?或命名参数:name),您可以告诉数据库引擎要过滤的位置。然后,当您调用时execute,预准备语句将与您指定的参数值组合。

这里重要的是参数值与编译语句结合,而不是SQL字符串。SQL注入通过在创建SQL以发送到数据库时欺骗脚本来包含恶意字符串。因此,通过从参数中单独发送实际SQL,可以限制最终出现您不想要的内容的风险。使用预准备语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能作为数字)。在上面的示例中,如果$name变量包含'Sarah'; DELETE FROM employees结果,那么只需要搜索字符串"'Sarah'; DELETE FROM employees",您就不会得到一个空表。

使用预准备语句的另一个好处是,如果在同一个会话中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升。

如果你问过如何为插入操作,这是一个例子(使用PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

准备好的语句可以用于动态查询吗?

虽然您仍然可以为查询参数使用预准备语句,但动态查询本身的结构无法进行参数化,并且某些查询功能无法进行参数化。

对于这些特定方案,最好的办法是使用限制可能值的白名单过滤器:

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

Kiwi

Valar Morghulis

文章评论(0)