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\UserProfileRoamingfolder and theLatest.datfile. - Create
%USERPROFILE%\Foowith a recent dummyLatest.datfile as bait. - After restart and login you will have ~1 minute to do the next step.
- Use
BaitAndSwitch.exewithFILE_SHARE_WRITEto create a pseudo-symlink toC:\Windows\System32\foobar.dll. - Wait a few seconds for the
VaultSvcservice to complete the initialization process. - The
foobar.dllfile has been created in the protectedC:\Windows\System32folder.
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.

Exploitation flow in Process Monitor
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).

Console output of the PoC exploit
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.

Patch diff of RaisePendingNotificationsForAllUsers()
Timeline
⬅️ 2020-02-03: Reported issue to MSRC.
➡️ 2020-02-03: MSRC opened case 56348.
➡️ 2020-05-12: Coordinated public release of advisory.