Windows WalletService Known-Folder Redirection → ESE Persisted-Callback DLL Load Local Privilege Escalation (CVE-2026-49176)
Published: 2026-07-27 • Researcher: DavidCarliez
- Severity
- High
- CVE
- CVE-2026-49176
- Category
- binary
- Affected product
- Windows WalletService (Windows.ApplicationModel.Wallet WinRT API, backed by an ESE/Jet Blue database under the caller's Documents\Wallet folder)
- Affected versions
- Windows 11 builds prior to 26200.8875; PoC developed and tested against vulnerable build **26200.8737** (25H2)
- Disclosed
- 2026-07-27
- Patch status
- Patched
Tags
References
Archive entry
intelseclab/poc-archiveOn this page
Metadata
| Field | Value |
|---|---|
| Date Added | 2026-07-27 |
| Last Updated | 2026-07-27 |
| Author / Researcher | DavidCarliez |
| CVE / Advisory | CVE-2026-49176 |
| Category | binary |
| Severity | High |
| CVSS Score | 7.8 (CVSS 3.1, AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Status | Weaponized — SYSTEM shell confirmed against a real, vulnerable Windows 11 build |
| Tags | windows, walletservice, lpe, privilege-escalation, ese, extensible-storage-engine, known-folder-redirection, persisted-callback, local |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Windows WalletService (Windows.ApplicationModel.Wallet WinRT API, backed by an ESE/Jet Blue database under the caller’s Documents\Wallet folder) |
| Versions Affected | Windows 11 builds prior to 26200.8875; PoC developed and tested against vulnerable build 26200.8737 (25H2) |
| Language / Platform | C (Win32 API, ESENT/Jet Blue API), PowerShell, Windows x64 |
| Authentication Required | Yes — requires a local, low-privilege (“PR:L”) authenticated session |
| Network Access Required | Local only |
Summary
Windows WalletService — which runs as LocalSystem — resolves the caller’s FOLDERID_Documents known folder while impersonating the calling user, then reverts to the LocalSystem token before opening <Documents>\Wallet\wallet.db. Because the folder resolution happens under impersonation but the file access happens as LocalSystem, a standard user can redirect their own Documents known folder (via SHSetKnownFolderPath, CWE-59 Link Following) to a directory containing an attacker-seeded ESE database. That database defines a Cards table column with JET_bitColumnUserDefinedDefault (an ESE “persisted callback”) pointing at an attacker-controlled DLL. When WalletService opens the table with ESE persisted callbacks enabled, the Jet Blue engine loads and invokes the attacker’s DLL — as LocalSystem. The DLL then creates and starts a throwaway Windows service whose binary spawns cmd.exe in the active console session via CreateProcessAsUserW, yielding a nt authority\system shell as a fully unprivileged local user. Microsoft fixed this in build 26200.8875 (CWE-59 + CWE-269).
Vulnerability Details
Root Cause
CWE-59 (Link Following) chained into CWE-269 (Improper Privilege Management). WalletService resolves FOLDERID_Documents for the calling user while impersonating that user’s token, then drops impersonation and reverts to its own LocalSystem token before it actually opens <Documents>\Wallet\wallet.db on disk. This split between “resolve path as the user” and “open file as SYSTEM” is exactly the well-known known-folder-redirection TOCTOU/link-following pattern: because SHSetKnownFolderPath lets any standard user repoint their own Documents known folder to an arbitrary attacker-writable directory, the service can be made to open a database it never expected — one the low-privilege caller fully controls.
Compounding this, WalletService accepts the caller-created/caller-controlled wallet.db as-is and opens it with JET_paramEnablePersistedCallbacks enabled. ESE (the Extensible Storage Engine / “Jet Blue”, also used by AD, Exchange, and Windows Search) supports “user-defined default” column callbacks (JET_bitColumnUserDefinedDefault) whose JET_USERDEFINEDDEFAULT_A.szCallback field is a "path\to.dll!ExportName" string stored directly in the database schema. When the Cards table is opened and persisted callbacks are enabled, the ESE engine LoadLibrarys that DLL and invokes the named export — in the caller’s chosen process, but under whatever token that process is running as. Since WalletService opens the table as LocalSystem, the attacker’s DLL executes as LocalSystem, satisfying CWE-269 (a component intended to run at a lower/isolated privilege boundary ends up executing arbitrary attacker code at its own elevated privilege).
Attack Vector
- As a standard, non-elevated local user, seed a fresh ESE database (
wallet.db) whoseCardstable has aJET_bitColumnUserDefinedDefaultcolumn callback pointing at an attacker-controlled DLL export (wallet_callback_shell.dll!WalletCallback), created withJET_paramEnablePersistedCallbacksenabled (src/wallet_ese_seed.c). - Redirect the calling user’s
FOLDERID_Documentsknown folder viaSHSetKnownFolderPathto the directory containing the seededWallet\wallet.db(src/documents_path.c). - From the same user context, invoke the public WinRT Wallet API —
Windows.ApplicationModel.Wallet.WalletManager.RequestStoreAsync()followed byWalletItemStore.GetItemsAsync()— which causesWalletService(running asLocalSystem) to resolveFOLDERID_Documents(now redirected), open the attacker’swallet.db, and access theCardstable with persisted callbacks enabled. - ESE loads the attacker’s DLL as
LocalSystemand invokes theWalletCallbackexport (src/wallet_callback_shell.c), which spins up a background thread that verifies it is running asLocalSystem, then creates and starts a short-lived Windows service (SERVICE_DEMAND_START) whose binary isshell_broker.exe. shell_broker.exe(src/shell_broker.c) runs asLocalSystem, duplicates/adjusts its own token (enablingSeAssignPrimaryTokenPrivilege,SeIncreaseQuotaPrivilege,SeTcbPrivilege), retargets the token’s session ID to the active console session, and callsCreateProcessAsUserWto spawncmd.exein the interactive desktop (winsta0\default) of the currently logged-in session — producing a visiblent authority\systemcommand prompt.- Restore the original
Documentsknown folder path (the PoC does this automatically in afinallyblock).
Impact
Local privilege escalation from a standard/unprivileged authenticated user directly to LocalSystem (SYSTEM), the highest privilege level on Windows. Full host compromise: arbitrary code execution as SYSTEM, ability to disable security tooling, dump credentials/LSA secrets, install persistence, and pivot further.
Environment / Lab Setup
OS: Windows 11 25H2, vulnerable build 26200.8737 (fixed in 26200.8875)
Target: Local machine — WalletService (LocalSystem), reachable via public WinRT
Windows.ApplicationModel.Wallet API from any standard user session
Attacker: Non-elevated local standard-user PowerShell session
Tools: Visual Studio C++ build tools (cl.exe, esent.lib, shell32.lib, ole32.lib,
uuid.lib, advapi32.lib, userenv.lib, wtsapi32.lib), PowerShell 5.1+Setup Steps
| |
Proof of Concept
Step-by-Step Reproduction
Build — compile the four native components with
build.ps1(invokescl.exeundervcvars64.bat, linksesent.lib/shell32.lib/ole32.lib/uuid.lib/advapi32.lib/userenv.lib/wtsapi32.lib).PowerShell1powershell.exe -ExecutionPolicy Bypass -File .\build.ps1Trigger — from a non-elevated PowerShell, run
trigger.ps1. It refuses to run if already elevated, stages a fresh run directory under%LOCALAPPDATA%\CVE-2026-49176-SHELL\<guid>, copies the callback DLL and broker EXE into apayloadsubfolder, seedsWallet\wallet.dbwith the persisted-callback column viawallet_ese_seed.exe, redirectsDocumentsto the run directory viadocuments_path.exe set, and then calls the real WinRTWalletManager.RequestStoreAsync()/WalletItemStore.GetItemsAsync()to make WalletService open the seeded database.PowerShell1powershell.exe -ExecutionPolicy Bypass -File .\trigger.ps1Alternatively, double-click
run.cmd.Observe — WalletService (SYSTEM) loads
wallet_callback_shell.dllvia ESE’s persisted-callback mechanism, which registers and starts theshell_broker.exeservice; the broker spawnscmd.exeas SYSTEM in the active console session.trigger.ps1polls aresult.txtevent log in the payload folder for aSYSTEM_SHELL_STARTED/SYSTEM_SHELL_FAILEDrecord and restores the originalDocumentspath in afinallyblock regardless of outcome.
Exploit Code
See
src/wallet_ese_seed.c,src/wallet_callback_shell.c,src/shell_broker.c,src/documents_path.c,trigger.ps1,build.ps1, andrun.cmd(all unmodified from upstream) in this folder. See alsoupstream-README.md.
| |
| |
| |
| |
Expected Output
[*] User: <machine>\<standarduser>
[+] SYSTEM shell started: PID 4812, session 1
[+] Result: event=SYSTEM_SHELL_STARTED broker_pid=4820 child_pid=4812 session=1 error=0 user=SYSTEM
[*] Run directory: C:\Users\<standarduser>\AppData\Local\CVE-2026-49176-SHELL\<guid>A cmd.exe window titled “CVE-2026-49176 SYSTEM Shell” opens on the active desktop, and whoami inside it prints:
nt authority\systemScreenshots / Evidence
- Not included in the upstream repository. Reproduce in an isolated, authorized VM snapshot of the vulnerable build (26200.8737) to capture evidence.
Detection & Indicators of Compromise
SIEM / IDS Rule (example):
Detect sequence: standard-user SHSetKnownFolderPath(FOLDERID_Documents) ->
WalletManager.RequestStoreAsync()/GetItemsAsync() from same user session ->
svchost.exe (WalletService) loads unsigned/user-writable-path DLL ->
new demand-start service created+started+deleted within seconds ->
cmd.exe spawned as SYSTEM in interactive sessionRemediation
| Action | Detail |
|---|---|
| Patch | Apply Microsoft’s fix — update to Windows 11 build 26200.8875 or later. |
| Workaround | Restrict/monitor use of SHSetKnownFolderPath/SetKnownFolderPath for known folders on multi-user or sensitive endpoints; monitor for anomalous ESE persisted-callback DLL loads by system services. |
| Config Hardening | Enforce application allowlisting (WDAC/AppLocker) to block unsigned DLL loads by system service processes; audit for ephemeral service creation events (created, started, and deleted within seconds) as a general SYSTEM-shell-broker detection pattern applicable beyond this specific CVE. |
References
Notes
Verified before ingestion: all 4 C source files (wallet_ese_seed.c, wallet_callback_shell.c, shell_broker.c, documents_path.c) plus trigger.ps1/build.ps1/run.cmd were read directly in full and confirmed as genuine, compilable Win32/ESE code implementing the documented known-folder-redirection + persisted-callback DLL-load chain end to end — real ESENT API usage (JetCreateInstanceA, JetSetSystemParameterA(JET_paramEnablePersistedCallbacks), JetAddColumnA with JET_bitColumnUserDefinedDefault), real SHSetKnownFolderPath/SHGetKnownFolderPath known-folder redirection, a real WinRT WalletManager.RequestStoreAsync()/GetItemsAsync() trigger from PowerShell, and a real SYSTEM-token broker using CreateProcessAsUserW with session-ID retargeting. No stubs, no obfuscation, no unrelated network calls or destructive default behavior.
Author DavidCarliez is a credible, prolific July-2026 Windows LPE researcher with a consistent track record across multiple repos in this space — also authored the CVE-2026-54992 MSMQ entry already present in this archive — and publishes a matching personal technical blog write-up for each CVE (davidcarliez.github.io), which is consistent with genuine, tested exploitation work rather than a templated or copy-pasted PoC.
CVSS 7.8 (CVSS 3.1, AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H), CWE-59 (Link Following) + CWE-269 (Improper Privilege Management) per NVD/MSRC. Fix landed in Windows 11 build 26200.8875; this PoC was tested against the last vulnerable build, 26200.8737.
| |