假设我有这样的代码:

$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或其他可能的漏洞。


当前回答

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

其他回答

然而,如果你要防止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注入”;它不防止意外的值,只是防止意外的连接

简短的回答是肯定的,如果使用得当,PDO准备是足够安全的。


我用这个答案来讨论PDO…

要回答这个问题并不容易。它是基于这里演示的攻击。

这次袭击

所以,让我们从展示攻击开始…

$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));

在某些情况下,它会返回多于一行。让我们来分析一下这里发生了什么:

Selecting a Character Set $pdo->query('SET NAMES gbk'); For this attack to work, we need the encoding that the server's expecting on the connection both to encode ' as in ASCII i.e. 0x27 and to have some character whose final byte is an ASCII \ i.e. 0x5c. As it turns out, there are 5 such encodings supported in MySQL 5.6 by default: big5, cp932, gb2312, gbk and sjis. We'll select gbk here. Now, it's very important to note the use of SET NAMES here. This sets the character set ON THE SERVER. There is another way of doing it, but we'll get there soon enough. The Payload The payload we're going to use for this injection starts with the byte sequence 0xbf27. In gbk, that's an invalid multibyte character; in latin1, it's the string ¿'. Note that in latin1 and gbk, 0x27 on its own is a literal ' character. We have chosen this payload because, if we called addslashes() on it, we'd insert an ASCII \ i.e. 0x5c, before the ' character. So we'd wind up with 0xbf5c27, which in gbk is a two character sequence: 0xbf5c followed by 0x27. Or in other words, a valid character followed by an unescaped '. But we're not using addslashes(). So on to the next step... $stmt->execute() The important thing to realize here is that PDO by default does NOT do true prepared statements. It emulates them (for MySQL). Therefore, PDO internally builds the query string, calling mysql_real_escape_string() (the MySQL C API function) on each bound string value. The C API call to mysql_real_escape_string() differs from addslashes() in that it knows the connection character set. So it can perform the escaping properly for the character set that the server is expecting. However, up to this point, the client thinks that we're still using latin1 for the connection, because we never told it otherwise. We did tell the server we're using gbk, but the client still thinks it's latin1. Therefore the call to mysql_real_escape_string() inserts the backslash, and we have a free hanging ' character in our "escaped" content! In fact, if we were to look at $var in the gbk character set, we'd see: 縗' OR 1=1 /* Which is exactly what the attack requires. The Query This part is just a formality, but here's the rendered query: SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

恭喜你,你刚刚成功地攻击了一个使用PDO准备语句的程序…

简单的解决方法

现在,值得注意的是,你可以通过禁用模拟预处理语句来防止这种情况:

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

这通常会导致一个真正的预处理语句(即数据从查询中以单独的数据包发送)。但是,请注意PDO将会无声地退回到模拟MySQL不能在本地准备的语句:手册中列出了那些它可以准备的语句,但要注意选择适当的服务器版本)。

正确的解决方法

这里的问题是我们使用了SET NAMES而不是C API的mysql_set_charset()。否则,攻击不会成功。但最糟糕的是,PDO直到5.3.6才公开mysql_set_charset()的C API,所以在以前的版本中,它不能防止每个可能的命令都受到这种攻击! 它现在被暴露为一个DSN参数,应该使用它而不是SET NAMES…

前提是我们使用的是2006年以后的MySQL版本。如果您使用的是较早的MySQL版本,那么mysql_real_escape_string()中的一个错误意味着无效的多字节字符(例如我们的有效负载中的那些字符)被当作单字节进行转义,即使客户端已经正确地通知了连接编码,因此这种攻击仍然会成功。该错误已在MySQL 4.1.20, 5.0.22和5.1.11中修复。

可取之处

正如我们在开始时所说,要使这种攻击起作用,必须使用易受攻击的字符集对数据库连接进行编码。utf8mb4不容易受到攻击,但可以支持所有Unicode字符:所以你可以选择使用它,但它只在MySQL 5.5.3之后可用。另一种替代方法是utf8,它也不容易受到攻击,并且可以支持整个Unicode基本多语言平面。

Alternatively, you can enable the NO_BACKSLASH_ESCAPES SQL mode, which (amongst other things) alters the operation of mysql_real_escape_string(). With this mode enabled, 0x27 will be replaced with 0x2727 rather than 0x5c27 and thus the escaping process cannot create valid characters in any of the vulnerable encodings where they did not exist previously (i.e. 0xbf27 is still 0xbf27 etc.)—so the server will still reject the string as invalid. However, see @eggyal's answer for a different vulnerability that can arise from using this SQL mode (albeit not with PDO).

安全的例子

下面的例子是安全的:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

因为服务器期望utf8…

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

因为我们已经正确地设置了字符集,所以客户端和服务器匹配。

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

因为我们已经关闭了模拟的准备语句。

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

因为我们已经正确地设置了字符集。

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

因为MySQLi一直在执行真准备语句。

结束

如果你:

使用现代版本的MySQL(5.1后期,所有5.5,5.6等)和PDO的DSN字符集参数(在PHP≥5.3.6)

OR

不要使用易受攻击的字符集进行连接编码(你只使用utf8 / latin1 / ascii / etc)

OR

开启NO_BACKSLASH_ESCAPES SQL模式

你100%安全。

否则,即使您使用PDO预处理语句,您也很容易受到攻击…

齿顶高

我一直在慢慢地编写一个补丁,将默认值更改为不模拟,为PHP的未来版本做准备。我遇到的问题是,当我这样做时,很多测试都中断了。一个问题是模拟的准备只会在执行时抛出语法错误,但真正的准备会在准备时抛出错误。所以这可能会导致问题(也是测试令人厌烦的部分原因)。

就我个人而言,我总是首先在数据上运行某种形式的卫生,因为你永远不能相信用户输入,然而,当使用占位符/参数绑定时,输入的数据被单独发送到服务器的sql语句,然后绑定在一起。这里的关键是,这将提供的数据绑定到特定类型和特定用途,并消除了更改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。

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

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

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