How I have exploited reflected self-XSS or CORS is not the end

The other day I have stumbled upon the reflected self-XSS. It is OK, if the self-XSS is stored – in this case the injected code is stored inside some application area, which is available only for the same user, that could inject the code there, for example, inside user personal area. In my case the following has occurred: I had discovered the link, vulnerable to classical reflected XSS and reported it into BugBounty. It didn’t even occur to check the link transferring to other user. Over time, I have received the report reject because of “can not reproduce”.

It turned out that the vulnerable application has the external sources video uploading function, and this is how it works:

  1. The user presses the “Upload video” button, and the separated video uploading page is opened. Then, as I think, some video dummy is inserted into DB. This dummy has associated video ID like 10345 already (this ID is visible inside the page source).
  2. The user selects the “Upload from external source” item and the other separated page with URL like
    /uploadExt?videoId=10345&vulnerableParam=test is opened. This is exactly the same page, that is vulnerable for XSS via vulnerableParam parameter.

The problem was that the vulnerable link couldn’t be transferred to other user, as he had no access to video with this ID, so the user has got the 403 Unauthorized server error. Thus, the user could be attacked only if he has initiated the video uploading process by himself and has navigated to vulnerable URL containing his own videoId.

Read further, what have I tried to bypass this limitation and what have I achieved 🙂

Initiating the uploading process

After clarifying the nuances, it became clear that the link is not enough to exploit this XSS, so I have decided to make HTML-page with scripts as a payload.

First, it was clear, that the user cannot be attacked if he is not initiated the uploading process first, because successful attack requires some video ID to be available for victim. Having investigating the “upload video” button source code, I have noted, that it was the simple link to the upload page in fact. So sending the necessary GET-request with user’s cookie, I can initiate the uploading process on behalf of user.

It is impossible to send the request from other domain directly, e.g. via XMLHttpRequest, because of CORS. However, there is well-known simple way to send GET-request with user’s cookie – to use image. Loading the image at some URL (e.g. http://example.com/image.png), the browser sends the user’s cookie for this domain also. At the same time, the URL does not have to be a valid image URL. You can use any URL, and browser will try to load an image using the URL provided. If there no valid image at the server side, the image will displayed broken, but the request would be processed by server by that time, as required.

So, I have crafted the first payload part:

1
2
3
4
5
6
7
8
<div id="main" style="display: none;"></div>
<script>
    div = document.getElementById('main');
    el = document.createElement('img');
    el.src = 'https://example.com/upload-video';
    el.onerror = step2;
    div.appendChild(el);
</script>

Here I create the image with necessary video upload process initiate page URL and set the step2 function as onerror event handler. In this case, this function will be executed as soon as the request is processed by server and browser understands, that there is no image at the server-side by this URL.

Finding necessary video ID

It was easy enough to initiate the video upload process. But it is needed to know now the specific video ID, associated with the user. I have decided to bruteforce it since the IDs were incremental. As I have said above, I have noted, that the 403 Unauthorized error is returned, if the user tries to navigate to vulnerable link with the foreign ID. It was the thing, I have decided to use to bruteforce the right ID. It remains only to think how to catch this error, because CORS prevents me from sending any requests directly.

First, I have tried to use the same way, as I have used earlier to initiate the video upload process, and create many images referring to URLs with different IDs, starting from some I know (e.g. associated with the user under my control). The hope was that the browser will fire the errors for the images referring to URLs with 403 response code, but will load successfully the image referring to the only right URL with 200 response, and the onload event handler will be executed. However, it was absurdly enough to hope for it, because there was no any real image by these URLs, so all images have failed with errors.

Next, I have tried to use iframe‘s instead of images. In this case it was expected that the injected JS-code would be executed in the frame referring to URL with right guessed ID. However, despite of the vulnerable page was the service one having quite short content, the server forbad it for framing using X-Frame-Options: SAMEORIGIN header. I have remembered, that Chrome does not support this header value, but it is not so now, starting from ver. 61 Chrome works with it correctly and forbids these URLs framing also.

In further investigations, it occurs that if to use the script tags instead of images, then the script is loaded successfully for any URL, returning 200 response code, and in case of 403 response code the script loading fails. So it remains only to finish the payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _getFunc(i) {
  return function() {
    document.location.href = 'http://example.com/uploadExt?videoId=' + i.toString() + '&vulnerableParam=<' + 'script>alert()<' + '/script>';
  }
}

function step2() {
    for (var i=10098; i < 10150; i++) {
      el = document.createElement('script');
      el.src = 'http://example.com/uploadExt?videoId=' + i.toString() + '&vulnerableParam=<' + 'script>alert()<' + '/script>';
      el.onload = _getFunc(i);
      div.appendChild(el);
    }
}

Here I create new scripts referring to potentially vulnerable pages with different IDs. In case of server returns 200 code for one of these requests, the script would be “loaded” successfully and the onload event handler would be executed. This event handler redirects user to the necessary page with guessed video ID and injected JS-payload. So, it is fully automated exploitation, despite of quite actions number is needed to be made.

As a result, the report was updated, the vulnerability was accepted and I have received an extra $ bonus for the proof of concept complication 🙂

P.S.: one more solution

There is one more way to bypass the CORS in the similar cases. The CORS policy enforcement is the only browser’s limitation, so any other software can simply send any requests with any cookie inside. So we could do the following in the case described:

  1. After user visits the HTML-page under out control, we can send the request to, for example, the PHP-script, which initiate the video upload process on behalf of some other user controlled by us and reads the associated video ID. As the script is not the browser, it could easily sign into the app, send the request, and read the response.
  2. Then, having received the ID associated with our user from PHP-script, we can initiate the video upload process on behalf of victim (using the image as I described above) and redirect him to vulnerable page with ID+1 (because the IDs are incremental).

The same exploitation success probability is quite high in case of IDs are not updating too often. For the tweet IDs, for example, this method could fail, because the hundreds of them are created every second. However, it was too hardly to bruteforce them in that case too 🙂

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published. Required fields are marked *