首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
宝塔
V2EX  ›  PHP

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

  •  
  •   xiaoyanbot · 2017-05-20 15:23:11 +08:00 · 7019 次点击
    这是一个创建于 912 天前的主题,其中的信息可能已经有所发展或是发生改变。

    手册上说:

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

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

    59 回复  |  直到 2017-05-22 23:27:30 +08:00
        1
    xiaoyanbot   2017-05-20 15:23:38 +08:00
        2
    maskerTUI   2017-05-20 18:52:08 +08:00 via Android
        3
    ovear   2017-05-20 18:55:05 +08:00
        4
    bianhua   2017-05-20 19:06:15 +08:00
        5
    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 了。
        6
    lsido   2017-05-20 19:26:14 +08:00 via Android
    没有绝对的安全,弱
        7
    lsido   2017-05-20 19:27:20 +08:00 via Android
    字符集编码的部分转义可能存在边缘操作
        8
    bianhua   2017-05-20 19:46:38 +08:00
    @ovear

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

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

    或者,这只是一种 Charset 攻击,没涉及到你所说的东西。;)
        11
    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

    请再认真的读一遍这句话。
        12
    eoo   2017-05-20 20:13:15 +08:00 via Android
    一直用 mysqli 预处理
        13
    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 来对输入参数进行过滤,防止注入。
        14
    murmur   2017-05-20 20:44:24 +08:00
    @ovear 我记得 php5.3 还是 5.几之前有个 bug 转义是在本地做的然而高版本的 pdo 不是都交给 mysql 了么
        15
    bianhua   2017-05-20 21:33:00 +08:00   ♥ 1
    @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 能保证你的安全不被黑么?答:不能。

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

    综上所述,安全是个体系,而不是一条函数。你需要将所有的事情作对才是安全的。
        16
    sensui7   2017-05-20 21:38:36 +08:00
    与其去纠结 PDO 的问题, 不如好好花心思在自己的程序上, 程序上不能交给 PDO 就不管了吧? 万一你更换持久化方案呢??
    所以你的程序还是要做 sanitize
        17
    ovear   2017-05-20 21:47:21 +08:00
    @bianhua 怪不得你说你英语不好。。的确有点差,建议再多看几遍
        18
    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
        19
    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.
        20
    ovear   2017-05-20 22:16:29 +08:00
    @ovear 噢,再纠正一点

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

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

    最后如果我的理解有问题,欢迎指出。
        21
    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 注入,怪谁呢楼主?

    我建议你直接承认你的错误,因为你在三楼敲“能”这个字的时候,无论后面你怎么说,都已经无法挽回了。
        22
    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 拼接。 -> 这句话是完全错误的,详情请见上面连接的抓包信息。
        23
    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 /*"));
        24
    ovear   2017-05-20 22:43:35 +08:00   ♥ 1
    @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
        25
    ovear   2017-05-20 22:49:41 +08:00
    @murmur 啊,刚才没看到,这就不太清楚了。我也没查过相关文档,不过应该会完善点的吧。。
        26
    bianhua   2017-05-20 22:54:37 +08:00
    @ovear

    抓包,呵呵呵。

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

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

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

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

    我不知道你在后面拼命证明我是错的,最后得到了什么结论。
        27
    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%
        28
    bianhua   2017-05-20 23:08:50 +08:00
    @ovear

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

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

    这不是诡辩是什么?我建议我们还是不要经常进行这样的交流比较好。
        29
    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 也救不了你了

    )啊啊啊啊英语的大小写好烦啊,不管了
        30
    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。我特意指出来,我指的是后者。

    所以建议你还是多看看书。
        31
    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 驱动不会发送。

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

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

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

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

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

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

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

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

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

    这就是问题。

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

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

    这很邪恶不是么?不知道是不是仅仅是为了让自己个人主页的 Reply 好看,显得自己专业?
        41
    leeg810312   2017-05-21 08:04:00 +08:00 via Android
    一个说好好写代码,避免已知 bug,肯定不会有 sql 注入。一个说总会有写不好代码的时候,所以一定不能避免 sql 注入。就 lz 问题,我认为前者的回答是没有问题的,后者有点吹毛求疵。
        42
    bianhua   2017-05-21 09:01:24 +08:00
    @leeg810312 如果你在说我吹毛求疵的话,那么你真应该看看我到底说了什么:
    https://www.v2ex.com/t/362646#r_4343233

    当然,大部分人都喜欢简单的答案嘛。“能”,够简单,喜欢吧?

    可你在发帖的时候也带了前提:“好好写代码,避免已知 bug ”。

    这跟我说的难道不一样?
        43
    yangqi   2017-05-21 10:52:00 +08:00   ♥ 1
    @bianhua 要么是你逻辑有问题,要么是你语言表达有问题。打个比方,你问别人“用枪能 100%杀死一个人么?”,答案很明显是能。但你说不能,然后举了个例子,开枪的人不会使用或者打偏了,所以得出结论,枪不能 100%杀死人,还要看用枪的人。乍一看好像有点道理,其实是典型的答非所问,或者说利用问题里没有提到的条件来诡辩。
        44
    ovear   2017-05-21 11:01:30 +08:00
    @bianhua 不好意思,你不能正确讨论问题,导致你已经被降权了。不是 @msg7086 我还收不到提示。

    @msg7086 所以这种情况已经排除掉了,要是这种可能性算上去,那任何事件都不可能 100%了。硬件还可以受到辐射导致 1+1 != 2 呢。

    至于我做好什么工作,那我的确没有做功课,我全程都是一边看 银河护卫队 一边回答你的。但是到后来,中断的次数已经有点让我不开心了。
    我的目的是纠正你对 prepare 错误的理解,然而你对我的一直是人身攻击。我也不好说什么了。

    另外可能你的语文理解能力也有点问题。
    我一直在说的是
    你使用了不能 native prepare 的语句
    你使用了不能 native prepare 的语句
    你使用了不能 native prepare 的语句

    从而导致了
    pdo 回退为拼接 sql
    pdo 回退为拼接 sql
    pdo 回退为拼接 sql

    够清楚了吧,我不很不懂为什么讨论一个技术问题,要上升到人身攻击上来。
    而且我是很不懂为什么我跟你说 A,你要跟我说 B,我说你 B 说的有问题,你又跟我说 C。这样偷梁换柱完全没意思啊。
    整天 judge 别人,噢你很正义,你很邪恶,没有什么意思啊。
    我也没看出什么最后一根稻草,你说了半天只能佐证你之前的英语不好啊,理解错了而已。

    PS: 这位层主上面的理论还是全部错误的,至少关于 MySQL native prepare 是这样。

    Are PDO prepared statements sufficient to prevent SQL injection?
    https://stackoverflow.com/questions/134099/are-pdo-prepared-statements-sufficient-to-prevent-sql-injection/12202218#12202218
    这个帖子的层主引用的是他在

    https://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string/12118602#12118602
    SQL injection that gets around mysql_real_escape_string()

    的回复。

    从这里可以看出,这其实是 mysql_real_escape_string 的一个 bug 吧,PDO boom 的原因,是因为他 fallback 到普通的 sql query 模式了。

    我也引用一下这位 stackoverflow 答主的回答吧

    Wrapping Up

    If you:

    Use Modern Versions of MySQL (late 5.1, all 5.5, 5.6, etc) AND PDO's DSN charset parameter (in PHP ≥ 5.3.6)
    OR

    Don't use a vulnerable character set for connection encoding (you only use utf8 / latin1 / ascii / etc)
    OR

    Enable NO_BACKSLASH_ESCAPES SQL mode
    You're 100% safe.

    这个 100% safe 是通常意义上的 100%,是可知论中的 100%。

    至此,我再参与与 @bianhua 的任何讨论,被人身攻击我已经很不开心了,我也不能保证再继续讨论,自己是否能理性分析问题,不跑题了。

    当然如果我有错误,或者各位有任何问题都可以 @我。
        45
    bianhua   2017-05-21 11:05:04 +08:00
    @yangqi

    不,我不觉得我有任何问题。因为(可能很多人看贴的时候忽略了这一点)其中的一个陈述:100%

    PDO prepare 能防止 SQL 注入么?能
    PDO prepare 能 100%防止 SQL 注入么?不能,除非你正确的使用了它

    用枪能杀死一个人么?能
    用枪能 100%杀死一个人么?不能,除非你正确的使用了它

    不知道你是否同意上面的陈述。

    // BTW: Dear FBI & NSA, yes, I just googled something like "Can I 100%ly kill a man with gun" on ... Google. It's just for this topic, I'm not actually going to kill anyone, please don't come to my door. Thank you!
        46
    bianhua   2017-05-21 11:08:50 +08:00
    好了,我早就开始后悔回复这些帖子了。

    如果各位路过的看客在看过所有相关的内容之后仍然认为我是在“诡辩”,请麻烦按下 Block。

    (如果你不愿意看相关的内容,也请 Block )
        47
    yangqi   2017-05-21 11:42:43 +08:00
    @bianhua 呵呵,你这么说就说明你逻辑问题了。或者是语文没学好。如果要问,“有了枪能 100%杀死一个人么?”和“用枪能 100%杀死一个人?”这是两个不同的问题。同理你想问的问题是“用了 PDO 的 prepare 处理,是不是 100%能防注入”?,这个和你问的“ PDO 的 prepare 处理是否能 100%防注入?”表达的是不同意思好吗?你小学语文应该没学好吧?
        48
    bianhua   2017-05-21 11:58:34 +08:00
    @yangqi

    可能吧,我并没有看出

    > “用了 PDO 的 prepare 处理,是不是 100%能防注入(?)”



    > “ PDO 的 prepare 处理是否能 100%防注入?”

    在这个问题上的区别。

    感谢你一直和我讨论这个问题,现在我们能让它过去了么?我实在不想继续回复这个帖子。事实上,我想我应该不太会想其他的帖子了。不是厌恶,只是为了避免由于我的理解不好而引起不必要的争论。
        49
    yangqi   2017-05-21 12:05:10 +08:00 via Android
    @bianhua 呵呵,所以问题归根结底是你小学语文没学好,想问的问题和表达的不是一个意思,导致交流不在一个频率上。
        50
    bianhua   2017-05-21 12:23:02 +08:00
    @yangqi 嗯,是我的错。

    但是,自从回了这个帖子之后,其他的回帖者在总结这个问题的时候,都会带上“在正确使用的前提下”这个提示。

    就像我很早之前就说到的:
    > 安全问题没有银弹,不要认为引入一个库或者一个方法就能解决所有问题,你还需要知道如何正确的使用它们。
    >
    > 综上所述,安全是个体系,而不是一条函数。你需要将所有的事情作对才是安全的。

    这逆转了一开始两个盲目信心满满的“能”,我对这样的结果是满意的,只是我在这件事上并没有得到任何好处。
        51
    bianhua   2017-05-21 13:14:05 +08:00
    @ovear

    我并不在乎一个网站的帐号,但如果我让你觉得我在人身攻击你,我向你道歉。至于我 AT 你你无法收到,除了降权,可能还因为我 Block 了你。

    我这么做是因为想要控制我能跟你争辩的频率,这样我不会因为太过生气。

    生气的原因是这样的:

    我给出的链接:
    stackoverflow.com/questions/134099/are-pdo-prepared-statements-sufficient-to-prevent-sql-injection/12202218#12202218

    The Attack 部分讲述的是攻击的过程和方法,其实就是这个版本:
    shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string

    主题是:addslashes() versus mysql_real_escape_string() debate。就是说在过滤数据的时候是否应该用 addslashes (这不是犯傻么?)。Blog 仍然是拿 0xbf27 来举例,字符变形到 0xbf5c27 然后进行攻击,很简单,几十年前的技术。

    结论是:To avoid this type of vulnerability, use mysql_real_escape_string(), prepared statements, or any of the major database abstraction libraries. 就是说通过 mysql_real_escape_string 来避免注入,而不要用 addslashes。

    于是 SO 上的回答就着这个问题衍生到了 PDO 上,然后故意没有 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 这样当然会退回本地的模拟。

    这是那个 SO 帖子的主题。下面的 Selecting a Character Set、The Payload、$stmt->execute()和 The Query 都是在讨论这个问题。这些都是进行在 MySQL 上的,而不是本地驱动。

    而解决方案则列在了 The Simple Fix、The Correct Fix 和 The Saving Grace 这几个小结下,其中 The Simple Fix 小节里,作为一种提示,SO 的 PO 主提到: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);之后,仍然有可能退回到本地 prepare,并且给出了档案

    最后,Safe Examples、Wrapping Up 和 Addendum 对 SO 回答进行了总结。

    这就是为什么我一开始觉得是不是我英语不好看漏了某些部分,因为我觉得能挑出那句话放在不应该的地方很奇怪。然而当我看过了全部链接并且提示你之后,你仍然在向我抛出“ However, be aware that PDO will silently fallback to emulating statements that MySQL can't prepare natively ”这句话,并且指责是我没有认真了看过文章并且了解原理。

    我个人觉得这是对我之前所花费精力的冒犯。

    当然,可能我理解错了一部分你的发言。如果是这样,非常抱歉。
        52
    sagaxu   2017-05-21 13:51:43 +08:00
    如果是我用,可以 100%保证
        53
    styx   2017-05-21 13:56:40 +08:00
    @bianhua @ovear 我看完整个讨论,觉得前半部分讨论已经把问题的大部分都讲明白了,只是你们执着于不同的攻击层面不愿把观点结合起来。我理解下来 @ovear 的意思是在 MySQL 的接口层面(即抓包所表示的 API ),可以认为 native prepare 是不会引入注入攻击的,因为 MySQL 是 blabla 这样做的。而 @bianhua 的重点则在于 PHP 如果认为后端 MySQL 无法 native prepare,那么会在 PHP 中自己做拼接而不能正确调用 native prepare (个人猜测如果强行 native prepare 的话应该是直接返回错误),导致可能注入。可以看到在 MySQL (native?) <--> PHP 的中间,两人的讨论重点错开来了。

    其实如果不看 SO 最开始的提问,不纠结谁对错,都退一步的话,大概就可以整合得出一个完整的回答吧,好像就是 SO 那个回答。
        54
    lsido   2017-05-21 16:14:52 +08:00
    那么这个问题,没有继续下去的必要了,楼主的意思我可以这样理解然后回答:无论是 pdo 还是拼接,只要在 [正确的情况下] ,都可以 100%避免
        55
    ywisax   2017-05-21 16:41:26 +08:00
    就我的 code review 经验来看,pdo 参数绑定是足够安全的了。
    不过实际项目中使用了 pdo 也依然会有被注入的危险,因为 prepare 之前依然有 sql 拼接的过程存在,这个环节处理不好依然会被带入恶意字符。
        56
    voocel   2017-05-21 17:26:17 +08:00 via Android
    到此为止!
        57
    Silicon   2017-05-22 05:24:51 +08:00
    我建议 @bianhua 如果你真的想证明有问题的话,毋须多言,丢以下信息上来:
    * 页面代码
    * Nginx/Apache、PHP 和 MySQL 版本
    * PoC

    这样无论是 @ovear 还是其他人,都有一个讨论的原点。

    手册和 stackoverflow 都是没什么意义的,PoC 和 exp 才有意义。
        58
    msg7086   2017-05-22 09:46:06 +08:00
    @bianhua
    @ovear
    所以我说你们俩就没站在一个层面上啊。

    @bianhua 你这里提到的:
    PDO prepare 能 100%防止 SQL 注入么?不能,除非你正确的使用了它

    根据你自己的前提来看,你这个结论当然也是错的。
    不仅仅需要你正确使用它,还需要类库的作者正确处理了他,还需要服务器端正确处理了他,还需要服务器硬件正常运行而没有受到大量电磁干扰或者辐射,等等。

    大部分情况下我们讨论内容需要有一个共同的基准,这个基准就是 Common Sense 常理。如果你对常理之外的东西进行补充,这当然没问题,但是你揪着一个常理外的做法去钻牛角尖就不好了。

    就说你 4 楼的回复,如果换成「核弹爆炸造成严重电磁干扰会影响 MySQL 的语法解析器造成注入漏洞」,那你一眼就能看出这是句意义不大的回复。那么同样的「绕过标准的 set_charset 函数,故意为服务器单独设定不同的字符集并且使用不能 native prepare 的语句去触发内置的 escape 再故意填入字节编码中含有引号的字符串会造成注入漏洞」,对正常讨论串来说也是意义不大的回复。你可以把他当做补充说明,但是开口就是一句「感觉你们信心很足啊。」怼上来,就不太好看了吧。

    如果你只是根据 100%这个词来辩的话,那就更没意思了,因为严格来说「 100%」本身就没有意义,就像「无限」这个词一样。
        59
    xiaoyanbot   2017-05-22 23:27:30 +08:00
    感谢各路大神回复
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4334 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 31ms · UTC 02:24 · PVG 10:24 · LAX 18:24 · JFK 21:24
    ♥ Do have faith in what you're doing.