abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vfs-mount-dehydrate
类别: 权限残留 影响范围: 本地
VFS for Git master ab95cf51(标签 v2.0.26259.1)- Microsoft
CreatePipeByName 在每个 enlistment 的挂载管道上授予 BUILTIN\Users ReadWrite | CreateNewInstance。没有所有者 SID 检查,也没有 ImpersonateNamedPipeClient。HandleDehydrateFolders 用 Path.Combine 拼接攻击者提供的文件夹名,并通过 ProcessStartInfo.Arguments 传给 git。CLI 的 dehydrate 路径会执行 IsFolderValid。挂载处理器不会。
Users 组中任何能猜出 \\.\pipe\GVFS_<ENLISTMENT> 的人都可以卸载 ProjFS、以挂载用户身份移动目录,并强制执行 git reset HEAD --hard。
| ID | 暂无 CVE |
| 类别 | 权限残留(命名管道 ACL)/ 路径遍历 / 参数注入 |
| 影响范围 | 本地 |
| CWE | CWE-284、CWE-22、CWE-88 |
| CVSS | 高危:7.8 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| 产品 | VFS for Git |
| 受影响版本 | master ab95cf51d6ffd0ee0e2feae7180078fe9ecbbb3a;安装程序标签 v2.0.26229.1 / v2.0.26259.1。仅限 Windows。 |
| 认证 | 本地。受害者存在挂载时,任意 BUILTIN\Users 主体 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1。how=gadget(无 Windows VM / 实时 ProjFS)。 |
管道协议:UTF-8 编码的 Header 或 Header|body,以字节 0x03 终止。管道名称为 GVFS_ 加上大写化的 enlistment 根路径,其中 : 替换为 _(例如 C:\Repos\Foo -> \\.\pipe\GVFS_C_\REPOS\FOO)。
受害者在已知根路径上执行了 gvfs mount。攻击者是同一台机器上的另一个 Users 主体。以受害者身份获得完整性和可用性影响。不是 SYSTEM。本身不是 shell。
| 权限 | 方法 / 汇聚点 | 授予的能力 | 侦察 / 完整性 / 可用性 | 实验验证或推断 |
|---|---|---|---|---|
| 连接到受害者挂载管道 | CreatePipeByName ACL BuiltinUsers ReadWrite | CreateNewInstance;无所有者检查 | 与挂载 IPC 通信 | 对管道进行侦察 | 实验验证(gadget ACL)。攻击者 SID 被接受;仅所有者模式返回连接被拒绝 |
| 读取 enlistment 路径、缓存路径、RepoUrl | GetStatus | 泄露用于残留 #1 / #4 的根路径 | 侦察 | 实验验证。状态 JSON 包含 RepoUrl 和 GVFS_C_\REPOS\FOO |
卸载 ProjFS 并退出 gvfs.mount | 消息 Unmount | 使 enlistment 下线 | 可用性 | 实验验证。回复 Ack 然后 Complete;挂载状态为 Unmounted |
| 解析失败时卸载 | ReleaseLock 空 body -> RequestData == null -> UnmountAndStopWorkingDirectoryCallbacks + Environment.Exit | 同样的卸载加上进程退出 | 可用性 | 实验验证。空的 ReleaseLock| 触发了卸载 |
| 丢弃受害者工作树 | Dehydrate|{"Folders":"--hard",...}。即使源目录不存在也会执行 movedFolders.Add。Reset("HEAD", "\"--hard\" ") 在去除引号后变成 git reset HEAD --hard | 销毁已修改的已跟踪文件 | 完整性 / 可用性 | 实验验证。已修改的已跟踪文件丢失了见证字符串 |
| 以受害者身份将文件夹移出工作树 | Path.Combine(WorkingDirectoryBackingRoot, folder) + MoveDirectory 到 BackupFolderPath | 窃取 / 转移工作树文件夹 | 完整性 | 实验验证。keepme 离开了源目录并出现在攻击者备份路径下 |
遍历 ..(未使用 CLI 的 IsFolderValid) | 同样的 Combine;..\secret | 移动挂载用户可写的同级目录 | 完整性 | 实验验证。同级目录 secret 落到了攻击者备份路径下。应用 IsFolderValid 可阻止 |
| 窃取或销毁其他受害者可写目录 | 同样的移动,使用解析到 enlistment 之外的 ..\ 链 | 以挂载用户身份进行更广泛的移动 | 完整性 | 从 Combine 推断。本实验仅使用一次性目录树 |
| 以受害者身份执行代码 | 此管道 | 创建攻击者镜像的进程 | 完整性 | 已排除。此残留无法创建攻击者镜像的进程(参见 vfs-service-sid 和 vfs-hook-impersonate) |
| SYSTEM | 挂载 IPC | LocalSystem | 完整性 | 已排除。挂载以 enlistment 用户身份运行 |
--hard 不是 .. 路径。CLI 的 IsFolderValid 仍然允许它。修复还必须拒绝类似选项的文件夹名。
同一 pin 的其他残留:vfs-quote-breakout、vfs-clone-acl、vfs-service-sid、vfs-hook-impersonate、vfs-install-cwd。
我在 2026-09-28 合并后 pin 了 VFSForGit master ab95cf51。服务管道和每个 enlistment 的挂载管道共用 CreatePipeByName。CLI 上的 dehydrate 会验证文件夹。挂载处理器不会。
已记录的弯路:将 --hard 视为路径遍历(它实际上是去除引号后的参数注入);声称可获取 SYSTEM;反弹 shell。都是表演。缺少原生 Windows,因此实验环境是这些函数的 gadget 加上一个一次性 git 树。
python3 vfs-mount-dehydrate-Abraxas-Labs.py
或
cd lab
./run.sh
Gadget HTTP 监听 127.0.0.1:19640,使用管道消息格式。容器内的一次性 git 树。无反弹 shell。无真实配置文件路径。最后一行使用 how=gadget。
SUCCESS vfs-mount-dehydrate how=gadget unmount=yes reset-hard=yes dirty-gone=yes move-inside=yes move-dotdot=yes pipe-acl=users-no-owner is-folder-valid-not-on-pipe=yes VFS-MOUNT-DEHYDRATE-WITNESS
将 CreatePipeByName 限制为 enlistment 所有者 SID 加上 LocalSystem。移除 Users 的 CreateNewInstance。模拟客户端并要求所有者身份。在 HandleDehydrateFolders 中复制 IsFolderValid,并拒绝看起来像 git 选项的名称。不要将 ReleaseLock 解析失败视为进程退出。
ab95cf51d6ffd0ee0e2feae7180078fe9ecbbb3aWindowsPlatform.cs CreatePipeByNameInProcessMount.cs dehydrate / unmount / ReleaseLockDehydrateVerb.cs IsFolderValidGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。无担保。