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

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

手册上说:

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

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

10794 次点击
所在节点    PHP
59 条回复
bianhua
2017-05-20 22:27:00 +08:00
@ovear

Let's recap:

从第二层(包括你)开始两位说“能( 100%防止 SQL 注入)”。

我在第三层说“不完全能”,有边界情况并且给出了链接。

你在第四层说:
> However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively
>
> 请认真朗读上句。。这是一个 bug 级别的东西,下面看评论已经提交给 php 官方了。

好,你为了支持你的管但认为这是的 Bug,没问题。我把过程解释给你看,告诉你 PO 说的是类似在没设置好字符集的情况下跑 mysql_real_escape_string 的情况(本身跟 PDO 机制如何没关系),会造成字符变形攻击。

你对我丢:

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

这还是想证明“那是个 Bug ”并且坚持你“能”的观点还是承认原来 prepare 并不能完全阻止 SQL 注入?

另外,你在 9 楼所说

> 而你举例的这个问题在于,PHP 没有这么操作,而是因为某种原因,回退到 SQL 语句拼接了,只不过是 PHP 自动拼接的。

这根本就是错的,原因是进行了本地的 prepare,而不是退回了 SQL 拼接。

此外:

> 另外,PHP 官方已经写得很清楚了,设置 charset 的时候,要在初始化的时候进行。你错误的使用了 PDO,导致 PDO 本身失效,能怪谁呢?

是啊,“你错误的使用了 PDO ”,导致 prepare 不能 100%防止 SQL 注入,怪谁呢楼主?

我建议你直接承认你的错误,因为你在三楼敲“能”这个字的时候,无论后面你怎么说,都已经无法挽回了。
ovear
2017-05-20 22:34:58 +08:00
@bianhua 看来你到现在都没有理解 pdo 的工作原理,就不跟你讨论技术了。
那就希望你可以再花点时间认真看
http://zhangxugg-163-com.iteye.com/blog/1835721

如果你非得揪着字眼,我承认我那句话不完善
应该是 在 pdo 起作用的时候,fully functional 的情况下,100%

另外请不要使用诡辩论,这对问题的解决没有任何的意义。按你的说法,那就是任何东西都是不能回答 100% 的。那这些所有的问题都不用讨论了,反正你的解决方法也不是 100%的,你说是吧。

另外,你的错误我还是要指出来的


>另外,你在 9 楼所说
>> 而你举例的这个问题在于,PHP 没有这么操作,而是因为某种原因,回退到 SQL 语句拼接了,只不过是 PHP 自动拼接的。
>这根本就是错的,原因是进行了本地的 prepare,而不是退回了 SQL 拼接。 -> 这句话是完全错误的,详情请见上面连接的抓包信息。
gdtv
2017-05-20 22:39:01 +08:00
@ovear
我英文不好,请问这样写代码是不是可以保证 100%没有注入了? 0day 之类的漏洞不算。

