Holiday

Gaining Access

Nmap scan:

$ nmap -p- --min-rate 3000 10.129.29.106               
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-17 09:07 EDT
Nmap scan report for 10.129.29.106
Host is up (0.0072s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http-alt

Detailed scan:

$ nmap -p 8000 -sC -sV --min-rate 3000 10.129.29.106   
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-17 09:07 EDT
Nmap scan report for 10.129.29.106
Host is up (0.0077s latency).

PORT     STATE SERVICE VERSION
8000/tcp open  http    Node.js Express framework
|_http-title: Error

Added holiday.htb to the /etc/hosts file as per normal practice.

Web Enum -> Login SQLI

The website just shows a hexagon:

Oddly, both feroxbuster and gobuster returned nothing useful. Only dirb returned anything useful.

Visiting /admin redirects me to the /login page:

Attempting to login with weak credentials results in this error:

When testing for basic SQLI, I noticed that using double quotes results in an error:

Interesting. I tested the username field a bit more, I found out that using "OR "1" ="1 results in a different error:

Now RickA is the user. The same payload doesn't work with the password field. This probably means that only the username is vulnerable to SQL Injection.

SQLI -> Login Creds

Using the fact that there are different errors on the page for valid and invalid queries, I attempt to exfiltrate the password.

UNION injection can be used. First, I have to figure out the syntax to 'end' the first query and return no error, since using " alone was causing an error.

I tried some of het entry point detection payloads from Hacktricks, and found that ")) triggered no error.

From here, I can start the UNION injections. I tested the number of columns manually, and found that there were 4:

From here, I need to find out what database was this thing running, and which column actually returned strings.

Using @@version for all columns resulted in an error, so its not MySQL or MSSQL. Using version() failed too, so its not PostgreSQL. Using sqlite_version() in column 2 returned 'Incorrect Password', thus the database is SQLite.

The most interesting part was the fact that the result was auto-filled into the username portion:

Using this, I can easily find out the table names within this database.

This produced users,sqlite_sequence,notes,bookings,sessions,sessions. Now, I can enumerate the columns in users.

This produces CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT,active TINYINT(1)) as the output, meaning there's a password column. Extracting the password can be done using "))UNION SELECT 1,group_concat(password),3,4 FROM users--.

This produces the hash:

This hash can be cracked using Crackstation:

And with that, I can login to the interface:

XSS -> Administrator

In each of the booking details, there was an 'Add Note' function:

This was being viewed by an admin, which means XSS! I tried this payload:

The JS file only had this line:

However, I never received a callback, and that's when I realised it was not reflected XSS, but rather stored:

The payload is reflected directly on screen. I copied and pasted a TON of payloads, then took a look at how the filtering worked.

I noticed that it kept <img> tags in tact:

It spaced out the javascript, but it also kept ` characters in tact.

I also noticed this part here:

If the javascript link was within a ` character, it was left alone. The original payload was:

So it removed spaces, and the entire XSS portion there, thus my final payload cannot contain spaces. The filter also removed all ' characters.

This payload made it past the string filter but I don't think it works since it doesn't use script tags or anything:

I eventually gave up finding this payload, and I checked the solution. The actual payload was:

The final payload did not allow me to have spaces in it, so the actual code executed has to use the eval(String.fromCharCode) method.

For this, I can use document.write to insert HTML into the DOM since I'm using eval.

The final payload would thus be:

This finally works and I got callback:

From here, I just need to figure out how to get the cookie out. The cookie is set to HttpOnly, meaning doucment.cookie and what not will fail. I attempted to steal the page contents using this:

The above payload doesn't work, but I can tweak it a bit to steal page contents:

This works in sending me the page contents:

Within the page content is the cookie I need:

When this cookie used, I get a new 'Admin' panel:

Admin Panel RCE

Earlier, I found a /admin panel, and now I can view it:

Viewing the page source reveals it sends requests to these endpoints:

Visiting the /admin/export directory results in an error:

Attempting to tamper with the table parameter results in this error:

It seems to allow & characters, and using that results in RCE:

The only issue is that no . characters are allowed, meaning I have specify my IP address in decimal instead of the octal notation.

10.10.14.18=168431122. Here are the commands I executed to get a shell:

Privilege Escalation

Sudo Npm -> Root

This user had sudo privileges:

This can be exploited using the script on GTFOBins:

Rooted! Super hard start.

Code Vulnerabilities

I took a look at the vulnerabilities for both the SQL Injection, XSS and RCE within the /home/algernon/app/setup directory.

XSS

Here's the XSS code:

Logs in each time, and then calls evaluate to load the elements, thus allowing me to execute Javascript on this.

The XSS whitelist was within router.js:

Seems that it only allows <img src>, and it replaces certain characters with empty strings except for ` characters.

SQL Injection

Now I can see why ")) was a valid entry point, and why password was not vulnerable.

RCE

The /admin/export directory passed the table parameter to exec, pretty self-explanatory. The regex filter was quite weak as well.

Last updated