假设我有这样的代码:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
PDO文档说:
准备好的语句的参数不需要用引号引起来。司机为您处理。
那真的是我避免SQL注入所需要做的一切吗? 真的那么容易吗?
您可以假设MySQL会有所作为。另外,我真的只是对针对SQL注入使用准备好的语句感到好奇。在这种情况下,我不在乎XSS或其他可能的漏洞。
简短的回答是“ 否” ,PDO准备将不会为您防御所有可能的SQL注入攻击。对于某些晦涩的边缘情况。
攻击
因此,让我们开始展示攻击…
$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));
在某些情况下,它将返回1行以上。让我们剖析这里发生的事情:
$pdo->query('SET NAMES gbk');
对于这种攻击的工作,我们所需要的编码服务器的期望既编码的连接'
为ASCII即0x27
和 有一些文字,其最后一个字节是一个ASCII\
即0x5c
。事实证明,会默认在MySQL
5.6支持5个这样的编码:big5
,cp932
,gb2312
,gbk
和sjis
。我们将gbk
在此处选择。
现在,注意SET NAMES
这里的用法非常重要。这将在 服务器上 设置字符集。还有另一种方法,但是我们会尽快到达那里。
我们将用于此注入的有效负载从字节序列开始0xbf27
。在中gbk
,这是一个无效的多字节字符;在latin1
,这是字符串¿'
。请注意,在latin1
和 gbk
,0x27
对自己是一个文字'
字符。
我们选择此有效负载是因为,如果调用addslashes()
它,我们会在字符之前插入一个ASCII码,\
即。因此,我们将以结束,其中有两个字符序列:后跟。换句话说,就是一个
有效 字符,后跟一个未转义的。但是我们没有使用。继续下一步…0x5c``'``0xbf5c27``gbk``0xbf5c``0x27
__'``addslashes()
这里要意识到的重要一点是,默认情况下,PDO 不会
执行真正的预处理语句。它模拟它们(对于MySQL)。因此,PDO在内部构建查询字符串,并mysql_real_escape_string()
在每个绑定的字符串值上调用(MySQL
C API函数)。
对CAPI的调用的mysql_real_escape_string()
不同之处addslashes()
在于它知道连接字符集。因此,它可以为服务器期望的字符集正确执行转义。但是,到目前为止,客户端认为我们仍在使用latin1
该连接,因为我们从未告诉过它。我们确实告诉我们正在使用的_服务器_gbk
,但是 客户端 仍然认为是latin1
。
因此,调用会mysql_real_escape_string()
插入反斜杠,并且我们'
的“转义”内容中有一个自由悬挂的字符!事实上,如果我们看一下$var
在gbk
字符集,我们会看到:
OR'OR 1 = 1 / *
这正是攻击所需要的。
这只是一个形式,但这是呈现的查询:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
恭喜,您刚刚使用PDO Prepared Statements成功攻击了一个程序…
简单修复
现在,值得注意的是,可以通过禁用模拟的准备好的语句来防止这种情况:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
这 通常会
导致产生真正准备好的语句(即,将数据发送到与查询分开的数据包中)。但是,要知道,PDO会悄悄地退回到仿真陈述,MySQL不能原生准备:那些可被列在手册中,但要注意选择合适的服务器版本)。
正确的解决方法
这里的问题是我们没有调用C API mysql_set_charset()
代替SET NAMES
。如果这样做的话,如果我们从2006年开始使用MySQL版本,我们会很好的。
如果您使用的是较早的MySQL版本,则存在一个错误,mysql_real_escape_string()
即出于逃避目的,无效的多字节字符(例如我们的有效负载中的字符)被视为单个字节,
即使客户端已被正确告知连接编码 ,因此该攻击也可能仍然成功。该错误是固定在MySQL4.1.20,5.0.22和[5.1.11。
但是最糟糕的是,直到5.3.6 PDO
才公开C API mysql_set_charset()
,因此在以前的版本中,它 无法
防止所有可能的命令遭受这种攻击!现在,它作为DSN参数公开,应 代替 SET NAMES
…
拯救的恩典
正如我们在一开始所说的那样,要使这种攻击起作用,必须使用易受攻击的字符集对数据库连接进行编码。
utf8mb4
是 不容易,但可以支持 所有的 Unicode字符:所以你可以选择使用的是代替,但它只是可利用从MySQL
5.5.3。另一种选择是utf8
,它也_不易受到攻击,_ 并且可以支持整个Unicode Basic MultilingualPlane。
或者,您可以启用NO_BACKSLASH_ESCAPES
SQL模式,该模式(除其他外)会更改的操作mysql_real_escape_string()
。启用该模式,0x27
将被替换0x2727
,而不是0x5c27
从而逃逸过程_不能_ 在任何地方,他们以前不存在的脆弱编码的创建有效的字符(即0xbf27
尚0xbf27
等)-这样的服务器仍然会拒绝的字符串为无效。但是,
安全的例子
以下示例是安全的:
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始终会执行真正的预备语句。
如果你:
要么
utf8
/ latin1
/ ascii
/等)要么
NO_BACKSLASH_ESCAPES
SQL模式您是100%安全的。
否则, 即使您使用的是PDO预准备语句 ,也容易受到攻击 。
附录
我一直在缓慢地开发一个补丁程序,以更改默认值,以不模仿将来的PHP版本。我遇到的问题是,当我这样做时,很多测试都失败了。一个问题是,模拟的Prepare只会在执行时抛出语法错误,而真正的Prepare则会在Prepare上抛出错误。因此,这可能会导致问题(这是测试很乏味的部分原因)。
问题内容: 停止使用不推荐使用的mysql_ *函数后,我切换到mysqli。但是后来,我注意到未准备好的语句对于SQL注入是不安全的。然后,我再次更改了代码。 我所拥有的是以下函数,该函数检查数据库中是否存在变量 $ ID 并打印该行的 title 值: 我将其更改为: 我的问题是:这是执行准备好的语句的正确方法吗?另外,我现在可以安全使用SQL注入吗?非常感谢任何愿意回答这个问题的人:) 问题
我正在处理的目标系统不支持PDO ist,尽管我在Postgres-DB8.2+上使用PHP 5.1.x寻求防止SQL注入的解决方案。目前没有机会切换到PDO。 我目前的解决方案是pg_prepare-prepared语句: 该函数从查询字符串创建一个名为stmtname的准备好的语句,该语句必须包含单个SQL命令。stmtname可以是“”来创建未命名语句,在这种情况下,任何先前存在的未命名语句
问题内容: 准备好的语句如何帮助我们防止SQL注入攻击? 维基百科说: 准备好的语句可以抵御SQL注入,因为稍后需要使用其他协议传输的参数值不需要正确地转义。如果原始语句模板不是从外部输入派生的,则不会发生SQL注入。 我不太清楚原因。用简单的英语和一些例子,简单的解释是什么? 问题答案: 这个想法很简单-查询和数据被发送到数据库服务器 分开 。 就这样。 SQL注入问题的根源在于 代码和数据 的
假设上面的代码是我的查询。什么是一个安全的替代方案,将允许自动增加在mySQL数据库。
问题内容: 在PDO :: Prepare页上指出: “并且通过消除对参数的手动引用来帮助防止SQL注入攻击” 知道这一点,是否有一个像mysql_real_escape_string()这样的PHP函数可以照顾到PDO的转义?还是PDO会为我做好一切转义? 编辑 我现在意识到我问了一个错误的问题。我的问题确实是,“ PDO会为我做什么?” 我现在通过这些答案意识到,它实际上仅消除了对引号进行转义
问题内容: 今天有人告诉我,我确实应该在应用程序中使用PDO和准备好的语句。在我了解好处的同时,我也在努力了解如何将其实现到我的工作流程中。除了它使代码更简洁外,我是否应该有一个特定的数据库类来容纳所有准备好的语句,还是应该在每次运行查询时都创建一个?我发现很难理解何时应使用标准PDO查询以及何时应使用准备好的语句。任何示例,技巧或教程链接将不胜感激。 问题答案: pdo :: prepare()