栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

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

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

简短的回答是“ 否” ,PDO准备将不会为您防御所有可能的SQL注入攻击。对于某些晦涩的边缘情况。

攻击

因此,让我们开始展示攻击…

$pdo->query('SET NAMES gbk');$var = "xbfx27 OR 1=1 /*";$query = 'SELECt * FROM test WHERe name = ? LIMIT 1';$stmt = $pdo->prepare($query);$stmt->execute(array($var));

在某些情况下,它将返回1行以上。让我们剖析这里发生的事情:

  1. 选择字符集
    $pdo->query('SET NAMES gbk');

对于这种攻击的工作,我们所需要的编码服务器的期望既编码的连接

'
为ASCII即
0x27
有一些文字,其最后一个字节是一个ASCII
0x5c
。事实证明,会默认在MySQL
5.6支持5个这样的编码:
big5
cp932
gb2312
gbk
sjis
。我们将
gbk
在此处选择。

现在,注意

SET NAMES
这里的用法非常重要。这将在 服务器上 设置字符集。还有另一种方法,但是我们会尽快到达那里。

  1. 有效载荷

我们将用于此注入的有效负载从字节序列开始

0xbf27
。在中
gbk
,这是一个无效的多字节字符;在
latin1
,这是字符串
¿'
。请注意,在
latin1

gbk
0x27
对自己是一个文字
'
字符。

我们选择此有效负载是因为,如果调用

addslashes()
它,我们会在字符之前插入一个ASCII码,
即。因此,我们将以结束,其中有两个字符序列:后跟。换句话说,就是一个
有效 字符,后跟一个未转义的。但是我们没有使用。继续下一步…
0x5c``'``0xbf5c27``gbk``0xbf5c``0x27

__
'``addslashes()

  1. $ stmt- > execute()

这里要意识到的重要一点是,默认情况下,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 / *

这正是攻击所需要的。

  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()
代替
SETNAMES
。如果这样做的话,如果我们从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
不容易,但可以支持 所有的 Unipre字符:所以你可以选择使用的是代替,但它只是可利用从MySQL
5.5.3。另一种选择是
utf8
,它也_不易受到攻击,_ 并且可以支持整个Unipre 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("xbfx27 OR 1=1 /*");mysql_query("SELECt * FROM test WHERe name = '$var' LIMIT 1");

因为服务器的期望

utf8

mysql_set_charset('gbk');$var = mysql_real_escape_string("xbfx27 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("xbfx27 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("xbfx27 OR 1=1 /*"));

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

$mysqli->query('SET NAMES gbk');$stmt = $mysqli->prepare('SELECt * FROM test WHERe name = ? LIMIT 1');$param = "xbfx27 OR 1=1 /*";$stmt->bind_param('s', $param);$stmt->execute();

因为MySQLi始终会执行真正的预备语句。

如果你:

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

要么

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

要么

  • 启用
    NO_BACKSLASH_ESCAPES
    SQL模式

您是100%安全的。

否则, 即使您使用的是PDO预准备语句 ,也容易受到攻击

附录

我一直在缓慢地开发一个补丁程序,以更改默认值,以不模仿将来的PHP版本。我遇到的问题是,当我这样做时,很多测试都失败了。一个问题是,模拟的Prepare只会在执行时抛出语法错误,而真正的Prepare则会在Prepare上抛出错误。因此,这可能会导致问题(这是测试很乏味的部分原因)。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/442880.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号