Packer-Fuzzer漏扫工具RCE 0day(当前已被官方修复)

 

0x00关于本文

今天朋友丢我一个授权测试的站,那个站用vue写的前端,因此前端js包含了所有的接口。
而另一方面整个前端就一个登录框堵在那儿,因此翻js几乎成了唯一的思路。
翻着翻着,我烦了,于是估摸着网上已经有前辈们写出了专门针对webpack的扫描工具,一搜就搜到了Packer-Fuzzer(https://github.com/rtcatc/Packer-Fuzzer
当我浏览这个工具的介绍的时候,我注意到了这句话
“本工具将会通过PyExecJS运行原生NodeJS代码,故我们推荐您安装NodeJS环境”


看到这句话我一下就警觉起来,毕竟nodejs是可以执行命令的,而相关的安全问题已经让蚁剑翻过车了(https://xz.aliyun.com/t/8167),于是一瞬改变目标开始研究这个工具,并且挖掘出了RCE

当前漏洞已经被官方修复,并且奖励800(由于我Twitter上提前公开了漏洞导致高危->中危)
本文将会从分析者的角度分析漏洞

0x01定位js执行点

简单搜索execjs就可以找到执行点,位置在Recoversplit.py的57行

0x02 防护绕过

作者在写这个代码的时候也意识到了被反打的可能性(“防止黑吃黑被命令执行”),所以当js出现了exec和spawn的时候就不会执行,但是这样的简单的防护几乎是没有用的。
且不说nodejs沙箱逃逸已经被师傅们玩出花来了,单是这里eval没有过滤掉就可以通过字符串拼接或者url编码的方式绕过这个限制
比如说这里就用了url编码了能弹出计算器的payload,解码并eval之后就能弹出计算器

0x03 调用分析

那么这个执行点是怎么被调用到的呢
看了下代码,发现执行点在RecoverSplit类的jsCodeCompile里,这个函数被同一个类的checkCodeSpilting调用,而checkCodeSpilting又被这个类的recoverStart调用,recoverStart被Project类的parseStart调用,而parseStart,而再往前追溯就是命令参数处理之类乱七八糟的地方了
知道了这些东西之后,我们就可以根据它的调用一步一步走,一点一点写出RCE的POC了

0x04 如何进入recoverStart函数?让程序相信这个网站使用了webpack

我们看到这个parseStart长这样
发现如果想要让程序调用recoverStart,就需要让前面的checkStart返回1或者777,这里的checkStart的意思是检查网站是否真的用了webpack技术,如果真的用了才继续扫描下去

checkStart调用了checkHTML,如果能让checkHTML返回1就可以让checkStart返回1
跟过去看一下
发现如果返回的html包含了fingerprint_html的某一项就返回1


在fingerprint_html中看到了这些片段,可以知道,只需要在html里面包含其中的任意一个片段就可以让扫描器相信网站使用了webpack技术
因此在POC的html中,我加入了<noscript>naive!</noscript>来骗过扫描器

0x04 如何进入checkCodeSpilting函数?

recoverStart用于处理js(而不是html了)

这里并没有加入什么恼人的判断,那个if也只是判断文件后缀名不为db。推测扫描器是先把js下到本地然后再读取的,因此在构造POC的时候,通过script src引入一个js,就可以让它进入到这个函数

0x04 如何进入jsCodeCompile函数并把我们想要的东西传入?

checkCodeSpilting会读取文件,判断是否包含了document.createElement("script");这个字符串(以检查是否有异步加载的js代码),如果是的话再做一个正则匹配,然后把值加一个前缀一个后缀之后传入jsCodeCompile函数
想要成功调用jsCodeCompile函数,在js中就得加入document.createElement("script");,而想要传我们想传的参数进去就得研究这个正则了。
我比较菜,对正则一直心怀恐惧,但是好在这个正则也不难懂,首先匹配一个字母或者数字或者下划线(\w),在匹配一个点和一个字母p以及加号(\.p+),接着就是匹配到的内容,正则会不断匹配知道遇到一个.和一个字母j以及字母s(\.js)
因此实际上就是 这个正则就是匹配如下内容
【随便一个字母数字下划线】p【我们想让他匹配的内容】.js
匹配完了之后,前面加个",后面加个.js,变成jsCode传入jsCodeCompile

0x04 如何在jsCodeCompile函数中实现RCE?

这个函数是最后也是最重要的函数,它相对复杂,因此需要分几个步骤分析
首先看到这个js执行的地方,它进行了“防止黑吃黑命令执行”的检查之后,首先会去编译这个js,接着从nameList里面取东西然后传入到js中的js_compile函数里。
因此我们要做的就是两件事情,一个是让jsCodeFunc里面变成我们的RCE代码,第二个是让nameList里面有东西可以传进去

首先我们来出了让jsCodeFunc有RCE代码的问题
jsCodeFunc的生成过程如下
首先从jsCode中正则匹配出被[]包裹着的第一个内容,作为js_compile函数的参数,然后jsCode本身再被插入进去赋值给作为js_url,看起来工具的作者是希望能够动态解析js以获取url地址
因此为了让里面能够接收一个参数,需要直接在jsCode里面加入一个[s],匹配到之后就会让variable为字母s,这样前面的部分就是js_compile(s),解决了前面传入参数的问题
接着就要处理如何加入了jsCode之后能执行恶意代码的问题了。
刚才的分析结果表明传入的内容前面被加了个"后面被加了个.js,而且我们还要在传入内容加一个[s],并且加完了这一堆东西之后还不能有问题。
虽然看起来条件苛刻,实际上处理起来也不复杂,针对前面的",我们再加一个"然后来个;来结束语句即可。接着加入RCE语句,最后为了对付后面的.js和[s],直接用//注释掉
由于后面拼接的return js_url}前面有个\n,因此注释对其不起作用,因此我们不需要自己return一个值然后用大括号闭合

接着我们来处理如何让nameList有值的问题

发现nameList就是匹配了两个正则表达式之后加进来的,因此随便加一个能让某个表达式匹配到内容的字符串就可以了,这里我加的是{114514:,会让第一个正则表达式匹配到114514,并且加入到nameList中
我们把{114514:加入到刚才写的语句的注释中就可以了
以上就是payload的完整生成过程

0x05 RCE展示&POC



0x06修复

个人认为单纯的过滤没法在根本上解决问题,可能需要找一个安全的JS执行方式

评论

此博客中的热门博文

局域网监控软件WFilter ICF 鸡肋0day RCE漏洞挖掘

别想偷我源码:通用的针对源码泄露利用程序的反制(常见工具集体沦陷)

复现基于eBPF实现的Docker逃逸