前言:Packer Fuzzer是一款对Webpack等前端打包工具所构造的网站进行快速、高效安全检测的扫描工具。当我们在Goby中遇到前端打包器所生成的站点时,联动Packer Fuzzer可以自动解析全部JS文件并提取该站点所有API及API参数,从而进行高效漏洞模糊检测。
0x001 最终效果
1.1 插件入口
安装完Packer Fuzzer Goby插件之后,可以在Web检测(Webfinder)的显示框内右侧看到一排“Packer Fuzzer”按钮:
若对应开发端口资产类型为网页,也可以在响应栏目内看见“Packer Fuzzer”按钮:
点击“Packer Fuzzer”,则会通过自带指纹规则检测对应目标站点是否存在对应特征,若不存在则会有如下提示:
点击“OK”开启扫描,当插件检测到对应特征时则会直接开始扫描。无需担心多次点击按钮造成重复扫描的问题,当扫描任务开始之后脚本会生成对应的“文件锁”防止再次执行扫描任务,重复点击时将会看到如下提示:
1.2 生成报告
当扫描结束之后,再次点击对应“Packer Fuzzer”即可查看丰富的API模糊检测报告:
0x002 编写流程
2.1 插件所需变量
首先,我们需要确定一些插件所需的用户设置内容,在package.json文件加入以下三个键值:
"Packer Fuzzer路径": {
"type": "string",
"default": "",
"description": "请输入Packer Fuzzer的路径",
"fromDialog": true
},
"Python3命令名": {
"type": "string",
"default": "python3",
"description": "请输入在当前环境下的Python3的命令,也可以是绝对路径"
},
"扫描模式": {
"type": "string",
"default": "simple",
"description": "可以选择高级模式(adv)或者普通模式(simple), 并不推荐高级模式"
}
- 第一个键值Packer Fuzzer路径很好理解,为用户下载的Packer Fuzzer扫描器存放位置,便于插件能顺利联动;
- 第二个键值Python3命令便是python3在用户系统中的名称,默认为python3。因为有些用户在系统中将python3设置为:py3、python、python3.5或者并未设置相对路径,若我们在开发时直接写死则很容易出现调用失败。并且在笔者测试时(Big Sur Bêta),虽然在终端内使用python3命令调用的为python3.7.7版本,但是在Goby的node.js环境中调用python3则实际使用的是python3.8.2版本,这样会则会因为python3.8.2版本未安装对应的python扩展库导致调用失败:
此时我们可以使用which命令查看终端中python3.7.7版本的安装位置,并将得到的值填入此参数(绝对路径调用)中从使插件能正常调用。
- 第三个键值扫描模式决定了Packer Fuzzer的扫描模式,默认为普通模式(simple),同时扫描器也支持高级模式(adv),但不推荐用户使用高级模式,这将会消耗大量的时间用于提取每个API的具体参数信息(故此版本插件不论此值修改为如何均使用普通模式进行扫描,此设置保留在此单纯因为比较好看)。
2.2 插件入口点
Web检测:使用了与《Xray插件》作者go0p!相同的判断方式,只有符合条件才会显示“Packer Fuzzer”按钮。
首先,资产类型必须为“https”、“http”、“web”三者其一:
let identical = {"web": true,"http": true,"https": true};
接着便会在Packer_Check
中判断,若不符合则会返回false
结果从而实现不显示对应按钮:
goby.registerCommand('Packer_Check', function (content) {
if (identical[content.protocol]) return true;
return false;
});
2.3 设置扫描锁定
点击“Packer Fuzzer”之后,程序会判断扫描锁文件是否存在,若存在则给出提示不进行后续操作:
if (fs.existsSync(lockPath)) {
goby.showInformationMessage("Packer Fuzzer正在扫描中,可以稍后再来看看!");
} else {
......
}
扫描锁文件位于PackerFuzzer项目tmp目录下,命名格式为:当前项目ID + 当前目标IP + 当前目标端口 + 当前资产类型 + lock扩展名:
let lockPath = config["Packer Fuzzer路径"]["default"] + dirB + taskID + '_' + hostIP + '_' + hostPort + '_' + webProtocol + ".lock";
首次开启扫描时,本插件会使用如下命令生成扫描锁文件随后调用扫描函数:
fs.writeFileSync(lockPath,'lock it','utf8');
runScanner(hostIP,hostPort,webProtocol,webURL,taskID);
当然考虑到系统差异,我们对路径中的斜杠做了如下处理:
if (os.type() == 'Windows_NT') {
dirA = '\\reports\\';
dirB = '\\tmp\\';
} else {
dirA = '/reports/';
dirB = '/tmp/';
}
2.4 检测目标特征
点击“Packer Fuzzer”按钮之后程序会判断扫描锁文件是否存在,若存在则给出提示不进行后续操作:
let fingerprint = ['\\u003cnoscript','webpackJsonp','\\u003cscript id=\\"__NEXT_DATA__','webpack-','\\u003cstyle id=\\"gatsby-inlined-css','\\u003cdiv id=\\"___gatsby','\\u003cmeta name=\\"generator\\" content=\\"phoenix','\\u003cmeta name=\\"generator\\" content=\\"Gatsby','\\u003cmeta name=\\"generator\\" content=\\"Docusaurus'];
写指纹并不难,但被识别数据从何而来呢?我们知道Goby在扫描完成之后会保留每一个目标的banner信息,但这个API并非是在官方插件文档内公开的,那么我们便需要自己找出来。首先我们需要在Goby开发版中在控制台对关键操作下断点,之后我们便可以寻找到我们所需要的API详细信息及如何调用:
可以看到getIpInfo需要传入三个参数,第一个是callback函数、第二个是当前任务ID、第三个是当前目标站点IP,之后此函数会将banner信息json格式化之后传入callback函数中调用。在了解了这些之后,我们便可以开始调用此功能:
getIpInfo(dealWebInfo,goby.getTaskId(),hostIP);
dealWebInfo函数的检测功能如下:
function dealWebInfo(result){
taskID = result.data["taskId"];
if (result.data["honeypot"] == 0){
if (identical[result.data["protocols"][content.hostinfo]["protocol"]]){
var flag = 0;
for(var i = 0; i < fingerprint.length; i++){
webJson = result.data["protocols"][content.hostinfo]["json"];
webProtocol = result.data["protocols"][content.hostinfo]["protocol"];
if(webJson.indexOf(fingerprint[i]) != -1){
flag = 1;
}
......
函数首先会检测是否为蜜罐环境,接着便是循环检测指纹是否存在于banner信息中,若存在则将flag的值设置为1。(由于无法检测JS文件的特征,故此功能并不能实现百分百准确检测)。
2.5 开始联动扫描
首先会检测flag的值是否为1,若不为1则显示提示并检测用户选项:
if(flag == 1){
console.log("存在滴");
XXXXX调用执行
} else {
console.log("木有检测到");
var check = confirm("貌似不是打包器所生成的站点,是否继续扫描?");
if (check == true) {
XXXXX调用执行
}
}
接着我们会去检测是否为web资产,若是web资产则直接使用web地址作为目标检测地址;若为IP地址则使用http协议类型 + IP + 端口的方式对检测目标进行拼接:
if (webProtocol == "web"){
targetURL = webURL + "/"; //加一个杠好看些
} else {
if (webProtocol == "http"){
targetURL = "http://" + hostIP + ":" + hostPort + "/";
} else {
targetURL = "https://" + hostIP + ":" + hostPort + "/";
}
}
随后调用node.js的命令执行功能:
var process = require('child_process');
var cmd = 'cd ' + config["Packer Fuzzer路径"]["default"] + ' && ' + config["Python3命令名"]["default"] + ' PackerFuzzer.py -u ' + targetURL + ' -l zh -r html -s ' + taskID + '_' + hostIP + '_' + hostPort + '_' + webProtocol;
process.exec(cmd);
其中命令行详解如下:
cd Packer Fuzzer路径(空格需自己转义) && python3 PackerFuzzer.py -u 目标地址 -l 指定使用中文 -r 只生成html类型报告 -s 开启静默模式+指定报告名称 -t 指定使用普通扫描模式
会先切换到Packer Fuzzer路径下,然后使用“&&”连调引用命令开始进行扫描,一气呵成无需考虑系统差异。在WinNT或者其他内核之下均可正常执行。
2.6 生成扫描报告
和扫描锁文件命名方式类似,其报告位于Packer Fuzzer路径reports目录下:
let reportPath = config["Packer Fuzzer路径"]["default"] + dirA + taskID + '_' + hostIP + '_' + hostPort + '_' + webProtocol + ".html";
和扫描锁文件命名方式类似,其报告位于Packer Fuzzer路径reports目录下:
if (fs.existsSync(reportPath)) {
goby.showIframeDia(reportPath, "Packer Fuzzer 扫描报告", "1777", "1777");
}
注:由于Goby限制,无法将iframe页面放至最大。
0x003 插件小结
第一次开发node.js的插件,发现坑还是挺多的,除了上面遇到的python环境问题,我还遇到了因为非I/O阻塞及callback无法取出返回值导致函数无法及时获取return内容的问题。此外由于懒得重启Goby所以一直在控制台的Sources中修改脚本并测试,但还是会出现突然卡住的情况,换句话说基本上是在面向控制台开发。
插件目前还并不够完善,计划下一版的改进内容如下:1. 根据Goby当前语言生成对应语言的报告;2. 此版本未设置获取连调运行异常状态,脚本只通过判断是否存在报告来判断扫描是否结束。若在扫描过程中异常退出,则会永远停在提示页面无法重新检测等(当然可以手动删除扫描锁文件及查看Packer Fuzzer自带的日志文件);3. 加入更多的扫描参数。
最后笔者代表Packer Fuzzer团队感谢您使用本插件及Packer Fuzzer扫描器。
Merci le destin de nous rencontrer, le moment que je t’ai rencontré est le moment le plus chanceux de ma vie. Il me faut apprécier à vous, la cerise qui peut me connaître par âme.
插件开发文档及Goby开发版下载:
https://gobies.org/docs.html
Packer Fuzzer项目地址:
https://github.com/rtcatc/Packer-Fuzzer文章来自Goby社区成员:PoC Sir,转载请注明出处。
下载Goby内测版,请关注微信公众号:GobySec
下载Goby正式版,请关注网址:https://gobies.org