CYS4 - CVE-2021-37841 - Docker - HiveNightmare
Table of contents
Introduction
On July 20, 2021, Microsoft released a new CVE (CVE-2021-36934 aka HiveNightmare or SeriousSAM) described as Elevation of Privileged.
In details, due to an over permissive (incorrect) Access Control Lists (ACLs) on multiple system files, including the Security Accounts Manager (SAM) database, an attacker could abuse this vulnerability to run arbitrary code with SYSTEM privileges. For example, an attacker could use it to install programs, view, change, delete data, or create new accounts with full user rights. The security update fixed the ACLs, but users must manually delete all shadow copies of system files, including the SAM database, to mitigate the security issues fully. Simply installing the security update was not fully mitigate this vulnerability.
This security issue created a considerable stir, and several news outlets have reported the impact. Interestingly enough, a few weeks ahed of this issue becoming public, I have reported a similar vulnerability targeting Docker for Windows, with the assigned CVE-2021-37841. I will describe the vulnerability CVE-2021-37841 in this article.
Preface
CYS4 offers an entirely developed in-house, e-learning SOC training platform where we aim to provide specialized, continuous, and hands-on training for the operational components of a SOC (Security Operation Center). Our product offers clear and always updated Learning Paths, designed to make clear and steady learning steps. Specifically, it allows the user to tackle new topics through a guided itinerary based on MITRE ATT&CK framework. While developing a new Learning Path about the attacks targeting container technologies, I researched how Docker for Windows works, discovering the security issue mentioned on top of this article.
A brief about Docker for Windows
For those who are unfamiliar with it, Windows 10 and its server counterparts added support for containerization. Starting from the Google Project Zero’s blog1, MSDN2, and with the resource written by James Forshaw, I began to better understand how Windows Containers work, focusing on the Docker platform, as it supports Windows containers.
The primary goal of a container is to hide the real OS from an application and improve the scalability. For example, with Docker you can download a standard container image containing a completely separate Windows copy. The image is used to build the container, is built on top of the host operating system’s kernel, and can be executed in two different ways:
-
Process Isolation: this uses a feature of the Windows kernel called a Server Silo allowing for redirection of resources such as the object manager, registry, and networking. The kernel is shared with the host OS.
-
Hardware Isolation: this uses an entirely separate high-performance virtual machine through Hyper-V.
Microsoft suggests using the Hyper-V for isolation. This virtual isolation mode allows multiple container instances to run concurrently on a single host in a secure manner. Hyper-V isolation effectively gives each container its kernel while providing hardware isolation, introducing a security boundary around the containers instead of processes.
The following image represents on the left the Process Isolation architecture and on the right the Hardware one:
In contrast to a container that runs on a Process Isolation, a virtual machine (VMs) runs a complete operating system-including its own kernel.
Threat Scenario
Most of the research that I have read was focused on trying to escape from the container to attack the host. However, I decided to go beyond that and try something different:
- Is it possible for a low privileged user inside the host to break containers on it?
Setup
I used a VM for test purposes, so I used Process Isolation instead of using the secure Hardware isolation (nested virtualization does not quite get along with VBox). Note that, for the considered threat scenario, this configuration does not matter.
On the following code snippets, it is possible to define a docker file used to build an image that will execute a Powershell command as a test.
Dockerfile:
FROM mcr.microsoft.com/windows/servercore:1809-amd64
RUN net user admin B@dPWD2021! /ADD
RUN net localgroup Administrators admin /add
USER ContainerUser
CMD [ "powershell", "-noexit", "-command", "Write-Host Hello" ]
Start and create the service:
sc create dockerd binPath="\"C:\Program Files\Docker\Docker\resources\dockerd.exe\" -H npipe:////./pipe/XXX -G docker --exec-opt isolation=process --run-service" start=delayed-auto
Build command:
docker -H "npipe:////./pipe/XXX" build -t test_image_user .
Start the docker image:
docker -H "npipe:////./pipe/XXX" run --isolation=process -it test_image_user
Windows containers using Process Isolation create a “ContainerAdministrator” on Windows Server Core and “ContainerUser” on Nano Server containers, by default, that will be used to access the files and directories in the mounted volume. Hardware Isolation uses a LocalSystem account.
Instead of using the defaults accounts, most users add a new user to the image to run a microservice or a task and separate the privileges. In our example, we have added a local administrator with the username admin.
With the help of the Sysinternals suite, I analyzed a few interesting handles and other important information: while on this task, something anomalous caught my eye.
The ACL bug
Now from the host, it is possible to manage the container created. To do this, the user needs the proper privileges, for example, administrative rights or present in the “docker” group. It should be impossible to access the container data from a low-privileged account running on the host without the conditions specified above.
Unfortunately, during the installation of the Docker Desktop, if the data root directory doesn’t exist, it is created with a wrong ACL. The bug is not present in the Feature Installation because Moby’s daemon sets correctly the permission for the data root directory if it does not exist.
//https://github.com/moby/moby/blob/7b9275c0da707b030e62c96b679a976f31f929d3/daemon/daemon_windows.go#L480-L481
const (
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System
SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
)
func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
config.Root = rootDir
// Create the root directory if it doesn't exists
if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil {
return err
}
return nil
}
Exploit
Due to a wrong ACL, a low privileged user could access the data unpacked files from a container, present by default in this following directory:
C:\ProgramData\docker\windowsfilter\<id>\
The id represents the container identification of the image. Inside the id directory, it is possible to find the folder containing the Holy Grail for every red teamer: Hives.
Reaching RCE is quite tricky because it is impossible to switch the user’s context inside a windows container. However, it is possible to execute malicious code inside the container. An attacker, reading the hives with tools like mimikatz could exec a classic Pass-the-Hash attack and take control of the container.
This security issue leads an attacker with a low privilege to read, write and even execute code inside the containers.
Remediation
Docker updated their installer to set the ACLs for the folder and file created correctly. Docker installer code is not public, for reference, the code used to set the ACLs looks like this:
private static readonly string SddlAdministratorsLocalSystem = "D:P(A;OICIID;FA;;;BA)(A;OICIID;FA;;;SY)";
public static void SetAdministratorLocalSystemOnly(string path)
{
DirectorySecurity security = Directory.GetAccessControl(path);
security.SetSecurityDescriptorSddlForm(SddlAdministratorsLocalSystem);
SecurityIdentifier admin = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
security.SetOwner(admin);
Directory.SetAccessControl(path, security);
}