用户
搜索
  • TA的每日心情
    奋斗
    13 小时前
  • 签到天数: 108 天

    连续签到: 9 天

    [LV.6]常住居民II

    i春秋作家

    i春秋十五军装逼团团长

    Rank: 7Rank: 7Rank: 7

    27

    主题

    94

    帖子

    838

    魔法币
    收听
    0
    粉丝
    9
    注册时间
    2018-3-1

    i春秋签约作者春秋文阁积极活跃奖春秋游侠

    F0rmat i春秋作家 i春秋十五军装逼团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠 楼主
    发表于 2018-4-11 13:12:22 712653
    本帖最后由 F0rmat 于 2018-4-11 05:14 编辑

    0x01 前言

    前天在公众号看到石大神发的一篇审计thinkphp的文章,就想写一个分析流程,delay到了今天。昨天在先知也看到了chybeta发的一篇分析文章感觉也不错。分析过程,我也会做thinkphp部分功能的解析。
    废话不多说,开始吧!

    0x02 环境搭建和漏洞复现

    程序下载地址:http://www.thinkphp.cn/down/1126.html
    PHPstudy:Apache+php5.6+MySQL
    工具:PHPstorm

    1. 首先建立一个数据库名为:thinkphp
    2. 建立一个表名为:user
    3. 添加三个字段:id,name,password
    4. 在thinkphp的数据库文件填上刚才我们建立的数据库信息:
      文件位置:\thinkphp\application\database.php
    5. 打开thinkphp的调试模式:
      文件位置:\thinkphp\application\config.php
    6. 简单写一个update功能,石大神用到了模型,这里就简单写一个例子就行了。
      文件位置:\thinkphp\application\index\controller\Index.php
    <?php
    namespace app\index\controller;
    
    class Index
    {
        public function index()
        {
            $password = input('get.password/a');
            db('user')->where(['id'=> 1])->update(['password'=>$password]);
        }
    }

    这里用到了thinkphp的助手函数input(),是专用来接收get,post等的值。具体可以看:https://www.kancloud.cn/manual/thinkphp5/118044

    还有就是thinkphp的数据库操作,框架本身写好了我们调用就比较方便。所以为什么那么多人用框架去开发程序,快捷而且安全,不过也会有安全问题,就像今天这个sql漏洞,不过如果是新手的话总比自己写的好对吧哈。
    具体可以看链接:https://www.kancloud.cn/manual/thinkphp5/135178

    1. 现在就可以访问我们的payload了:
      http://thinkphp.test/thinkphp/public/index.php?password[0]=inc&password[1]=updatexml(2,concat(0x7e,user()),0)&password[2]=1

    0x03 漏洞分析

    1. 这里用phpstorm+debug来动态分析,有不懂配置的可以访问我写一篇配置文章:利用phpstorm+xdebug进行断点调试
      我们在主函数下一断点:

    然后访问我们payload,它会跳到入口文件,我们只要分析的是sql执行的地方,所以我们直接F8跳到执行sql地方:

    1. 我们继续跟进到loader.php它会包含thinkphp的Db.php文件,

      接下还会包\thinkphp\library\think\db\connector\Mysql.php文件,主要是连接数据库的操作,这里就直接跳过了。别分析分析把自己绕进去了,我只是在这里讲诉下过程,我们还是直接分析sql执行的部分吧。
    2. 我们跳到update执行的部分,文件位置:\thinkphp\library\think\db\Query.php

      继续往下看,这句是执行我们sql的地方:
    3. 我们F7跟进去,跳到文件位置\thinkphp\library\think\db\Builder.php
      parseTable函数直接F8往下执行了,这函数是处理table分析的,主要还是parseData函数,我们继续F7跟进
    4. 我们继续往下跟进

      我们看到了这里如果传入的值为数组形式的话,并且第一个参数为inc就执行switch所对应的的语句。

    可以看到这里函数对我们传入的值没有做任何处理,返回内容仍然是我们的语句:

    跟到后面返回的执行sql语句:

    执行完,我们跟进到报错的地方,说明我的语句执行成功:

    6.到这里就差不多结束了,有人问,为什么要这样给数组三个字段?
    我们可以看到我们刚才传入数组的地方,分别有三个数组

    到后面返回$result的时候就组合在一起了:

    下面是parseData的代码:

     protected function parseData($data, $options)
        {
            if (empty($data)) {
                return [];
            }
    
            // 获取绑定信息
            $bind = $this->query->getFieldsBind($options['table']);
            if ('*' == $options['field']) {
                $fields = array_keys($bind);
            } else {
                $fields = $options['field'];
            }
    
            $result = [];
            foreach ($data as $key => $val) {
                $item = $this->parseKey($key, $options);
                if (is_object($val) && method_exists($val, '__toString')) {
                    // 对象数据写入
                    $val = $val->__toString();
                }
                if (false === strpos($key, '.') && !in_array($key, $fields, true)) {
                    if ($options['strict']) {
                        throw new Exception('fields not exists:[' . $key . ']');
                    }
                } elseif (is_null($val)) {
                    $result[$item] = 'NULL';
                } elseif (is_array($val) && !empty($val)) {
                    switch ($val[0]) {
                        case 'exp':
                            $result[$item] = $val[1];
                            break;
                        case 'inc':
                            $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
                            break;
                        case 'dec':
                            $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]);
                            break;
                    }
                } elseif (is_scalar($val)) {
                    // 过滤非标量数据
                    if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) {
                        $result[$item] = $val;
                    } else {
                        $key = str_replace('.', '_', $key);
                        $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
                        $result[$item] = ':data__' . $key;
                    }
                }
            }
            return $result;
        }

    0x04 结束

    如果看不懂的,可以先去了解一下thinkphp这个框架,其他框架大同小异。如果里面一些PHP代码看不懂的话,可以去复习下PHP。

    0x05 参考

    https://www.kancloud.cn/manual/thinkphp5/

    https://mp.weixin.qq.com/s?__biz=MzU2NzE3MTU0Mw==&mid=2247483720&idx=1&sn=973bd7daa287a9e9c0171e852db6cb6b&chksm=fca001f0cbd788e6ed3119de5d3c4bfc3325205c5dd7f296e55a2ba313f73b3808525d1022e6&mpshare=1&scene=23&srcid=04104vXPMKRNVfrVzzzg7HXg#rd

    https://chybeta.github.io/2018/04/10/Thinkphp%E6%A1%86%E6%9E%B6-5-0-16-sql%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

    本帖被以下淘专辑推荐:

    getpass.cn
    围观大神操作,还是用些看不太懂!
    使用道具 举报 回复
    看大神的复现,再看看自己,留下了没有技术的泪水
    使用道具 举报 回复
    围观围观!因为在博客看了一遍了,已收藏好好研究
    使用道具 举报 回复
    发表于 2018-4-13 09:47:17
    本帖最后由 crZh 于 2018-4-13 09:49 编辑

    大神,执行过程中的call stack 如果调出来的,请教(后来了解了,不好意思我的问题)
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军装逼团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    5#
    发表于 2018-4-13 10:15:09
    crZh 发表于 2018-4-13 01:47
    大神,执行过程中的call stack 如果调出来的,请教(后来了解了,不好意思我的问题) ...

    不错,实践永远是技术进步最快都道路!
    getpass.cn
    使用道具 举报 回复
    大佬最近活跃啊 ~
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军装逼团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    7#
    发表于 2018-4-16 17:53:50

    感谢回复哈
    getpass.cn
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册