ThinkPHP5.1.17_parseArrayDate函数导致的SQL注入漏洞分析

  • ThinkPHP5.1.16-ThinkPHP5.1.17(非最新版的5.1.18版本也可利用
  • 漏洞代码
<?php
namespace app\index\controller;

class Index{
    public function index(){
        $username = request()->get('username/a');
        db('users')->where(['id'=>10])->update(['username' => $username]);
    }
}
  • POC
?username[0]=point&username[1]=0x7e,database(),0x7e),'1&username[2]=updatexml&username[3]=1',concat

漏洞分析

  1. 这里先把漏洞代码的强制转换为数组去掉,进行分析,get方法就不分析了,直接分析where方法,where方法先获取where方法里的参数,然后进行删除,这里不用管,没啥用,然后调用parseWhereExp方法返回
  2. parseWhereExp方法就是解析where方法里面参数的,我们不在where里面构造SQL语句,直接过,跟进到Query::update方法,这里把我们传入的GET参数和options['data']合并然后又赋值给options['data'],然后再调用Connection::update,而parseOptions方法前面也看过了,就是对options进行操作的,无大影响。
  3. 跟进到Connection::update方法,前面的很多代码也是没用的,直接跟进到生成SQL语句的地方,也就是Builder::update
  4. 跟进Builder::update方法,可以看到这里也是熟悉的parseData方法然后再进行替换
  5. 跟进parseData方法,和parseData造成的注入一样,如果没用强制转换为数组,则会进行预编译
  6. 然后回到Connection::update方法,执行execute方法进行执行SQL语句
  7. 跟进execute方法,这里跟之前的那条链一样,都是预处理然后执行SQL语句,这样就无法注入了
  8. 回到parseData方法,之前那条链是通过让$val[0]为INC或者DEC来控制SQK语句,但是现在就算是INC或者EXP也无法控制SQL语句,但是多了一个default分支,里面调用了parseArrayData方法
  9. 跟进parseArrayData方法,只要$data[0]为point即可进入point分支,然后进行一系列赋值和拼接,
  10. 这个$result变量的结构很像updatexml函数报错的格式,此时SQL语句是直接拼接的,且拼接的内容可控,导致了SQL注入漏洞的产生。

写在后面

  • 这条链其实跟parseData造成的注入链差不多,都是绕过预编译,这个版本中把parseData造成注入的地方修复了,但是由于增加了parseArrayData漏洞方法,所以导致注入再次产生。
  • 注意的是,我在用composer和在Github上下载的下来的TP都没有default分支和Mysql::parseArrayData方法,所以这个要自己在Mysql这个类和Connection这个类加上漏洞代码