# [CVE-2020-0863 - An Arbitrary File Read Vulnerability in Windows DiagnosticTracking Service](/cve-2020-0863-windows-diagtrack-info-disclo/)
March 18, 2020
Although this vulnerability doesn't directly result in a full elevation of privileges with code execution as `NTAUTHORITY\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 `NTAUTHORITY\SYSTEM`.
![](https://images.seebug.org/1584594999969-w331s)
## 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**.
![](https://images.seebug.org/1584595002036-w331s)
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!
![:wink:](https://images.seebug.org/1584595132695-w331s)
![](https://images.seebug.org/1584595134938-w331s)
The vulnerability I found lied in the `UtcApi_DownloadLatestSettings`
procedure… ![:smirk:](https://images.seebug.org/1584595137291-w331s)
## The "UtcApi_DownloadLatestSettings" procedure
**RpcView** can generate the C source code corresponding to the RPC interface.
This yields the following prototype for the `UtcApi_DownloadLatestSettings`
procedure.
```
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.
```
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.
![](https://images.seebug.org/1584595139111-w331s)
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`.
![](https://images.seebug.org/1584595141720-w331s)
The folder seems to be empty by default so I created a few XML files there.
![](https://images.seebug.org/1584595143823-w331s)
I ran my test program again and observed the result.
![](https://images.seebug.org/1584595145920-w331s)
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`.
![](https://images.seebug.org/1584595148435-w331s)
![](https://images.seebug.org/1584595150923-w331s)
Finally, all the XML files which were created in
`C:\ProgramData\[…]\SoftLandingStage` are deleted at the end of the process.
![](https://images.seebug.org/1584595153411-w331s)
> **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`.
![](https://images.seebug.org/1584595157291-w331s)
## 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.
![](https://images.seebug.org/1584595159636-w331s)
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?!
![:sunglasses:](https://images.seebug.org/1584595161973-w331s)
Well… Wait a minute. We have two problems here.
![:confused:](https://images.seebug.org/1584595163879-w331s)
1. The `FindFirstFileW()` call on the `Tips` folder would fail because the target of the mountpoint isn't a "real" folder.
2. The new `file1.xml` file which is created in `C:\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:
1. DiagTrack tries to read `file1.xml` and hits the first OpLock.
2. At this point, we switch the mountpoint, create the two symlinks and set an OpLock on `file3.xml`.
3. We release the first OpLock (`file1.xml`).
4. DiagTrack copies `file1.xml` and `file2.xml` which points to `secret.txt`.
5. DiagTrack tries to read `file3.xml` and hits the second OpLock.
6. **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 of `secret.txt`.
7. We release the second OpLock (`file3.xml`).
8. 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.
![](https://images.seebug.org/1584595165630-w331s)
## Links and 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-0863>
- My PoC for CVE-2020-0863
<https://github.com/itm4n/DiagTrackAribtraryFileRead>
- RpcView
<https://www.rpcview.org/>
- Symbolic Link Testing Tools - James Forshaw
<https://github.com/googleprojectzero/symboliclink-testing-tools>
暂无评论