The Short Story On How The Steam Has Been Compromised!

STEAM Main Logo

The Short Story On How The Steam Has Been Compromised!

Hello guys! Today, we are going to share with you what Valve paid the highest prices in the history of their reward program for vulnerabilities.

For those who interested, welcome on board!

STEAM Photo 1

1. SQL Injection

Service partner.steampowered.com is designed to obtain financial information from Steam partners. On the sales reports page, a graph is drawn with buttons that change the display period of the statistics. Here they are in a green rectangle:

STEAM Photo 2

The request for loading statistics looks like this:

STEAM Photo 3

  • Where “UA” is the country code.
  • Well, it’s time for quotes!
  • Let’s try this one “UA’ “:

STEAM Photo 4

The statistics did NOT return, which was to be expected.

  • Now “UA:

STEAM Photo 5

The statistics are back and it looks like an injection!

Why?

Suppose that the database instruction looks like this:


SELECT * FROM countries WHERE country_code = `UA`;

If you send UA ’, the database instruction will be:


SELECT * FROM countries WHERE country_code = `UA``;

Notice the extra quote? This means that the instruction is invalid.
Corresponding to the SQL syntax, the query below is completely valid (there are no extra quotes):


SELECT * FROM countries WHERE country_code = `UA```;

Notice we are dealing with an array of countryFilter []. I assumed that if in the request to duplicate the country filter [] parameter several times, then all the values that we send will be combined in the SQL query like this:


'value1', 'value2', 'value3'

Check and make sure:

STEAM Photo 6

In fact, we requested the statistics of three countries from the database:


`UA`, `,` ,`RU`

The syntax is correct – the statistics returned.

The bypass of the Web Application Firewall 

Steam servers are hiding behind Akamai WAF. This disgrace inserts a stick in the wheels of good (and not very) hackers. However, I managed to overcome it by combining the values of the array into one query (what I explained above) and commenting. First, make sure the last one:


?countryFilter[]=UA`/*&countryFilter[]=*/,`RU

The request is valid, it means there are comments in our assortment.

We had several variants of syntax, local databases for testing payloads, comment symbols and an infinite number of quotes for all encodings, as well as self-written scripts on Python, documentation on all databases, instructions for circumventing firewalls, Wikipedia and anti-mail. Not that it was the necessary reserve for the promotion of the injection, but since it began to break the database, it is difficult to stop.SmartSpate

WAF blocks the request when it encounters a function. Did you know that DB_NAME / ** / () is a valid function call? The firewall also knows and blocks. But, thanks to this feature, we can divide the function call into two parameters!


?countryFilter[]=UA’,DB_NAME/*&countryFilter[]=*/(),’RU

We sent a request from DB_NAME / * anyway * / () – WAF did not understand anything, but the database successfully processed such an instruction.

Retrieving values from a database

So, an example of getting the length of a DB_NAME () value:


https://partner.steampowered.com/report_xml.php?query=QuerySteamHistory&countryFilter[]=',(SELECT/*&countryFilter[]=*/CASE/**/WHEN/*&countryFilter[]=*/(len(DB_NAME/*&countryFilter[]=*/())/*&countryFilter[]=*/=1)/**/THEN/**/'UA'/**/ELSE/*&countryFilter[]=*/'qwerty'/**/END),'

In SQL:


SELECT CASE WHEN (len(DB_NAME())= 1) THEN 'UA' ELSE 'qwerty' END

Well, humanly:


If the length of DB_NAME () is "1", then the result is "UA", otherwise the result is "qwerty".

This means that if the comparison is true, then in response we will receive statistics for the country “UA”. It is not difficult to guess that going through values from 1 to infinity, we will find the right one sooner or later.

In the same way, you can iterate through text values:


If the first character DB_NAME () is “a”, then “UA”, otherwise “qwerty”.

Usually, the “substring” function is used to get the Nth character, but WAF persistently blocked it. Here the combination came to the rescue:


right(left(system_user,N),1)

How does it work? We get N characters of system_user value from which we take the last one.
Imagine that system_user = “steam”. This is how the third character will look like:


left(system_user,3) = ste
right(“ste”,1) = e

Using a simple script, this process was automated and I got the hostname, system_user, version and the names of all the databases. This information is more than enough (the latter is even superfluous, but it was interesting) to demonstrate criticality.

After 5 hours, the vulnerability was corrected, but the status of triaged (adopted) was put to her after 8 hours and, damn it, for me it was a very difficult 3 hours during which my brain managed to survive the stages from denial to acceptance.

Clarification of paranoia

Since the vulnerability was not designated as accepted, I pushed that the turn to my report had not yet reached. But the bug was fixed, which means it could have been reported before me.

2. Getting all the keys to any game

In the Steam partner interface, there is the functionality of generating keys to the games.
Download the generated set of keys, you can use the request:


https://partner.steamgames.com/partnercdkeys/assignkeys/&sessionid=xxxxxxxxxxxxx&keyid=123456&sourceAccount=xxxxxxxxx&appid=xxxxxx&keycount=1&generateButton=Download

In this query, the keyid parameter is the id of the key set, and keycount is the number of keys that must be obtained from the given set.

Of course, my hands instantly reached out to drive in different keyid, but in response, I received an error: “Couldn`t generate CD keys: No assignment for the user.” It turned out not so simple, and Steam checked whether the requested set of keys belonged to me. How did I get around this check?

Attention:


keycount=0

Generated a file with 36,000 keys from the game Portal 2. Wow.
Only one set turned out that number of keys. And the total sets at the moment more than 430,000. Thus, going through the keyid values, I was a potential attacker who could download all the keys ever generated by the Steam game developers.

Conclusion

  • Costly WAF systems from top companies are not a guarantee for the security of your web applications.
  • If you are a bug hunter, then try to penetrate as deep as possible. The fewer users have access to the interface, the more likely it is to find a vulnerability in this interface.
  • Developers and business owners, there are no absolutely safe applications! But you hold on. Have a good mood!