$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 /*"));
ovear
2017-05-20 22:43:35 +08:00
@gdtv 不能,你要在初始化的时候指定 charset,这是错误的方法。
不能使用$pdo->query('SET NAMES gbk');

要么使用刚刚那篇文章作者之前提到的 mysql_set_charset('gbk')
要么在初始化 pdo 的时候指定 charset (推荐这种)
$pdo = new PDO("mysql:host=localhost;dbname=world;charset=utf8", 'my_user', 'my_pass');

详情可以参考下
https://secure.php.net/manual/zh/mysqlinfo.concepts.charset.php
https://secure.php.net/manual/zh/function.mysql-set-charset.php
ovear
2017-05-20 22:49:41 +08:00
@murmur 啊,刚才没看到,这就不太清楚了。我也没查过相关文档,不过应该会完善点的吧。。
bianhua
2017-05-20 22:54:37 +08:00
@ovear

抓包,呵呵呵。

好,哪怕你将本地 prepare 模拟理解为“退回了 SQL 拼接”(当然,想想看确实,字符串最后毕竟是组合起来了嘛哈哈哈),但是楼主问题的预设是在 bind 和 exec 阶段,我提示的问题是在 initialization 阶段。

如果 Initialization 没有作对,那么之后的过程肯定会出问题,所以那时候的 prepare 必然不能保证安全。这没有任何问题,符合我的观点。

另外你上面:
> 如果你非得揪着字眼,我承认我那句话不完善
> 应该是 在 pdo 起作用的时候,fully functional 的情况下,100%

我能这样看么:prepare 自己没有办法保证 100%不会注入,只有正确配置好一切之后它才能提供安全保证?

我不知道你在后面拼命证明我是错的,最后得到了什么结论。
ovear
2017-05-20 22:59:33 +08:00
@bianhua

> 好,哪怕你将本地 prepare 模拟理解为“退回了 SQL 拼接”(当然,想想看确实,字符串最后毕竟是组合起来了嘛哈哈哈),但是楼主问题的预设是在 bind 和 exec 阶段,我提示的问题是在 initialization 阶段。
请再仔细看那篇 163 的文章,包括你后面的所谓的 initialization 都是错误的,希望大家也能仔细看一下之前的文章。

具体可以仔细看看 fallback (也就是普通 sql 执行),和 prepared statement create && execute 的区别。

> 我能这样看么:prepare 自己没有办法保证 100%不会注入,只有正确配置好一切之后它才能提供安全保证?
错误,只要是 MySQL native prepare,就没办法注入。特殊的 0day 之类的素除外。

>我不知道你在后面拼命证明我是错的,最后得到了什么结论。
MySQL native prepare 足够安全,就算不是 100%,也是无限接近于 100%
bianhua
2017-05-20 23:08:50 +08:00
@ovear

上面我说的:
>> 另外你上面:
>> 如果你非得揪着字眼,我承认我那句话不完善
>> 应该是 在 pdo 起作用的时候,fully functional 的情况下,100%
>>
>> 我能这样看么:prepare 自己没有办法保证 100%不会注入,只有正确配置好一切之后它才能提供安全保证?

你对此说:
> 错误,只要是 MySQL native prepare,就没办法注入。特殊的 0day 之类的素除外。

这不是诡辩是什么?我建议我们还是不要经常进行这样的交流比较好。
ovear
2017-05-20 23:12:42 +08:00
算了,我还是顺便贴出来吧



这是普通 sql 执行的情况,也可以说是在 PDO 失效的情况下,fallback 为 php 本地拼接的情况。



这是 PDO 正常工作的情况下,也就是 MySQL native prepare 工作的情况下。
首先是要分析语句逻辑( Create prepared statement ),所有数据都以占位符表示,所以只要你的语句本身(模板)没有问题,就不可能被注入。


这是执行语句的情况下,只传输数据,MySQL 会自动进行执行。

其实 prepared statement 是借用的 oop 的思想,可以这么概括
先创建一个语句模板,然后通过分析语句模板,创建出一个 sql 无关的 语句对象
然后调用这个 语句对象 的 execute 方法,再传入数据。因为 语句对象 已经完成分析了,是 sql 无关的,所以你传入任何数据都不会改变语义。

通过这个可以得出 MySQL native prepared statement 的工作流程

1)程序员编写语句模板
2)数据库创建语句对象
3)程序员传入数据,数据库执行 prepared statement

这里不存在什么 init bind exec 的区分。
只要创建了 语句对象, 即使用了 MySQL prepared statement,就已经完成所谓的 init 了。
这个语句逻辑是由程序员决定的,你要是写出这样的代码

> String sql = "select * from user where username =" + username;
> PreparedStatment pstmt = db.prepared(sql);
> pstmt.execute();

那 prepared statement 也救不了你了

)啊啊啊啊英语的大小写好烦啊,不管了
ovear
2017-05-20 23:15:27 +08:00
@bianhua
上面我说的:
>> 另外你上面:
>> 如果你非得揪着字眼,我承认我那句话不完善
>> 应该是 在 pdo 起作用的时候,fully functional 的情况下,100%
>>
>> 我能这样看么:prepare 自己没有办法保证 100%不会注入,只有正确配置好一切之后它才能提供安全保证?

你对此说:
> 错误,只要是 MySQL native prepare,就没办法注入。特殊的 0day 之类的素除外。

>>> 没有任何问题啊,prepare 就是指的 MySQL native prepared statement。但是因为你在上面一直搞混 PHP 的 emulate prepared statement 和 MySQL native prepared statement。我特意指出来,我指的是后者。

所以建议你还是多看看书。
bianhua
2017-05-20 23:35:11 +08:00
@ovear

所以你在证明什么?

你帖的这些东西只是我在发 4 楼那个帖子之前已经做好功课的东西。你为什么要尝试驳斥我并没有提出的东西呢?心虚?

我现在来割断你最后一根稻草吧:

The Simple Fix

Now, it's worth noting that you can prevent this by disabling emulated prepared statements:

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

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).

的意思是:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
能解决这个问题。但是(就是 However 那句)需要注意,说一些 MySQL 的版本不能正确的 prepare,那时 PHP 的 MySQL 驱动会退化到本地 prepare。所以需要选择正确的 MySQL 版本来解决问题(如果你程序里有某些旧版本不支持的 prepare 在新版本里支持了,要使用新版本)(当然,也可以不写那些语句)。

要点是:这并不是 Bug,而是 MySQL 还不支持这些,所以 PHP 的 MySQL 驱动不会发送。

请问你抓包有什么用?能驳斥我哪一条观点?
liaohongxing
2017-05-20 23:35:34 +08:00
PDO 是能 100% 防止注入的,其中有 BUG 难免 ,PHP 官方也在不断改进 使之能称得上 100%能这一目标。
正确使用前提 。未正确使用这一切都无从谈起 。

PDO 本地模拟的前提是要正确设置 “ DSN ” 字符串的 charset 信息
>> $pdo->query('SET NAMES gbk');
上面这行只是告诉 MYSQL 即将到来的编码而已 ,PHP 本身并不知道字符串的编码 。以上不推荐使用 。

一定要在 DSN 中设置编码 ,告诉 PDO, 本地驱动转义时使用指定的字符集,只有正确的告诉了 PHP 使用的编码 ,在即将到来的牛鬼蛇神乱七八糟的数据前 ,本地驱动才能正确处理。
Coande
2017-05-20 23:57:24 +08:00
1000 ?
Coande
2017-05-20 23:59:17 +08:00
@Coande 不用理 wo,回错帖子~
qhgongzi
2017-05-21 00:37:57 +08:00
看 lz 几个意思,是为了工程角度写代码,还是安全审计之类的评估。

工程角度:请了解原理,并坚定的相信能,sql 注入就是数据与 code 不分,用户能将数据变成 code,prepare 就是将执行 code 与数据分离,sql 注入彻底失去了生存土壤。

安全审计角度,ok 这 prepare 能保障,但他有没有失效的时候,有。mysql 太老,php 太老,模拟 prepare 等待。是风险要评估。

lz 的问法,是:"我现在要写一条 sql,手册说 pdo prepare 的参数是安全的,其他条件参数不是用户输入的,这样是不是可以确保 100% 不会 SQL 注入",请坚定的相信能,不用那种太古老的系统程序版本是架构师考虑的事情,只要能用上,应该相信手册。
qhgongzi
2017-05-21 00:40:35 +08:00
不是用户输入的,你只要保证不会间接构造,写死在 sql 里,别人绝对注入不了的。
fuxkcsdn
2017-05-21 00:42:16 +08:00
$sql = 'select * from tab where name=' . $name . ' and password = ? limit 1';
$pdo->prepared($sql);
$pdo->execute([$password]);

PDO: 被注入怪我咯?黑人问号.jpg
shiji
2017-05-21 00:57:50 +08:00
不是很明白这个话题有什么可撕的。。。。
msg7086
2017-05-21 06:31:52 +08:00
@bianhua
@ovear
你俩说了半天就不是在说一个东西 = =

首先,没什么东西是 100%的。就算是用了 Native Prepare 又怎样? Oracle 的码农脑子一抽,一个缓冲区溢出了,照样炸啊。网络被人 MITM 了,照样炸啊。
其次 SET NAMES gbk 这和拼完字符串再传进 Prepare 一样,都属于对类库的「误用」。误用的时候出问题,并不奇怪。

你俩本来就没站在同一个前提下讨论问题。
一个说的是正正常常写代码的情况,一个说的是程序员脑子一抽写了奇怪东西的情况……
bianhua
2017-05-21 07:54:47 +08:00
@msg7086

这就是问题。

他想用指出我“有问题”,来美化当时他的疏漏而已。

所以你看,那片文章大片篇幅在说字符的事(这是那个 Po 的主题,我想你能看懂),他偏偏看不见,却能看到关于 MySQL Driver 的 prepare 兼容性警告,然后说是“ Bug ”。

这很邪恶不是么?不知道是不是仅仅是为了让自己个人主页的 Reply 好看,显得自己专业?

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

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

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

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

© 2021 V2EX