Arbitrary file write in VaultSvc
TL;DR
A denial of service vulnerability (CVE-2020-1076) exists when the Credential Manager (VaultSvc
) improperly handles symbolic links resulting in a low privileged user being able to write arbitrary files.
Description
The VaultSvc
service binary is lsass.exe
and the service itself is implemented in the vaultsvc.dll
file. The service runs in the context of the local SYSTEM
account and it is auto-started during system boot to initialize the credential vault. The initialization creates the %LOCALAPPDATA%\Microsoft\Vault\UserProfileRoaming
directory and the Latest.dat
file using impersonation. The below is the relevant excerpt of the stack related to the CreateFile
operation during initialization that happens right after the user logged in.
1ServiceMain+7c8d CVaultRoamingNotification::RaiseNotification2
2ServiceMain+7bb3 CUserVaultMgr::Initialize
3ServiceMain+60ff CVaultMgr::GetUserVaultManager
4ServiceMain+8ffa VltOpenVault
I have found that after the initialization process the VaultSvc
service will call CVaultRoamingNotification::RaiseNotification2
again, this time without impersonating the user. The below is the relevant excerpt of the stack related to the CreateFile
operation after the initialization process.
1ServiceMain+7c8d CVaultRoamingNotification::RaiseNotification2
2ServiceMain+d67c CVaultRoamingNotification::RaisePendingNotifications
3ServiceMain+cdc7 CVaultMgr::RaisePendingNotificationsForAllUsers
The fact that CVaultRoamingNotification::RaiseNotification2
is called without impersonation means that there is a TOCTOU vulnerability here: by setting up a pseudo-symlink and placing an oplock on the target of the symlink, we can change the symlink when the target file is opened and make it point to another target file. That is, replacing the Latest.dat
file with a symbolic link after the initialization completed, but before the notifications are raised, allows a low privileged user to create or overwrite an arbitrary file. The BaitAndSwitch
tool in James Forshaw’s Symbolic Link Testing Tools implements this technique.
Note that the CVaultRoamingNotification::RaiseNotification2
function will check the time of the last modification and will only write the file if sufficient time has passed. The below is the relevant excerpt of the pseudocode.
1if (GetFileTime(hFile, 0, 0, &LastWriteTime))
2{
3 GetSystemTimeAsFileTime(&SystemTimeAsFileTime);
4 if (CompareFileTime(&SystemTimeAsFileTime, &LastWriteTime) <= 0 ||
5 ((0x0D6BF94D5E57A42BD * (*&SystemTimeAsFileTime - *&LastWriteTime) >> 64) >> 23) <= v7)
6 {
7 [...]
8 }
9}
Steps to reproduce
- Delete the
%LOCALAPPDATA%\Microsoft\Vault\UserProfileRoaming
folder and theLatest.dat
file. - Create
%USERPROFILE%\Foo
with a recent dummyLatest.dat
file as bait. - After restart and login you will have ~1 minute to do the next step.
- Use
BaitAndSwitch.exe
withFILE_SHARE_WRITE
to create a pseudo-symlink toC:\Windows\System32\foobar.dll
. - Wait a few seconds for the
VaultSvc
service to complete the initialization process. - The
foobar.dll
file has been created in the protectedC:\Windows\System32
folder.
Again, the last modification time of the dummy Latest.dat
file matters. If the dummy file is too old the service will overwrite it during the first stage and will not (or just much later) trigger the second stage.
PoC
Using the BaitAndSwitch.exe tool created by James Forshaw, I have executed the below command to create the pseudo-symlink.
BaitAndSwitch.exe %LOCALAPPDATA%\Microsoft\Vault\UserProfileRoaming\Latest.dat %USERPROFILE%\Foo\Latest.dat C:\Windows\System32\foobar.dll w
The below screenshot shows the events related to lsass.exe
captured by Process Monitor. We can see the lsass.exe
process catching the bait and checking the dummy Latest.dat
while impersonating as WINDEV1912EVAL\User
. Note that the time of testing was 2020. 02. 03. 00:11
and the last modification time of my dummy file was 2020. 02. 02. 02:17
. After ~1 minute the first stage completed the VaultSvc
service is back again, this time operating on the target file without impersonation in the context of the local SYSTEM
account.
The below screenshot shows the console output of the PoC exploit tested on a virtual machine running Windows 10, version 1909 (10.0.18363.418).
Fix
As usual, this vulnerability was also fixed by impersonating the logged on user in the RaisePendingNotificationsForAllUsers()
function. The below screenshot shows the patch diff of the affected function.
Timeline
⬅️ 2020-02-03: Reported issue to MSRC.
➡️ 2020-02-03: MSRC opened case 56348.
➡️ 2020-05-12: Coordinated public release of advisory.