Windows API - Information disclosure / covert channel

I use open-source software every day. And I try to contribute as much as I can.

However, sometimes weird things happen when writing OSS code. Like finding a security bug in a closed source software.

Note: there is a TL;DR at the end of the post.

Backup!

I’m using restic for backup at home. It’s a lovely, lightweight, and well-designed backup utility. It also runs on my NAS, which is a delightful surprise for me.

Restic supports many backends (some natively, some others leveraging on rclone, another fantastic utility). It also supports a simple REST backend, named rest-server. The advantage of rest-server is that it’s “restic-aware”, so it can force things like “append-only mode” or “per-user repositories” server-side.

No doubt that rest-server is a good choice when running daemons on storage nodes.

Observability

If you deploy a project in Kubernetes, Docker, or some other orchestration tool, you might leverage on health checks (readiness/liveness). Long story short, these APIs can report to the orchestrator the current “health” of the application so that the orchestrator can decide what to do (e.g., re-schedule the instance, temporarily put that instance out of the load balancer, etc.).

This is also true when using “plain-old” load balancers in non-orchestrated environments.

@anguslees opened an issue a few months ago saying that he is deploying rest-server on Kubernetes. He made a “feature request”: a health check endpoint so he can instruct Kubernetes to monitor rest-server. I immediately picked that request, as I enjoy helping OSS projects.

What to check?

There are a lot of good articles on the internet on how to do good health checks. In this case, as rest-server doesn’t have external dependencies except for the local storage, my goal was to write Go subroutines to check whether the process has all necessary permissions and there is enough space.

I was looking for a cross-platform solution. Luckily, writing multi-platform code in Go is straightforward: you can create different files: they will be included in the build process only if the platform matches the file suffix (e.g., _windows.go). Also, calling UNIX syscalls and Windows APIs is straightforward.

Check directory permissions on Windows

To have rest-server behave correctly in Windows, I had to implement a permission check subroutine. However, while in UNIX/Linux I can check permission bits, on Windows we need to deal with NTFS ACLs.

That’s not entirely true: Linux supports filesystem ACLs, also the whole filesystem might be read-only, and the process might be confined using SELinux or AppArmor. However, a simple permissions bit check on the directory is a good start.

Dealing with ACLs is hard when you need to decode and analyze them. And I’m lazy. Damn lazy. So I opened the Windows API documentation, and I tried some API calls.

Ooops….

I did some tests using C. Why? Because I went from “boring-search-of-an-API” mode to “lets-play-with-APIs-and-break-something” mode.

CreateFile(TEXT("C:\\Users\\Alice\\Desktop\\cv.doc"),
        FILE_READ_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

I was testing the CreateFile API to open a file for reading its attributes. It was one of the checks that I planned to do. The call parameters are:

Let assume that I’m Alice from now on. I tried the call above (the file was there), and it works. So far, so good.

What if the file exists but it’s in a directory where the current user doesn’t have access?

CreateFile(TEXT("C:\\Users\\Bob\\Desktop\\firing_alice.doc"),
        FILE_READ_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

GetLastError() is returning “Access denied”. Good. I mean, I should not have access to Bob’s desktop.

What if the file doesn’t exist?

CreateFile(TEXT("C:\\Users\\Bob\\Desktop\\promoting_alice.doc"),
        FILE_READ_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

GetLastError() is returning “Not found”. OOOPS!

So yes, if you use this Windows API, you can tell if the file exists even if you don’t have access to the underlying path.

In security terms, this vulnerability is named Information disclosure: Alice was not supposed to get any information about Bob. Also, this bug can be exploited for “covert channels” or “side channels”: unintended communication channels between two users who should not be allowed to exchange data.

Microsoft Researcher Portal

I quickly opened a case in Microsoft Researcher Portal: case number #66958. After few days, Microsoft acknowledged the issue (VULN-053244). However, they told me that this doesn’t qualify for a security update so that the fix will be released as usual. Also, they said that I could share these details in public based on their responsible disclosure policy.

TL;DR