This is an archive post from the Netsparker (now Invicti) blog. Please note that the content may not reflect current product names and features in the Invicti offering.
As a result a lot of advice was posted on the internet, reaching from “Don’t visit any Steam profiles” or “If you are a developer always make sure to sanitize your data before it even reaches your database” to “A limit of 128 characters is too short to do any damage”.
Within this blog post we will take a look at the vulnerability and debunk some myths about the Cross-site Scripting vulnerability and its impact.
What is Steam?
Steam is game distribution platform that allows users to buy games, interact with other players, play online against each other and offers copyright protection for game developers. It has about 125 million registered users, almost twice as much as the whole population of the United Kingdom. In 2015 sales on the platform totalled to about $3.5 billion, which is almost equal to Swaziland’s whole GDP.
The XSS vulnerability on Steam Platform
Judging from comments on several social media sites it seems like the vulnerability was present in a feature called “Steam Guides” on Steam’s community website steamcommunity.com. Steam guides allow a user to publish guides for any steam game in order to help other players get an overview of a specific game, or help them with a bug or difficult level.
The possible impact was huge and reportedly included phishing, complete draining of a user’s account funds or just modification of the profile’s appearance. Also a “Samy is my hero” - kind of XSS worm is one of the possible impacts.
But also funny and harmless profile modifications were possible like the one shown in the video below.
Misconceptions on the Cross-Site Scripting Vulnerability (XSS)
Unfortunately during the time Steam was vulnerable a lot of false information about XSS was spread. Let’s look at a few of them and explain some additional context within the next few paragraphs
“To be safe, don’t visit any suspicious profile pages”
This is only half the story. Logging out of Steam until the vulnerability was completely fixed and starting a new browser session before logging in again would have been a good measure that could have prevented an XSS attack to succeed. Otherwise you’d actually have to say “Don’t use the internet”. Surprisingly often people assume that XSS can only be triggered if you directly point your browser at the affected page. However this is not true.
This advice given on reddit.com/r/steam wouldn’t save you
An attacker can simply create an iframe, which is not visible for the user, to trigger the XSS. The iframe can be placed on just any attacker-controlled website and can be pointed to a profile with a malicious payload.
Steam could prevent this type of attack by adding a Content Security Policy rule, containing the frame-ancestors directive or an X-Frame-Options header.
However, this won’t help against redirects to the affected profile. Any site that you visit could redirect you to a malicious profile and then back to another site as soon as the malicious XSS payload is executed. This is not immediately obvious as it happens very quickly. Another idea is to utilize reverse tabnabbing to achieve the same.
Once a link which opens a new web page is clicked, the opened website has access to the window object of the tab that opened it. This means that the new page can redirect the parent page to any other website, like a malicious Steam page, execute code and immediately redirect back to the initial web page.
By combining window.opener.history.go and window.opener.document.location these redirects won’t show up in the browser’s back button. The only suspicious thing a user sees is the opening browser tab that behaves weirdly. But this behaviour can also be explained with a refresh.
“128 characters are not enough to do anything bad”
This was quickly debunked by users that showed example code of how such an exploit could look like. It was also possible to load script code from other resources like the profile’s comment box.
Another more stealthy way is using
Therefore Steam could see that there was a possibility that malicious code has been executed, but had no way to verify which code it was. This code consists of exactly 52 characters and allows any script code to be executed.
“Developers should sanitize their code against XSS before it even hits the database”
This is also not true. Sanitization should be applied output dependent and shouldn’t happen on input. The goal is to have the raw user input inside the database to apply flexible sanitization depending on the code. E.g. when the code is saved inside an HTML tag it should be sanitized in another way than code inside a script tag.
It is okay to reject input that doesn’t match certain criteria like links that don’t begin with http(s)://, but input should rather be rejected than modified or sanitized before it hits the database. Even to prevent SQL injection vulnerabilities, code shouldn’t be just sanitized. It’s much more secure to use prepared statements than just encoding or escaping special characters.
That being said it’s not as easy as just applying one type of sanitization to the input and be safe against any attack that follows while maintaining usability of the code. Security is hard and often depends on the context. While converting only < and > outside of HTML tags to entities prevents opening new tags, it’s still insecure in a script context or inside an HTML tag.
And while converting user input to hex inside of script tags is a good way to prevent users from breaking out of strings or the script block, the HTML parsers can’t convert it back to a human readable format in an HTML context. That’s why context dependant encoding is necessary.
“Chrome has an inbuilt XSS filter, so you are safe against any kind of Cross Site Scripting if you use it”
While ignoring multiple bypasses that have existed for XSS filters from Chrome to Edge,Internet Explorer and even NoScript, it’s worth noting that none of these filters could have prevented this XSS attack, or even tried to do so. The reason being is that the XSS payload was stored and didn’t need any input like a POST or GET parameter containing the script code or HTML.
That being said the malicious script was indistinguishable from any legit code that the Steam developers might have placed on the website for non malicious purposes. None of the inbuilt XSS filters would have been able to catch this specific vulnerability keeping other, non-malicious scripts functioning.
Why didn’t Content Security Policy prevent the attack?
An external link would easily fit in the 128 character limit. So why didn’t the users just include any script from another website? The answer is Content Security Policy. It prevented the inclusion from external scripts which weren’t whitelisted. However, the Policy wasn’t really effective as many attacks still worked. The reason was that script-src was set to unsafe-inline which made it possible to just write script blocks to the page and execute any code.
This could have been prevented by applying CSP correctly and only allowing script inclusions from steamcommunity.com or other whitelisted external websites. If inline script blocks are necessary they should additionally be secured with a nonce instead of just allowing them without any other safety precautions.
Is Netsparker able to detect such issues?
Yes. In fact Netsparker web application security scanner would not only have found the stored XSS vulnerability, but would also have alerted Steam about the insecure Content Security Policy. It would also inform them that attackers can frame the profile pages, which possibly makes it vulnerable to clickjacking.