使用 PDO 的 prepare 预处理,能 100%防止 SQL 注入吗?

2017-05-20 15:23:11 +08:00
 xiaoyanbot

手册上说:

提供给预处理语句的参数不需要用引号括起来,驱动程序会自动处理。如果应用程序只使用预处理语句,可以确保不会发生 SQL 注入。(然而,如果查询的其他部分是由未转义的输入来构建的,则仍存在 SQL 注入的风险)。

查询的其它部分不用输入。 是不是可以确保 100% 不会 SQL 注入了呢 ?

10757 次点击
所在节点    PHP
59 条回复
xiaoyanbot
2017-05-20 15:23:38 +08:00
maskerTUI
2017-05-20 18:52:08 +08:00
ovear
2017-05-20 18:55:05 +08:00
bianhua
2017-05-20 19:06:15 +08:00
ovear
2017-05-20 19:18:21 +08:00
@bianhua

However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively

请认真朗读上句。。这是一个 bug 级别的东西,下面看评论已经提交给 php 官方了。
MySQL 的 databind 是通过二进制传输的,他的语法检测器根本不是 sql 语句,哪来的注入。。至于 fallback 的问题,得找 php 官方问下为什么会 fallback 了。
lsido
2017-05-20 19:26:14 +08:00
没有绝对的安全,弱
lsido
2017-05-20 19:27:20 +08:00
字符集编码的部分转义可能存在边缘操作
bianhua
2017-05-20 19:46:38 +08:00
@ovear

哦。好吧,我之前看的时候竟然没看出这句话与问题本身的上下文关系(事实上,现在我看了 10 次也没看出来)。英语渣表示抱歉。
ovear
2017-05-20 20:02:34 +08:00
@bianhua 简而言之
pdo 的核心在于 databind
比如说你写 select * from post where fid = ? and username = ?
那么在 pdo 的情况下,mysql 接收到的就是这个语句,带有未知参数的 sql 语句。
然后 MySQL 的语法分析器,会对这句语句直接进行分析,然后得出带有未知参数的内部语句
然后 PHP 会把与之绑定的参数再传过去,然后 MySQL 再用完形填空的形式,把数据填上。
因为这时候已经完成语义分析了,任何传输过来的数据,都仅仅被当做数据处理,而不会影响语义。
这种情况下,除非 MySQL 内部有内存溢出之类的 BUG,不然都是安全的。

而你举例的这个问题在于,PHP 没有这么操作,而是因为某种原因,回退到 SQL 语句拼接了,只不过是 PHP 自动拼接的。
那么 MySQL 接受到的是已经拼接好的语句了,那这时候如果 PHP 自己的过滤没做好, 导致语义发生了变化,MySQL 也无能为力。
bianhua
2017-05-20 20:07:47 +08:00
@ovear

或者,这只是一种 Charset 攻击,没涉及到你所说的东西。;)
ovear
2017-05-20 20:12:02 +08:00
@bianhua
However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively

请再认真的读一遍这句话。
eoo
2017-05-20 20:13:15 +08:00
一直用 mysqli 预处理
ovear
2017-05-20 20:24:41 +08:00
算了,还是我翻一下吧

However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively
然而,你必须注意,有一些语句 MySQL 并不能直接 prepare,这时候 PDO 会静默回退到 SQL 拼接,并且启用 PHP 内置的 prepare 模拟器。

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.
你必须清楚,PDO 并 不不不不不不不 会 总是执行真正的 prepare 操作。它会通过自带的 prepare 模拟器,进行模拟。 因此,在这种情况下,PDO 会在内部直接 拼接语句,然后通过 mysql_real_escape_string 来对输入参数进行过滤,防止注入。
murmur
2017-05-20 20:44:24 +08:00
@ovear 我记得 php5.3 还是 5.几之前有个 bug 转义是在本地做的然而高版本的 pdo 不是都交给 mysql 了么
bianhua
2017-05-20 21:33:00 +08:00
@ovear

但为什么你不翻译完?或者我们通过同一个链接看到的版本不是一个? LOL

要点是这样的,这是那个 PO 主的 Demo:
$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));

注意,他没有用 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

其实到这就不用看了,因为后面肯定会有问题:服务器所认为的链接字符集被设置成了 gbk,本地的未改变,而且 prepare 是在本地。

