CVE-2026-49176 Exploit Development: WalletService to SYSTEM • David Carliez skip to content
Dark Theme Open main menu CVE-2026-49176 Exploit Development: WalletService to SYSTEM<br>17 July 2026 / 9 min read
Updated:<br>17 July 2026<br>View more blogs with the tag windows , View more blogs with the tag vulnerability-research , View more blogs with the tag cve , View more blogs with the tag local-privilege-escalation , View more blogs with the tag exploit-development , View more blogs with the tag walletservice
Table of Contents A standard user redirects WalletService to an attacker-seeded ESE database, loads a callback DLL as LocalSystem, then moves the resulting SYSTEM token into the active desktop session.
TL;DR
CVE-2026-49176 is a local privilege-escalation vulnerability in Windows WalletService. The public Wallet API allows a standard user to activate a LocalSystem service. During initialization, WalletService resolves the caller’s FOLDERID_Documents, appends a fixed Wallet component, reverts impersonation, and opens wallet.db with Extensible Storage Engine persisted callbacks enabled.
The exploit proceeds as follows:
standard user
-> create an ESE wallet.db containing a persisted callback
-> redirect the user's Documents known folder to the seeded parent
-> call WalletManager.RequestStoreAsync and GetItemsAsync
-> WalletService opens \Wallet\wallet.db as LocalSystem
-> ESE opens the mandatory Cards table
-> ESE resolves the persisted callback as DLL!Export
-> attacker DLL loads inside the SYSTEM service host
-> SYSTEM token is moved into the active session
-> interactive cmd.exe as NT AUTHORITY\SYSTEM
create an ESE wallet.db containing a persisted callback -> redirect the user's Documents known folder to the seeded parent -> call WalletManager.RequestStoreAsync and GetItemsAsync -> WalletService opens \Wallet\wallet.db as LocalSystem -> ESE opens the mandatory Cards table -> ESE resolves the persisted callback as DLL!Export -> attacker DLL loads inside the SYSTEM service host -> SYSTEM token is moved into the active session -> interactive cmd.exe as NT AUTHORITY\SYSTEM">
Original vulnerability credit: Microsoft credits Chen Le Qi (@cplearns2h4ck) and Mochi Nishimiya (@MochiNishimiya) , both with STAR Labs SG , for reporting CVE-2026-49176. This post covers my independent root-cause analysis and exploit development, not the original discovery.
Microsoft rates the issue Important , with a CVSS 3.1 score of 7.8 . The advisory maps it to CWE-269: Improper Privilege Management and CWE-59: Improper Link Resolution Before File Access .
The WalletService attack surface
WalletService backs the public Windows.ApplicationModel.Wallet WinRT API. An ordinary user can reach the service through the intended public route:
WalletManager.RequestStoreAsync()
-> WalletItemStore.GetItemsAsync()
-> WalletService activation
-> Wallet item manager initialization
-> WalletDatabaseESE::Open
WalletItemStore.GetItemsAsync() -> WalletService activation -> Wallet item manager initialization -> WalletDatabaseESE::Open">
Direct activation of the private Wallet COM class is capability-gated and returned E_ACCESSDENIED from a standard desktop process. That was not a blocker because the public Wallet API performs the supported activation and reaches the same privileged initialization path.
The trust boundary is crossed while the service builds its application-data path. WalletService obtains the caller token, resolves that token’s Documents known folder, then continues after reverting to itself:
CoImpersonateClient
-> OpenThreadToken
-> SHGetKnownFolderPath(FOLDERID_Documents, caller token)
-> CoRevertToSelf
-> append "\Wallet\"
-> open wallet.db as LocalSystem
OpenThreadToken -> SHGetKnownFolderPath(FOLDERID_Documents, caller token) -> CoRevertToSelf -> append "\Wallet\" -> open wallet.db as LocalSystem">
A Windows user can change that user’s Documents mapping with SHSetKnownFolderPath. WalletService then treats the resulting path as trusted service storage, even though the caller controls both the location and any pre-existing Wallet child.
The bug is in that handoff. The service uses caller-derived identity state to select a pathname, then crosses back into LocalSystem before deciding what the object at that pathname may contain.
What the July patch changed
Patch diffing localized the fix to a servicing feature named:
Feature_Servicing_WalletServiceRedirectionGuard
internal feature ID: 3305877819
The fixed build gates several legacy operations:
FunctionFixed behaviorWalletDatabaseESE::OpenReturns before opening the ESE databaseServerUtils::HideAndRestrictFolderSkips the legacy folder restrictionRestrictFolderSkips replacement of the directory DACLFileUtils::DeleteFilesSkips legacy cleanup deletionWalletDatabaseESE::HandleDatabaseCorruptionSkips corruption cleanup<br>The directory is created before the guarded ESE open. That produces a clear patched differential: build 26200.8875 can still...