On a recent client engagement, we were challenged to gain access to their private CI server. The CI server suffered from a security misconfiguration, and we were able to gain access. This was already a success, but we wanted to show more impact on XSS.
We started looking around the client’s TeamCity instance to see how we could increase the impact of the issue. While manually crawling the site, with Burp open in the background, Burp popped a DOM-based XSS issue. This is generally a false positive, but we had a look anyway.
This is where the fun began.
The analysis BurpSuite produced was the following:
Data is read from location.href and passed to jQuery.html.
The following value was injected into the source:
https://teamcity.jetbrains.com/project.html?projectId=ja21ikln4q%27%22`'"/ja21ikln4q/><ja21ikln4q/>eqoajxnqdk&cb_Root&fromExperimentalUI=ja21ikln4q%27%22`'"/ja21ikln4q/><ja21ikln4q/>eqoajxnqdk&true&tab=ja21ikln4q%27%22`'"/ja21ikln4q/><ja21ikln4q/>eqoajxnqdk&stats
The previous value reached the sink as:
<divdata-projectId="cb_Root" data-parentId="_Root"><span><span><span><svg width="20" height="20" viewBox="0 0 20 20"><path d="M2 9h7V2H2zm2-5h3v3H4zm7-2v7h7V2zm5 5h-3V4h3zM2 18h7v-7H2zm2-5h3v3H4zm7 5h7v-7h-7zm2-5h3v3h-3z"></path></svg></span><span><svg width="20" height="20" viewBox="0 0 20 20"><path d="M2 9h7V2H2zm2-5h3v3H4zm6 7l5 8 5-8zm8-9h-7v7h7zm-2 5h-3V4h3zM2 18h7v-7H2zm2-5h3v3H4z"></path></svg></span></span><a href='https://teamcity.jetbrains.com/project.html?projectId=ja21ikln4q%27%22`'"/ja21ikln4q/><ja21ikln4q/>eqoajxnqdk&cb_Root&fromExperimentalUI=ja21ikln4q%27%22`'"/ja21ikln4q/><ja21ikln4q/>eqoajxnqdk&true&stats&projectId=cb_Root'>teamcity.codebetter.com </a></span></div>
…snip…
The following proof of concept was generated for this issue:
https://teamcity.jetbrains.com/project.html?projectId='"><img src=1 onerror=alert(1)>cb_Root&fromExperimentalUI='"><img src=1 onerror=alert(1)>true&tab='"><img src=1 onerror=alert(1)>stats
I tried the POC, it didn’t work, and it returned a 404.
Let us try and get a working URL, and see if any injections are reflected. A working URL looks like:
https://teamcity.jetbrains.com/project.html?projectId=cb_Root&fromExperimentalUI=true&tab=stats
I tried injecting payloads I could easily search for in the DOM, like “xINJECTx”, into each query string parameter.
For the “tab” parameter, I found that my payload was reflected in the page inside a JavaScript context. This means my “xINJECTx” was inside script tags when the page was rendered. I sent the request “https://teamcity.jetbrains.com/overview.html?tab=xINJECTx” and was returned:
<script>
ReactUI.renderConnected('open_in_experimental_ui', ReactUI.OpenInExperimentalUI, {
tab: ‘xINJECTx’
});
</script>
This has the potential of a reflected XSS!
I tried the URL “https://teamcity.jetbrains.com/overview.html?tab=’-alert(document.cookie)-‘” and the payload:
‘-alert(document.cookie)-‘
And was met with the following page:
Jackpot!
This means our injection is returned to the browser, where the payload executes. This can lead to many outcomes, some of which have high impact, including the potential to steal CSRF tokens, which can lead to account takeover. The injection was found to work on 4 pages in total.
Another researcher found the same XSS independently of us, and was able to show more impact by proving they were able to gain remote command execution.
The issue was also valid on the open source TeamCity instance, so the examples above are all based on that instead of the private client data.
We disclosed the issue privately to Jetbrains, and they promptly created a fix. The XSS affects versions 2019.1 and 2019.1.1 of TeamCity and is fixed in version 2019.1.2.
The fixed script snippet looks like:
<script>
ReactUI.renderConnected('open_in_experimental_ui', ReactUI.OpenInExperimentalUI, {
favoriteBuilds: true,
tab: ReactUI.queryToObject(location.search).tab
});
</script>
This safely accepts input from the tab query string parameter.
Always dig a little deeper to see how we can increase the impact of our findings.
In general, the key to fixing XSS in any form, is to apply the proper encoding to the output before being rendered in a browser. In this case, the fix was to remove the reflected payload, and access the query string value in a safe way. This is a better approach in this case, as user input is no longer reflected onto the page. For detailed XSS mitigation techniques visit our post on practical mitigation techniques.
July 16, 2019 – Found reflected XSS issue
July 17, 2019 – Reported reflected XSS issue
July 18, 2019 – Issue updated to fixed
July 19, 2019 – Issue verified as fixed
July 31, 2019 – TeamCity 2019.1.2 released
September 26, 2019 – Quarterly Security Bulletin released describing security issues
Jetbrains Teamcity CVE Publication
https://blog.jetbrains.com/teamcity/2019/07/teamcity-2019-1-2-is-released/
Release Notes
Security Bulletin
https://blog.jetbrains.com/blog/2019/09/26/jetbrains-security-bulletin-q2-2019/
Note:
Another researcher found the same XSS independently of us, and was able to show more impact by proving they were able to gain remote command execution. https://twitter.com/JLLeitschuh/status/1169332316612644864?s=20
301 Moodie Dr. Unit 108
Ottawa ON K2H 9C4