然后那个 PO 主就挑了个 Easy Target,0xbf27 => addslashes() => 0xbf5c27 ( 0x5c == '\') => 字符串:縗'。

所以自然注入了。

这就是问题。所以这是一种 Charset Attack,利用字符串变形进行攻击。防御的方式也说了:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

> However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively

这说的是另一回事。解决方案是:beware to select the appropriate server version

希望这能解答你的疑问 :)

当然,回到楼主的问题:prepare 能保证你的安全不被黑么?答:不能。

安全问题没有银弹,不要认为引入一个库或者一个方法就能解决所有问题,你还需要知道如何正确的使用它们。

综上所述,安全是个体系,而不是一条函数。你需要将所有的事情作对才是安全的。
sensui7
2017-05-20 21:38:36 +08:00
与其去纠结 PDO 的问题, 不如好好花心思在自己的程序上, 程序上不能交给 PDO 就不管了吧? 万一你更换持久化方案呢??
所以你的程序还是要做 sanitize
ovear
2017-05-20 21:47:21 +08:00
@bianhua 怪不得你说你英语不好。。的确有点差,建议再多看几遍
ovear
2017-05-20 22:02:29 +08:00
但为什么你不翻译完?或者我们通过同一个链接看到的版本不是一个? LOL
>翻译完你给钱? 还有一种可能,要么是我的语文水平太差,要么是你英语太差

要点是这样的,这是那个 PO 主的 Demo:
$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));

注意,他没有用 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
> 根据 https://secure.php.net/manual/zh/pdo.setattribute.php
> PDO::ATTR_EMULATE_PREPARES 启用或禁用预处理语句的模拟。 有些驱动不支持或有限度地支持本地预处理。使用此设置强制 PDO 总是模拟预处理语句(如果为 TRUE ),或试着使用本地预处理语句(如果为 FALSE )。如果驱动不能成功预处理当前查询,它将总是回到模拟预处理语句上。 需要 bool 类型。
> 默认情况为 true,存在我翻译的问题

其实到这就不用看了,因为后面肯定会有问题:服务器所认为的链接字符集被设置成了 gbk,本地的未改变,而且 prepare 是在本地。
> 所以你想表达生命,再复述我的内容一遍?

然后那个 PO 主就挑了个 Easy Target,0xbf27 => addslashes() => 0xbf5c27 ( 0x5c == '\') => 字符串:縗'。

所以自然注入了。

这就是问题。所以这是一种 Charset Attack,利用字符串变形进行攻击。防御的方式也说了:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
> However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively

> This will usually result in a true prepared statement (i.e. the data being sent over in a separate packet from the query). However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively: those that it can are listed in the manual, but beware to select the appropriate server version).


下面是废话, 对 beware to select the appropriate server version 理解有问题,这不是解决方案,是让你注意看文档的时候选择正确的文档去看,因为不同版本的 MySQL fallback 的条件不同。

我们再看回答主的第一句话
>> I'm adapting this answer to talk about PDO...
>> adapting this answer
>>The short answer is yes, yes there is a way to get around mysql_real_escape_string().

所以这个问题产生的过程是
1)用了会 fallback 的 sql 语句,PDO 认为不能进行 MySQL native prepare,进行本地摸你
2)PHP 调用 mysql_real_escape_string()
3)mysql_real_escape_string 在某些版本下有 BUG,会导致过滤失败
4)PDO 直接把拼接的 SQL 发过去了
5)被注入了

另外,PHP 官方已经写得很清楚了,设置 charset 的时候,要在初始化的时候进行。你错误的使用了 PDO,导致 PDO 本身失效,能怪谁呢?
> https://secure.php.net/manual/zh/ref.pdo-mysql.connection.php
ovear
2017-05-20 22:03:37 +08:00
@ovear 噢看漏了,你的那篇文章作者也有说噢

> I said at the very beginning that we could have prevented all of this if we had used mysql_set_charset('gbk') instead of SET NAMES gbk. And that's true provided you are using a MySQL release since 2006.
ovear
2017-05-20 22:16:29 +08:00
@ovear 噢,再纠正一点

native prepare 的意思是 原生的 prepare,就是指的是 MySQL 自身的 prepare,这个是不会有任何注入问题的。之前 @我的层主理解有误,忘记说了,不然因为我没指出来,以后误导别人就不好了。
emulate prepare 的意思是 PHP 本地的 prepare 模拟

)还有挺多错别字,原谅我的输入法,在看电影,没怎么仔细看

最后如果我的理解有问题,欢迎指出。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/362646

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX