
VFS for Git ab95cf51 - Medium - Local - Privilege leftover - service pipe trusts JSON OwnerSID
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vfs-service-sid
Class: Privilege leftover Reach: Local
VFS for Git master ab95cf51 (tag v2.0.26259.1) - Microsoft
GVFS.Service runs as LocalSystem. WindowsPlatform.CreatePipeByName grants BUILTIN\Users ReadWrite | CreateNewInstance on GVFS.Service.pipe. RegisterRepoHandler.Run stores the JSON OwnerSID as-is. Nothing calls ImpersonateNamedPipeClient.
Any Users principal can register an attacker path under a victim SID. On SessionLogon the service CreateProcessAsUsers gvfs mount <attacker path> as the victim. The child is not NT AUTHORITY\SYSTEM.
| ID | no CVE yet |
| Class | Privilege leftover (missing authorization / cross-user execution) |
| Reach | Local |
| CWE | CWE-862, CWE-284 |
| CVSS | Medium: 6.6 CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N |
| Product | VFS for Git |
| Affected | master ab95cf51d6ffd0ee0e2feae7180078fe9ecbbb3a; GitHub tags v2.0.26229.1 / v2.0.26259.1. Windows only. |
| Auth | Local. Any BUILTIN\Users principal. Victim logon for automount. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only. how=gadget (no live Windows GVFS.Service). |
Git 2.35+ dubious-ownership may then fail git inside an attacker-owned tree. The process still starts as the victim. If the path is victim-owned but attacker-writable (vfs-clone-acl), mount git, credential.helper, and hooks run as that user.
Any BUILTIN\Users principal on a machine where the service is installed. Connect to \\.\pipe\GVFS.Service.pipe. No extra privilege.
| Privilege | Method / sink | What it grants | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|---|
| List every active enlistment root | GetActiveRepoListRequest with no SID check (TryGetActiveRepos, not GetActiveReposForUser) | Paths used as pipe-name oracle for leftover #2 | Recon | Lab-proven. Gadget listed /lab/repos/victim |
| Deactivate a victim enlistment so it does not automount | UnregisterRepoRequest {EnlistmentRoot} with no SID check (TryDeactivateRepo) | Victim repos stay unmounted | Availability | Lab-proven. Victim row gone from the active list |
| Persist attacker path under victim SID | RegisterRepoRequest JSON EnlistmentRoot + OwnerSID; TryRegisterRepo stores both | Registry row attacker-path + victim SID | Integrity | Lab-proven. Registry row /lab/repos/attacker + S-1-5-21-1000-2000-3000-1001 |
Start GVFS.exe mount <attacker path> as the victim on logon | OnSessionChange SessionLogon -> AutoMountRepos(userId) -> TryRunAs / CreateProcessAsUser | Cross-user process create as the logon user | Integrity | Lab-proven on the gadget. Spawn parent GVFS.Service, argv mount /lab/repos/attacker, uid 1002 victim, witness written |
| Run as NT AUTHORITY\SYSTEM | Same automount | LocalSystem child | Integrity | Closed. Child whoami is the victim, not SYSTEM (child-not-system=yes) |
Git / hook / credential.helper as the victim inside an attacker-owned tree | Process create still happens; git 2.35+ safe.directory may fail | Hook RCE as victim if the tree is victim-owned but attacker-writable | Integrity | Inferred. Lab did not run Microsoft git. Bind the path as victim-owned but attacker-writable for hook RCE |
Closed on this pin: remote unauthenticated access (named pipe is local). SYSTEM shell. Default pipe ACL still lets Users in; a patched register that binds the client SID (gadget X-Patched: 1) does not automount the attacker path for the victim.
Same pin leftovers: vfs-quote-breakout, vfs-mount-dehydrate, vfs-clone-acl, vfs-hook-impersonate, vfs-install-cwd.
I pinned VFSForGit master ab95cf51 after the 2026-09-28 merge. The service pipe ACL is Users ReadWrite. Register stores JSON OwnerSID. SessionLogon automounts as that SID. Nothing impersonates the pipe client.
Wrong turns already recorded: calling this a SYSTEM shell (automount is CreateProcessAsUser of the logon token); treating git safe.directory as authorization of the IPC; a reverse shell. Theatre. Native Windows was missing, so the lab is a gadget of the pin handlers plus a Unix TCP oracle.
python3 vfs-service-sid-Abraxas-Labs.py
or
cd lab
./run.sh
Gadget HTTP on 127.0.0.1:19660. Witness VFS-SERVICE-SID-WITNESS. Last line uses how=gadget.
SUCCESS vfs-service-sid how=gadget register-forged-sid=yes automount-as-victim=yes child-not-system=yes VFS-SERVICE-SID-WITNESS
Impersonate the named-pipe client (ImpersonateNamedPipeClient / NamedPipeServerStream.RunAsClient). Ignore JSON OwnerSID; bind TryRegisterRepo to the client SID. Restrict CreatePipeByName to the interactive user and LocalSystem (drop BuiltinUsersSid ReadWrite / CreateNewInstance). GetActiveRepoList should return only that client's repos. UnregisterRepo should require the same SID match.
ab95cf51d6ffd0ee0e2feae7180078fe9ecbbb3aWindowsPlatform.cs Users ACL and {serviceName}.pipeRegisterRepoHandler.csNamedPipeMessages.cs client-supplied SID fieldRepoRegistry.cs persist and AutoMountReposGVFSMountProcess.cs TryRunAs(..., "mount", repoRoot, ...)GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.