Привет, Хабр! Сегодня я рас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
    01.10.2018 18:01
    -3

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


    1. Popadanec
      01.10.2018 18:27
      +5

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


      1. KoToSveen
        02.10.2018 04:34

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


        1. Popadanec
          02.10.2018 10:29

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


          1. scruff
            02.10.2018 11:13

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


            1. Popadanec
              02.10.2018 12:45

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


          1. click0
            02.10.2018 21:11

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


      1. Kwisatz
        02.10.2018 07:43

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


        1. aPiks
          02.10.2018 12:48

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


          1. Melkij
            02.10.2018 13:44

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


            1. kalininmr
              02.10.2018 22:25

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


              1. Kwisatz
                02.10.2018 22:44

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


                1. kalininmr
                  03.10.2018 03:53

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


            1. aPiks
              03.10.2018 11:22

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


          1. ad1Dima
            02.10.2018 13:52

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

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


          1. Kwisatz
            02.10.2018 18:51

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


      1. pronvit
        02.10.2018 11:14

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


      1. stul5tul
        02.10.2018 13:19

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

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

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


    1. Suvitruf
      01.10.2018 18:33

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


      1. kalininmr
        03.10.2018 06:19

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


  1. Renaissance
    01.10.2018 18:08
    +1

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


    1. Popadanec
      01.10.2018 18:29

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


  1. Igor_Sib
    01.10.2018 19:13

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


    1. pyrk2142
      01.10.2018 19:57

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


      1. RussDragon
        01.10.2018 20:54
        +2

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


        1. Popadanec
          01.10.2018 22:08
          +1

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


          1. Quarc
            02.10.2018 03:35

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


            1. Igor_Sib
              02.10.2018 05:03

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


  1. Murimonai
    01.10.2018 22:19
    +2

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


  1. altman
    02.10.2018 07:52

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


  1. nerlihmax
    02.10.2018 08:42
    -4

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


    1. Newbilius
      02.10.2018 10:22
      +1

      «Игор» — это мем откуда-то. Ну и опечатки лучше в личку :)


      1. darthslider
        02.10.2018 11:42
        +1

        lurkmore.to/PS3_has_no_games — вот отсюда это мем :)


      1. nafgne
        02.10.2018 11:44

        Калька с "PS3 has no gaems".


      1. sHaggY_caT
        03.10.2018 03:04

        Есть ещё мем «Игорь тонет» на эту же тему :)


    1. aleaksah
      02.10.2018 16:15

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

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


  1. McDermott
    02.10.2018 09:22

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

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


    1. TimsTims
      02.10.2018 10:12

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


    1. Seryojik
      02.10.2018 10:32

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


  1. scruff
    02.10.2018 11:26

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


    1. psycho-coder
      02.10.2018 12:21

      try {
          return $db->getResults();
      } catch(...) {
          return [];
      }

      ?


  1. Anshi85
    02.10.2018 14:04

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


    1. ad1Dima
      02.10.2018 14:24

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


      1. 8street
        02.10.2018 14:43

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


        1. STFBEE
          02.10.2018 14:53

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


        1. moskowsky Автор
          02.10.2018 15:03

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


  1. bro-dev0
    02.10.2018 20:54

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