PDO准备好的语句是否足以防止SQL注入?

时间:2020-03-06 14:43:43  来源:igfitidea点击:

假设我有这样的代码:

$dbh = new PDO("blahblah");

$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

PDO文档说:

The parameters to prepared statements don't need to be quoted; the driver handles it for you.

那真的是我避免SQL注入所需要做的一切吗?真的那么容易吗?

我们可以假设MySQL会有所作为。而且,我真的只是对针对SQL注入使用准备好的语句感到好奇。在这种情况下,我不在乎XSS或者其他可能的漏洞。

解决方案

就个人而言,我总是会首先对数据进行某种形式的卫生处理,因为我们永远不会信任用户输入,但是当使用占位符/参数绑定时,输入的数据将分别发送到服务器的sql语句,然后绑定在一起。此处的关键是,这会将提供的数据绑定到特定的类型和特定的用途,并消除了更改SQL语句逻辑的任何机会。

准备好的语句/参数化查询通常足以防止对该语句*进行一阶注入。如果我们在应用程序中的其他任何地方使用未经检查的动态sql,则我们仍然容易受到二阶注入的攻击。

2阶注入意味着数据在包含在查询中之前已经在数据库中循环了一次,并且很难提取。 AFAIK,我们几乎永远不会看到真正的工程化二阶攻击,因为攻击者通常更容易进行社交工程,但是由于额外的良性字符或者类似字符,有时会出现二阶错误。

当我们可以使一个值存储在数据库中,该数据库以后用作查询中的文字时,就可以完成二阶注入攻击。举例来说,假设我们在网站上创建帐户时输入以下信息作为新的用户名(假设使用MySQL DB解决此问题):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

如果用户名没有其他限制,则一条准备好的语句仍将确保上述嵌入式查询在插入时不会执行,并将值正确存储在数据库中。但是,请想象一下,稍后应用程序将从数据库中检索用户名,并使用字符串串联将值包括在新查询中。我们可能会看到别人的密码。由于用户表中的前几个名称通常是管理员,因此我们可能也刚刚放弃了服务器场。 (还请注意:这是不将密码存储为纯文本的另一个原因!)

然后,我们看到准备好的语句足以用于单个查询,但是就其本身而言,它们不足以防止整个应用程序中的sql注入攻击,因为它们缺乏一种机制来强制应用程序中对数据库的所有访问都使用安全代码。但是,用作良好应用程序设计的一部分,其中可能包括诸如代码审查或者静态分析之类的实践,或者使用限制动态sql准备语句的ORM,数据层或者服务层,这是解决Sql Injection问题的主要工具。如果我们遵循良好的应用程序设计原则,从而使数据访问与程序的其余部分分开,则可以轻松实施或者审核每个查询正确使用参数化的过程。在这种情况下,完全防止了sql注入(一阶和二阶)。

*事实证明,当涉及到宽字符时,MySql / PHP只是(愚蠢的)对于处理参数是愚蠢的,在这里另一个极受好评的答案中仍然概述了一种罕见的情况,这种情况可以允许注入通过参数化来询问。

是的,足够了。注入式攻击的工作方式是通过某种方式让解释器(数据库)来评估应该是数据的某些事物,就好像它是代码一样。仅当我们在同一媒介中混合使用代码和数据时(例如,当我们将查询构造为字符串时),才有可能。

参数化查询通过分别发送代码和数据来工作,因此永远不可能在其中发现漏洞。

但是,我们仍然可能容易受到其他注入式攻击的攻击。例如,如果我们使用HTML页面中的数据,则可能会受到XSS类型的攻击。

不,他们并非总是如此。

这取决于我们是否允许将用户输入放置在查询本身中。例如:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

将容易受到SQL注入的攻击,并且在此示例中无法使用预处理语句,因为用户输入用作标识符而不是数据。正确的答案是使用某种过滤/验证,例如:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

注意:我们不能使用PDO绑定DDL(数据定义语言)之外的数据,即此方法不起作用:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

上面的方法不起作用的原因是因为" DESC"和" ASC"不是数据。 PDO只能转义数据。其次,我们甚至不能在其周围加上引号。允许用户选择排序的唯一方法是手动过滤并检查它是DESC还是ASC。