如何在PHP中预防SQL注入?

21 浏览
0 Comments

如何在PHP中预防SQL注入?

如果将用户输入原封不动地插入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;--')

为了防止这种情况的发生,可以采取什么措施呢?

admin 更改状态以发布 2023年5月24日
0
0 Comments

为了使用参数化查询,您需要使用Mysqli或PDO之一。要使用mysqli重新编写示例,我们需要类似以下内容的东西。\n

prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();

\n您想要阅读的关键函数是mysqli :: prepare。\n另外,正如其他人建议的那样,您可能会发现使用类似PDO的抽象层更有用/更容易。\n请注意,您提出的情况相当简单,更复杂的情况可能需要更复杂的方法。特别是:\n如果要根据用户输入更改SQL的结构,则参数化查询无法帮助,所需的转义并不包含在mysql_real_escape_string中。在这种情况下,最好通过白名单传递用户的输入,以确保只允许“安全”值通过。

0
0 Comments

无论您使用哪种数据库,避免SQL注入攻击的正确方法是要将数据与SQL分离,这样数据就保持为数据并且永远不会被SQL解析器解释为命令。可以创建一个具有正确格式化数据部分的SQL语句,但如果您不完全了解细节,您应该始终使用预处理语句和参数化查询。这些是发送到数据库服务器并单独解析的SQL语句,而不涉及任何参数。这样攻击者就无法注入恶意SQL。

您基本上有两个选择:

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    $stmt->execute([ 'name' => $name ]);
    foreach ($stmt as $row) {
        // Do something with $row
    }
    

  2. 使用MySQLi(针对MySQL):

自PHP 8.2+以来,我们可以利用execute_query()方法在一个方法中准备、绑定参数和执行SQL语句:

$result = $dbConnection->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
while ($row = $result->fetch_assoc()) {
    // Do something with $row
}

PHP8.1及以下版本:

$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,则可以参考特定于驱动程序的第二个选项(例如,针对PostgreSQL的pg_prepare()pg_execute())。PDO是通用选项。


正确设置连接

PDO

请注意,使用PDO访问MySQL数据库时,默认情况下不使用真正的预处理语句。要修复这个问题,您需要禁用预处理语句的模拟。使用PDO创建连接的示例如下:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

在上面的例子中,错误模式并不是必需的,但是建议加上。这样PDO将通过抛出PDOException来通知您所有的MySQL错误。

然而,必需的是第一个setAttribute()行,它告诉PDO禁用模拟预处理语句并使用真正的预处理语句。这确保了语句和值在发送到MySQL服务器之前不会被PHP解析(从而使可能的攻击者无法注入恶意SQL)。

虽然你可以在构造函数的选项中设置charset,但需要注意的是,“旧版本”的PHP(5.3.6之前)会“悄悄地忽略DSN中的字符集参数”。

Mysqli

对于mysqli,我们必须遵循同样的常规:

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset


解释

你传递给prepare的SQL语句是由数据库服务器解析和编译的。通过指定参数(在上面的例子中是?或命名参数: 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([ 'column' => $unsafeValue ]);


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

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

对于这些特定情况,最好的做法是使用白名单过滤器来限制可能的值。

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

0