CVE-2020-0863 - An Arbitrary File Read Vulnerability in Windows Diagnostic Tracking Service
Although this vulnerability doesn’t directly result in a full elevation of privileges with code execution as NT AUTHORITY\SYSTEM
, it is still quite interesting because of the exploitation “tricks” involved. Diagnostic Tracking Service (a.k.a. Connected User Experiences and Telemetry Service) is probably one of the most controversial Windows features, known for collecting user and system data. Therefore, the fact that I found an Information Disclosure vulnerability in this service is somewhat ironic. The bug allowed a local user to read arbitrary files in the context of NT AUTHORITY\SYSTEM
.
DiagTrack RPC Interfaces
This time, I won’t talk about COM but pure old school RPC so, let’s check the interfaces exposed by Diagtrack thanks to RpcView.
We can see that it has quite a few interfaces but we will focus on the one with the ID 4c9dbf19-d39e-4bb9-90ee-8f7179b20283
. This one has 37 methods. This makes for quite a large attack surface!
The vulnerability I found lied in the UtcApi_DownloadLatestSettings
procedure…
The “UtcApi_DownloadLatestSettings” procedure
RpcView can generate the Interface Definition Language (IDL) file corresponding to the RPC interface. Once compiled, we get the following C function prototype for the UtcApi_DownloadLatestSettings
procedure.
1
2
3
4
5
long DownloadLatestSettings(
/* [in] */ handle_t IDL_handle,
/* [in] */ long arg_1,
/* [in] */ long arg_2
)
Unsurprisingly, the first parameter is the RPC binding handle. The two other parameters are yet unknown.
Note: if you’re not familiar with the way RPC interfaces work, here is a very short explanation. While working with Remote Procedure Calls, the first thing you want to do is get a handle on the remote interface using its unique identifier (e.g. 4c9dbf19-d39e-4bb9-90ee-8f7179b20283
here). Only then, you can use this handle to invoke procedures. That’s why you’ll often find a handle_t
parameter as the first argument of a procedure. Not all interfaces work like this but most of them do.
After getting a binding handle on the remote interface, I first tried to invoke this function with the following parameters.
1
2
3
4
RPC_BINDING_HANDLE g_hBinding;
/* ... initialization of the binding handle skipped ... */
HRESULT hRes;
hRes = DownloadLatestSettings(g_hBinding, 1, 1);
And, as usual, I analyzed the file operations running in the background with Process Monitor.
Although the service is running as NT AUTHORITY\SYSTEM
, I noticed that it was trying to enumerate XML files located in the following folder, which is owned by the currently logged-on user.
C:\Users\lab-user\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Tips\
The user lab-user
is the one I use for my tests. It’s a normal user with standard privileges and no admin rights. This operation originated from a call to FindFirstFileW()
in diagtrack.dll
.
The folder seems to be empty by default so I created a few XML files there.
I ran my test program again and observed the result.
This time, the QueryDirectory
operation succeeds and the service reads the content of file1.xml
, which is the first XML file present in the directory and copies it into a new file in the C:\ProgramData\Microsoft\Diagnosis\SoftLandingStage\
folder (with the same name).
The same process applies to the two other files: file2.xml
, file3.xml
.
Finally, all the XML files which were created in C:\ProgramData\[…]\SoftLandingStage
are deleted at the end of the process.
Note: I created a specific rule in Procmon to highlight CreateFile
operations occurring in the context of a DeleteFile
API call.
The CreateFile
operations originated from a call to DeleteFileW()
in diagtrack.dll
.
The Arbitrary File Read Vulnerability
The files are not moved with a call to MoveFileW()
or copied with a call to CopyFileW()
and we cannot control the destination folder so, a local attacker wouldn’t be able to leverage this operation to move/copy an arbitrary file to an arbitrary location. Instead, each file is read and then the content is written to a new file in C:\ProgramData\[...]\SoftLandingStage\
. In a way, it’s a manual file copy operation.
The one thing we can fully control though is the source folder because it’s owned by the currently logged-on user. The second thing to consider is that the destination folder is readable by Everyone
. It means that, by default, new files created in this folder are also readable by Everyone
so this privileged file operation may still be abused.
For example, we could replace the C:\Users\lab-user\AppData\Local\Packages\[…]\Tips
folder with a mountpoint to an Object Directory
and create pseudo symbolic links to point to any file we want on the file system.
If a backup of the SAM file exists, we could create a symlink such as follows in order to get a copy of the file.
C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC\Control\file1.xml -> \??\C:\Windows\Repair\SAM
Theoretically, if the service tries to open file1.xml
, it would be redirected to C:\Windows\Repair\SAM
. So, it would read its content and copy it to C:\ProgramData\[…]\SoftLandingStage\file1.xml
, making it readable by any local user. Easy, right?!
Well… Wait a minute. We have two problems here.
- The
FindFirstFileW()
call on theTips
folder would fail because the target of the mountpoint isn’t a “real” folder. - The new
file1.xml
file which is created inC:\ProgramData\[…]\SoftLandingStage
is deleted at the end of the process.
It turns out that we can work around these two issues using an extra mountpoint, several bait files and a combination of opportunistic locks (see the details in the next parts).
Solving The “FindFirstFileW()” Problem
In order to exploit the behavior described in the previous part, we must find a way to reliably redirect the file read operation to any file we want. But, we cannot use a pseudo symbolic link straight away because of the call to FindFirstFileW()
.
Note: the Win32 FindFirstFileW()
function starts by listing the files which match a given filter in a target directory but this doesn’t make any sense for an Object Directory. To put it simple, you can dir C:\Windows
but you cannot dir "\RPC Control"
.
This first problem is quite simple to address though. Instead of creating a mountpoint to an Object Directory immediately, we can first create a mountpoint to an actual directory, containing some bait files.
First, we would have to create a temporary workspace directory such as follows:
C:\workspace
|__ file1.xml
|__ file2.xml
Then, we can create the mountpoint:
C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> C:\workspace
Doing so, FindFirstFileW()
would succeed and return file1.xml
. In addition, if we set an OpLock on this file we can partially control the execution flow of the service because the remote procedure would be paused whenever it tries to access it.
When the OpLock is triggered, we can switch the mountpoint to an Object Directory. This is possible because the QueryDirectory
operation already occurred and is done only once at the beginning of the FindFirstFileW()
call.
C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC Control\file2.xml -> \??\C:\users\lab-admin\desktop\secret.txt
Note: at this point, we don’t have to create a symbolic link for file1.xml
because the service already has a handle on this file.
Thus, when the service opens C:\Users\lab-user\AppData\[…]\Tips\file2.xml
, it actually opens secret.txt
and copies its content to C:\ProgramData\[…]\SoftLandingStage\file2.xml
.
Conclusion: we can trick the service into reading a file we don’t own but, this leads us to the second problem. At the end of the process, C:\ProgramData\[…]\SoftLandingStage\file2.xml
is deleted so we wouldn’t be able to read it anyway.
Solving The Final File Delete Problem
Since the target file is deleted at the end of the process, we must win a race against the service and get a copy of the file before this happens. To do so we have two options. The first one would be bruteforce. We could implement the strategy described in the previous part and then monitor the target directory C:\ProgramData\[…]\SoftLandingStage
in a loop in order to get a copy of the file as soon as NT AUTHORITY\SYSTEM
has finished writing the new XML file.
But, bruteforce is always the option of last resort. Here, we have a second option which is way more reliable but we have to rethink the strategy from the beginning.
Instead of creating two files in our initial temporary workspace directory, we will create three files.
C:\workspace
|__ file1.xml
|__ file2.xml
|__ file3.xml
The next steps will be the same but, when the OpLock on file1.xml
is triggered, we will perform two extra actions.
We will first switch the mountpoint and create two pseudo symbolic links. We must make sure that the file3.xml
link points to the actual file3.xml
file.
C:\Users\lab-user\AppData\Local\Packages\[…]\Tips -> \RPC Control
\RPC Control\file2.xml -> \??\C:\users\lab-admin\desktop\secret.txt
\RPC Control\file3.xml -> \??\C:\workspace\file3.xml
And, we set a new OpLock on file3.xml
before releasing the first one.
Thanks to this trick, will are able to influence the service as follows:
- DiagTrack tries to read
file1.xml
and hits the first OpLock. - At this point, we switch the mountpoint, create the two symlinks and set an OpLock on
file3.xml
. - We release the first OpLock (
file1.xml
). - DiagTrack copies
file1.xml
andfile2.xml
which points tosecret.txt
. - DiagTrack tries to read
file3.xml
and hits the second OpLock. -
This is the crucial part. At this point, the remote procedure is paused so we can get a copy of
C:\ProgramData\[…]\SoftLandingStage\file2.xml
, which is itself a copy ofsecret.txt
. - We release the second OpLock (
file3.xml
). - The remote procedure terminates and the three XML files are deleted.
Note: this trick works because the process performed by DiagTrack is done sequentially. Each file is copied one after each other and all newly created files are deleted at the very end.
This results in a reliable exploit which allows a normal user to get a copy of any file readable as NT AUTHORITY\SYSTEM
. Here is a screenshot showing the PoC I developped.
Links & Resources
CVE-2020-0863 - Connected User Experiences and Telemetry Service Information Disclosure Vulnerability
https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0863My PoC for CVE-2020-0863
https://github.com/itm4n/CVEs/tree/master/CVE-2020-0863/poc/RpcView
https://www.rpcview.org/Symbolic Link Testing Tools - James Forshaw
https://github.com/googleprojectzero/symboliclink-testing-tools