假设我有这样的代码:

$dbh = new PDO("blahblah");

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

PDO文档说:

预处理语句的参数不需要加引号;司机会帮你处理的。

这真的是避免SQL注入所需要做的一切吗?真的那么简单吗?

如果MySQL有影响,你可以假设它是MySQL。另外,我对使用预处理语句对抗SQL注入真的很好奇。在这种情况下,我不关心XSS或其他可能的漏洞。


当前回答

是的,这就足够了。注入型攻击的工作方式是通过某种方式让一个解释器(数据库)来计算一些东西,这些东西应该是数据,就像它是代码一样。这只有在你把代码和数据混合在同一种介质中(例如。当您将查询构造为字符串时)。

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

不过,您仍然容易受到其他注射类型的攻击。例如,如果在html页面中使用数据,则可能受到XSS类型攻击。

其他回答

是的,这就足够了。注入型攻击的工作方式是通过某种方式让一个解释器(数据库)来计算一些东西,这些东西应该是数据,就像它是代码一样。这只有在你把代码和数据混合在同一种介质中(例如。当您将查询构造为字符串时)。

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

不过,您仍然容易受到其他注射类型的攻击。例如,如果在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。

准备好的语句/参数化查询足以防止SQL注入,但只有在应用程序中的每个查询都一直使用时才可以。

如果在应用程序的其他地方使用未经检查的动态SQL,它仍然容易受到二阶注入的攻击。

二级注入意味着数据在被包含在查询中之前已经在数据库中循环了一次,并且很难实现。AFAIK,你几乎从未见过真正的工程二级攻击,因为攻击者通常更容易通过社会工程进入,但你有时会因为额外的良性“角色”或类似的东西而出现二级漏洞。

当您可以使一个值存储在数据库中,然后在查询中用作文字时,就可以实现二阶注入攻击。作为一个例子,假设你在一个网站上创建一个帐户时输入了以下信息作为你的新用户名(假设这个问题是MySQL DB):

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

If there are no other restrictions on the username, a prepared statement would still make sure that the above embedded query doesn't execute at the time of insert, and store the value correctly in the database. However, imagine that later the application retrieves your username from the database, and uses string concatenation to include that value a new query. You might get to see someone else's password. Since the first few names in users table tend to be admins, you may have also just given away the farm. (Also note: this is one more reason not to store passwords in plain text!)

We see, then, that if prepared statements are only used for a single query, but neglected for all other queries, this one query is not sufficient to protect against sql injection attacks throughout an entire application, because they lack a mechanism to enforce all access to a database within an application uses safe code. However, used as part of good application design — which may include practices such as code review or static analysis, or use of an ORM, data layer, or service layer that limits dynamic sql — **prepared statements are the primary tool for solving the Sql Injection problem.** If you follow good application design principles, such that your data access is separated from the rest of your program, it becomes easy to enforce or audit that every query correctly uses parameterization. In this case, sql injection (both first and second order) is completely prevented.


* MySql/PHP(很久很久以前)在处理涉及宽字符的参数时是愚蠢的,在这里的另一个高投票的答案中,有一个罕见的情况,可以允许注入通过参数化查询。

然而,如果你要防止sql注入前端,使用html或js检查,你必须考虑前端检查是“可绕过的”。

你可以禁用js或者使用前端开发工具编辑模式(现在firefox或chrome都内置了)。

因此,为了防止SQL注入,在控制器内部清洗输入日期后端是正确的。

我想建议您使用filter_input()原生PHP函数来净化GET和INPUT值。

如果您想进一步提高安全性,对于合理的数据库查询,我建议您使用正则表达式来验证数据格式。 在这种情况下,Preg_match()将帮助您! 但要小心!正则表达式引擎不是那么轻。仅在必要时使用它,否则应用程序性能将下降。

安全是有成本的,但不要浪费你的业绩!

简单的例子:

如果你想再次检查从GET接收到的值是否是一个小于99的数字 如果(! preg_match (' / [0 - 9] {1, 2 }/')){...} 是更重的

if (isset($value) && intval($value)) <99) {...}

所以,最终的答案是:“不!PDO Prepared Statements不阻止所有类型的sql注入”;它不防止意外的值,只是防止意外的连接

不,这是不够的(在某些特定的情况下)!默认情况下,当使用MySQL作为数据库驱动程序时,PDO使用模拟的预处理语句。在使用MySQL和PDO时,应该禁用模拟的预处理语句:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

另一件总是应该做的事情是设置正确的数据库编码:

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

另请参阅此相关问题:如何防止PHP中的SQL注入?

注意,这只会保护您不受SQL注入的攻击,但您的应用程序仍然容易受到其他类型的攻击。例如,你可以通过使用htmlspecialchars()再次使用正确的编码和引用样式来防止XSS。