Pwn2Own or Not2Pwn, Part 2.5: A brief tale of free 0days

Cim Stordal
Cognite
Published in
3 min readJun 2, 2020

In our first Pwn2Own blog post, we briefly talked about how we discovered a directory traversal vulnerability in less than 20 minutes after reading one of ZDI’s tweets. In this post, we will dive into the technical details of that vulnerability as well as a second directory traversal vulnerability in the unzipping code.

In the most recent update from Schneider, we were credited with CVE-2020–7495 and CVE-2020–7497. This post focuses on CVE-2020–7495. We won’t cover the technical details of CVE-2020–7497, which is a backup vulnerability that we had for our privilege escalation chain and can be used to acquire code execution as the System user during startup.

Vulnerability 1 (CVE-2020–7495)

Due to ZDI’s tweet, it became apparent that the other team used a directory traversal bug. We audited the unzipping code and found

string path = Path.Combine(ProjectLocalPath, contentFile.Uri.OriginalString.TrimStart(‘/’).Replace(“:”, string.Empty));

Because this code will remove “:” we can have file paths as `/..:/..:/lol`, a legal value, that will be translated into `/../../lol`, allowing directory traversal.

Let’s take a look at why this works.

Schneider unzipping code

Schneider uses the Package class from System.IO.Packaging to do the unzipping. The general gist of how they use it can be seen in the two code snippets below:

Here they will call OpenPackage, which will call Package.Open(). They will then iterate through all parts of the Package and pass it to Extractfile. ExtractFile will then decrypt the file and write it to the correct location.

Package class has built-in directory traversal mitigations. This explains why multiple Pwn2Own Miami participants swore that there was no way there was a bug in this unzipping.

By taking a look at the reference source for the Package class, we can start to understand how Microsoft mitigates them.

Package Class deep dive

The .NET Package validates package paths according to different rules; however, the one preventing path traversal is implemented by converting paths to Uri. It then assumes that a valid path is a path where the resolved Uri path is equal to the raw path. A resolved Uri will navigate on “..” so that “a/b/c/../” becomes “a/b”, which efficiently prevents a path traversal attack. The snippet below shows the validation implementation:

However since Schneider is replacing colons, we can instead use “..:” to traverse, which also happens to be a valid Uri, as seen below:

Crafting the malicious package

Crafting the malicious package file is quite straightforward. By modifying the packager, we used in the first part of this blog series, we can easily just say for file “hack,” add this prefix and it will work without any problems.

Endnotes

We mentioned the existence of this vulnerability in our first blog post and later received a friendly message that suggested the traversal bug was first introduced in Service Pack 1. However, as we now know, our directory traversal vulnerability does work prior to Service Pack 1. So let’s take a quick look at the Service Pack 1 update. Maybe there’s another vulnerability there?

Vulnerability 2 (CVE-2020-????)

In the UnpackOrCreateProjectFile function that is inside the PackageService class, if this.IsMetaDataFileExist() returns “true,” you will end up using the new unzipping method. If not you will use _compatibilityPackageService for unzipping (which is where our old unzipping bug is located).

If you are using the new unzipping method, you’ll end up in ExtractToDirectory(). As one can see in the snippet below, the output path is set to be:

string fullPath = Path.GetFullPath(Path.Combine(fullName, zipArchiveEntry.FullName));

However, since there is no validation of the zipArchiveEntry.FullName, a directory traversal is possible by setting the file name to “../”. This leads to a straightforward directory traversal bug. This is a known flaw in zipArchive that is mentioned in the first example in Microsoft’s documentation, so it’s not really that interesting.

We reported this bug as well, but it was marked as a duplicate.

Final words

Bugs are cool — even more so when they are free.

In order to exploit any of these bugs, you will need to find a second bug. For example, a bug that attempts to load a DLL file that does not exist during project loading should work.

Finding such a bug and building the full exploit is left as an exercise to the reader.

By Fredrik Østrem, Emil Sandstø and Cim Stordal

--

--