1. 犀牛前端部落首页
  2. PHP百科

php防止SQL注入

如果不加修改就将用户输入插入到SQL查询中,那么应用程序很容易受到SQL注入的攻击

php防止SQL注入

前言

首先,我们来看一个例子

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

这是因为用户可以输入的值,可以变成一个drop table语句,例如:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

那么我们如何避免这种情况呢?

解决思路

使用准备好的语句和参数化查询。这些SQL语句分别由数据库服务器发送和解析,与任何参数无关。这样攻击者就不可能注入恶意SQL。

有两种办法可以实现:

PDO

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

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

$stmt->execute([ 'name' => $name ]);

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

mysqli

如果是MySQL数据库,可以使用mysqli:

$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数据库时,默认情况下不使用real prepared语句。要解决这个问题,必须禁用模拟准备好的语句。使用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);

在上面的例子中,错误模式并不是必须的,但是建议您添加它。这样,当出现错误时,脚本就不会因为致命错误而停止。并且它给开发人员机会来捕获任何作为pdoexception抛出的错误。

但是,必须执行的是第一个setAttribute()行,它告诉PDO禁用模拟的准备好的语句并使用真正的准备好的语句。这可以确保PHP在将语句和值发送到MySQL服务器之前不会对其进行解析(这使攻击者没有机会注入恶意SQL)。

虽然您可以在构造函数的选项中设置charset,但需要注意的是,PHP的“较老”版本(5.3.6之前)会忽略DSN中的charset参数

解释

传递给prepare的SQL语句由数据库服务器解析和编译。通过指定参数(a ?或者一个命名参数,如:name(在上面的例子中),告诉数据库引擎要过滤到哪里。然后,当您调用execute时,准备好的语句与您指定的参数值相结合。

这里重要的一点是,参数值与编译后的语句相结合,而不是与SQL字符串相结合。SQL注入的工作原理是在脚本创建要发送到数据库的SQL时欺骗脚本包含恶意字符串。因此,通过将实际的SQL与参数分开发送,您可以限制以您不希望的方式结束的风险。

使用预处理语句的另一个好处是,如果在同一个会话中多次执行相同的语句,那么它只会被解析和编译一次,从而提高了一些速度。

那么如何插入数据呢,看下面这个例子(使用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';
}

原创文章,作者:犀牛前端部落,如若转载,请注明出处:https://www.pipipi.net/3358.html

发表评论

登录后才能评论