Всем доброго дня! В течении последнего полугода играю в одну не очень известную онлайн-игрушку на планшете. Попутно пытаюсь ее ломать то с одного боку, то с другого. Но каких-то успехов достиг только на пакетном уровне. К сожалению большинство переменных там защищены. Взлом в стиле ололо-репитов не канает. Дыры есть, но мало, особо не разгуляешся. А хочется гораздо большего. Поэтому пришел к вам, за советами. Итак, что уже достигнуто: 1. Обошел SSL-шифрование. Могу слушать трафик между игрой и клиентом. 2. Расковырял игровой клиент, выдрал оттуда алгоритм подписи пакетов. На его основе сделал тулзу чтоб подписывать свои собственные пакеты. Могу вклиниваться в процесс и менять значения переменных на лету. Либо самостоятельно формировать и отправлять на сервер любые пакеты. Сервер их принимает и обрабатывает. 3. Нашел несколько незащищенных переменных, подмена которых дает некоторое игровое преимущество. К сожалению возможности у этих дыр ограничены. 4. Собрал кое-какую информацию об игре. Сервер работает на CentOS, имеет Apache версии 2.2.15, клиент написан на С#. Запросы от клиента обрабатываются на сервере PHP скриптами. Думаю есть БД, но какая - не знаю. 5. Пытался проверить сервер на уязвимость Shellshock (Bashdoor). По моим скромным выводам результат отрицательный. Какие цели есть на данный момент. Найти любую уязвимость, которая откроет передо мной более широкие возможности. Хорошо бы по крайней мере один их пунктов: 1. Найти новые полезные дыры на пакетном уровне. Хотя за полгода я наверное уже все щели обнюхал. 2. Получить частичный или полный доступ к БД. Как минимум читать произвольные таблицы. Как максимум - редактировать их. 3. Получить доступ к исходникам серверной части игры. Как минимум заглянуть во внутренности скриптов. Как максимум - внести в них свои поправки. 4. Было бы хорошо залить шелл. Еще лучше - сделать это не привлекая внимания админов. 5. Получить доступ к админке (CP), FTP или SSH (мечты, мечты...) 6. Получить доступ еще к чему-нибудь полезному, может я о чем-то даже не подозреваю, а вы подскажете. Но оговорюсь что чужие аккаунты меня не интересуют. И гробить сервер или мешать его работе я тоже не собираюсь. Скорее наоборот, предпочитаю быть тише воды ниже травы. Какие ресурсы и опыт имеются: 1. Понимание самых базовых принципов работы приложений и серверов. На уровне начинающего. 2. Небольшой опыт программирования. Программирую методом топора, подпирая программы костылями со всех сторон. 3. Любительский опыт взлома. На 70% почерпнутый из разнообразных статей в сети, на 30% состоящий из личных шишек, пота и крови. На самом деле опыт не очень большой. Умею делать часть из того, чему учат в мануалах "для чайников" которыми пестрит рунет. 4. Терпение, время. Готовность курить мануалы с уровнем утомительности не превышающим мои психические возможности. "Войну и мир" о принципах работы с ассемблерным кодом читать не готов. Какие будут идеи?
Тема провисела на форуме 2 суток, набрала 500 просмотров и 0 ответов. Куда подевались все гуру-хакеры? Ладно, попробую задать более конкретный вопрос. Что имеем: Последние несколько дней я пытаюсь сделать игре SQL-инъекцию. Как я уже говорил, клиент обменивается с сервером HTTPS запросами. На сервер отправляются обыкновенные POST-запросы вида https://www.game.ru/public/api/script_type/script.php?var1=data1&var2=data2... В ответ приходит целая гора данных в формате JSON, основываясь на которых, клиент выдает картинку игровых действий (передвижение, битвы, игровой инвентарь ит.д.) Разумеется никаких html-страниц и форм нет и в помине. Но поскольку я могу слушать трафик напрямую, то и вмешиваться в него могу тоже напрямую. Следовательно - изменять переменные отправляемые на сервер так, как если бы я это делал в строке запроса GET. Я могу придать переменной любое значение, однако часть символов экранируется. А именно символы / (слэш), \ (обратный слэш) и "(двойная кавычка). Все остальные символы поступают на сервер без экранирования. По крайней мере символы ['],[`],[*],[:],[;],[&],[%],[$],[@],[?],[(],[)],[{],[}],[,],[.],[!],[-],[|],[=],[^],[#] можно передать в исходном виде. Однако SQL-инъекцию затрудняет то, что часть переменных защищена проверками на валидность, а другая часть, по видимому, в SQL-запросах не участвует. Но я таки отыскал одну переменную, через которую инъекция возможна. Как не удивительно, но это переменная uid (id игрока). Что работает: Предположим мы работаем с исходной переменной uid=99 Если привести ее к виду uid=99' получим ошибку 500 (Internal Server Error), т.е. ошибочный SQL-запрос который сервер выполнять откажется. Значение uid=99/* передать к сожалению нельзя из-за экранирования слэша. Значение uid=99-- будет обработано. Так же будет обработано: uid=99--+любой текст Будет обработано: uid=99+and+null-- Будет обработано: 99+and+null Будет обработано: 99+and+concat('1','2') Будет обработано: 99+and+version() Будет обработано: 99+and+@@version Приведет к небольшому зависанию и ошибке 504 Gateway Time-out: uid=99+or+1-- Ну еще бы, аккаунтов в игре зарегистрировано более 13 млн. Вот такая переменная затянет выполнение запроса на ~2300мс: uid=99+and+BENCHMARK(10000000,md5(current_date)) Обычно запрос выполняется за 5-15мс. Скорее всего есть возможность провести DOS атаку, но меня это не интересует. Что не работает (подводные камни): Проблема 1. Первая проблема состоит в том, что значение переменной uid используется не только в запросе к БД, но и каким-то образом связано с проверкой валидности игровой сессии. Всего в проверке сессии их учавствует три - uid (номер игрока), serverid (номер сервера) и sessionToken (ключ сессии). uid - всегда неизменный для отдельного игрока serverid - неизменен до тех пор пока мы играем на одном и том же сервере sessionToken - выдается каждый раз новый. Происходит это при входе в игру, либо при смене сервера. Предыдущий sessionToken при выдаче нового становится недействительным. Внешне он напоминает хеш md5. Если поменять хотя бы один символ в любой из этих переменных, получим обрабатываемую игровым движком ошибку "Ключ сессии неверный!". При этом, несмотря на то, что запрос к БД будет выполнен, обычный набор данных в JSON-формате PHP-скрипт нам не отправит. А если он их не отправит, то и результаты SQL-запроса вставлены никуда не будут... То есть не представляется возможным получить какую-либо пользу даже от успешного внедрения в запрос конструкции UNION SELECT... Как решить данную проблему я не понимаю и очень рассчитываю на вашу помощь. Проблема 2. Собственно, мне не удается узнать число столбцов между SELECT и FROM. Я пытался это сделать вот так: uid=99+union+select+null uid=99+union+select+null,null uid=99+union+select+null,null,null ... и т.д. К сожалению сервер все время возвращает ошибку 500. То есть запрос не выполняется. Предположим это может быть просто результатом неверного числа добавленный мной null. Я утомился после 30-го по счету. Конечно, их может быть 100 или 300. Ну да черт с ним. В случае чего я пару часов своего времени на перебор убить могу. Но что если сервер отказывается принимать саму конструкцию select+null? Или оператор union? Или еще что-нибудь в синтаксисе моего запроса? У меня есть основания полагать что это так, поскольку переменная uid=99; (добавлена точка с запятой) сервером обрабатывается. В то время как uid=99;+select+null приводит к ошибке 500, хотя после точки с запятой количество нуль-значений в запросе никак не должно влиять на его валидность. Ведь мы не объединяем результат с предыдущим, а делаем новый независимый запрос. Почему конструкция uid=99;+select+null не работает? Я не знаю какая именно СУБД там находится. Но поскольку сервер на CentOS думаю я не ошибусь если предположу что это MySQL, причем с версией никак не ниже 4.* Мне точно известно что Apache имеет версию 2.2.15, а PHP версию 5.4.11. По-моему крайне маловероятно, что вместе с ними стали устанавливать бы такую древность как MySQL 3.* А современные версии MySQL поддерживают, как UNION, так и расщепление запросов оператором ; (точка с запятой). Следовательно, существует какая-то другая причина, по которой uid=99;+select+null не работает. Но какая? Наверно ответ на вопрос элементарный. Но я к сожалению ничего не смог нагуглить, а чтобы догадаться самому - не хватает опыта. Требуется ваша помощь.
Возможно слепая инъекция. Пробуй так: Code: uid=99 and 1=1 -- true uid=99 and 1=2 -- false Так же, полистай эту тему https://rdot.org/forum/showthread.php?t=60
Не понял как ты хочеш узнать число стлобцов: Если: Скорее всего точка с запятой просто удаляеться, перед подачей скуль-серверу, ибо иначе ты ею рвешь неизвестный тебе запрос, и должна возвращаться ошибка скуль - ошибка.
Спасибо. Про слепые инъекции не знал, весьма любопытная штука оказывается! Поскольку никаких данных, кроме злосчастной ошибки о неверном ключе сессии, скрипт не выводит - пришлось в качестве критерия угадывания использовать HTTP ошибку 504. Постепенно по одной цифре узнал версию базы данных: MySQL 5.6.21 Использовал такие запросы: Code: uid=99 or substring(@@version,1,1)=1 uid=99 or substring(@@version,1,1)=2 uid=99 or substring(@@version,1,1)=3 uid=99 or substring(@@version,1,1)=4 ... До тех пор пока не получал ошибку 504, говорящую что цифра была угадана. После чего переходил к следующей цифре. Самое интересное, что после каждого такого угадывания сервер переставал отвечать на мои запросы на 10 минут, даже самые безобидные. Через 10 мин все возвращалось на круги своя. Причину такого поведения не понимаю. С одной стороны запрос возвращает 13 миллионов строк, это серьезная нагрузка на сервер. Но с другой сам по себе сервер не уходил в даун. Я проверял это входя в игру обычным способом, через клиент. Что ж, еще раз спасибо. Ссылка тоже полезная, я пока прочитал не все, так что когда закончу - отпишусь о результатах.
Очень просто. Дело в том, что игровой движок сперва выполняет SQL-запрос и только после этого проверяет игровую сессию на валидность. Я бы на месте разработчиков игры сделал с точностью до наоборот - сперва проверял нужно ли вообще делать запрос к БД, и только потом бы его делал. Но они решили иначе В результате получаем следующую картину: Когда запрос к БД успешно выполнен - я получаю движковую ошибку "Ключ сессии неверный!", ведь для инъекции мне волей-неволей пришлось изменить значение uid. Если же запрос к БД завершается ошибкой (например при добавлении символа кавычки), я получаю HTTP ошибку с кодом 500. Таким образом, постепенно добавляя к запросу uid=99+union+select+null новые и новые null через запятую, я буду получать ошибку 500 до тех пор пока количество null не станет равнымм количеству столбцов. В этом случае я должен буду получить сообщение о неверном ключе сессии. В теории должен, на практике пока ни разу не получил... Хочу уточнить, что SQL-ошибок мне не возвращается в принципе, ни при каких условиях. Ошибок Apache тоже. У меня бывает всего 4 ситуации: 1. Если переменная uid находится в девственно неприкосновенном виде, я получаю: JSON-текст, содержащий данные из БД. 2. Если переменная uid была изменена и SQL-запрос был выполнен, я получаю: ошибку движка throwException("Ключ сессии неверный!"); 3. Если переменная uid была изменена и SQL-запрос НЕ был выполнен (например добавлен символ кавычки), я получаю: HTTP 500 Internal Server Error 4. Если переменная uid была изменена, SQL-запрос был выполнен, при этом он попытался вернуть всю таблицу целиком (в ней 13млн. строк), я получаю: HTTP 504 Gateway Time-out Других ситуаций не бывает. В идеале я хочу получить 5-тую ситуацию, когда uid была изменена, SQL-запрос был выполнен, и я получаю JSON-текст, с данными из запроса. Но пока не получается... В этом и заключается Проблема №1, описанная во втором посте данной темы. А почему символ ; должен удаляться перед подачей в БД? Ты думаешь разработчики сделали специальную функцию для этого?
Версию скуля я узнал просто в виде эксперимента. Все-таки я впервые в жизни слепую инъекцию делал, хотел понять как она работает. Юзера я хочу и сам, но никак не могу сообразить, как его выцепить из БД. Я ведь даже имен таблиц не знаю. Наверно потому что спать пора и соображалка туго работает... Завтра продолжу.
Используй and, а не or. Для, простого, быстрого перебора: Code: id=99 and ascii(substring(version(),1,1)) > 100 -- false id=99 and ascii(substring(version(),1,1)) > 50 -- true id=99 and ascii(substring(version(),1,1)) > 55 -- false id=99 and ascii(substring(version(),1,1)) < 55 -- true id=99 and ascii(substring(version(),1,1)) < 52 -- false id=99 and ascii(substring(version(),1,1)) = 53 -- true Так же, к ознакомлению https://rdot.org/forum/showthread.php?t=245 Количество полей, для union based, можно подбирать так: Code: id=99 group by 100 -- false id=99 group by 50 -- false id=99 group by 25 -- false id=99 group by 12 -- true id=99 group by 18 -- false id=99 group by 15 -- true id=-99 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 -- true Большинство вопросов и догадок, с лёгкостью, проверяются на локалхосте.
2crlf, спасибо, ваши советы бессценны! Попробую применить все, что смогу. К великому сожалению, я вынужден использовать именно or вместо and, поскольку из-за "ошибки сессии" не имею возможности возможности отличить обычный результат запроса от нулевого. Да, and удобнее. Но если я буду использовать and - я получу throwException("Ключ сессии неверный!"); в не зависимости от результатов запроса. Я писал об этом ранее. Идея бинарного поиска просто гениальна, спасибо! К сожалению совсем быстрым он не выйдет, т.к. ошибка 504, на которую я ориентируюсь, помимо прочего приводит еще и к отказу сервера на 10 минут. Угадали символ - ждем 10 минут пока сервер отвиснет, угадали еще один - опять 10 минут ждем. Если я попробую автоматизировать этот процесс, рискую привлечь нежелательное внимание админов. все-таки столь большая нагрузка на сервер - плохо. Но другого выхода пока не вижу. Вот если б заставить работать оператор LIMIT... но он работать отказывается... Не знаю почему, но такие инъекции приводят к ошибке: Code: id=99 group by 1 -- а так же group by любое другое число или набор чисел id=99 order by 1 -- то же самое id=99; Select 1 -- ошибка id=99 union select null -- снова ошибка id=99 LIMIT 0,50 -- опять ошибка На локалхосте всё вышеперечисленное выполняется на ура! А вот на атакуемом сервере не хочет. Почему-то вообще ничего кроме эндов и оров НЕ ПРОХОДИТ. Такое может быть, если инъекция производится не в хвост а в середину запроса (я ведь не знаю какой он там). Но это предположение опровергается тем, что запрос id=99; обрабатывается корректно. Кроме того, я, на всякий случай, отбрасываю ввероятный хвост при помощи --, но и это не помогает. Откуда растут ноги у проблемы понять не могу.
Можно использовать time based варианты и обойтись без or, там вывод вообще не нужен. Так же, стоит попробовать конструкцию с and, без использования комментария. Возможно, дальше идёт важная часть запроса и выборка проходит неправильно, что, по дальнейшей логике сценария, приводит к ошибке. Либо, нужно каким-то образом попытаться достроить запрос. Как вариант, считать вслепую, таким образом: Code: SELECT INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE INFO LIKE '%xxx%' LIMIT 1
2crlf, спасибо, time based работает! Даже не знал что такое возможно. Вы, как всегда, по делу советуете) Побольше бы таких людей! Появился кое-какой прогресс во взломе. Вместе с новыми успехами появились и новые проблемы, требующие решения. Подробности расскажу позже, когда появится время. Сейчас вынужден заняться другими делами.
После длительного перерыва вернулся ко взлому. Достиг следующих успехов: 1. Методом blind-инъекции + брутфорса нащупал в базе данных названия четырех таблиц. Внутри этих таблиц нащупал 23 названия столбцов. 2. С помощью того же блайнда наловчился двоичным перебором ASCII кодов, вытаскивать данные из БД. То есть любые данные из любого столбца любой таблицы, названия которых мне известны. 3. Наконец-то обошел злосчастную проблему с ключом сессии. Нашел еще одну уязвимую переменную в другом скрипте. Инъекция в данную переменную позволяет получать php и скуль-ошибки. А это уже делает иньекцию не такой слепой, как до сих пор. Я даже почерпнул из одного мануала запрос, благодаря которому можно получать данные из БД прямо внутри текста ошибок! Например отправляю: Code: var=99 or (select count(*) from (select 1 union select 2 union select 3)x group by concat(mid((select useremail from user where id=99), 1, 63), FLOOR(RAND(0)*2))) Получаю: Code: nknown err:Duplicate entry '[email protected]' for key 'group_key' Теперь о том, что не получается: Брутфорсом я нащупал едва ли сотую часть БД. И мне по зарез нужны названия других таблиц/столбцов. Но я никак не могу извлечь их напрямую из information_schema. Когда, описанным выше способом, я пытаюсь вытащить из базы данные: Code: ... select table_name from information_schema.tables limit 1 ... то получаю ошибку Code: ... error in your SQL syntax ... near '' at line 1 Долго не мог понять откуда растут ноги у проблемы. Но проведя пару десятков других запросов, выяснил странный факт. Дело в том, что я не могу использовать в инъекциях символ точки [.] Проблема даже не в том, что точка фильтруется. Просто символ точки, почему-то, срабатывает как комментарий. Причем комментарий с довольно странными свойствами. Допустим в исходном виде без инъекции var=99 Запрос: var=99 -- abra cadabra comment (скрипт выполнится корректно) Запрос: var=99 abra cadabra comment (приводит к ошибке) Code: ... error in your SQL syntax ... near 'abra cadabra comment' at line 1 Запрос: var=99 .abra cadabra comment (приводит к другой ошибке) Code: host config can not be empty! Ошибка другая, возможно не MySQL, а движковая. Набор PHP-ошибок следующих за ней тоже отличается. Более того запросы вида: var=99 -- .abra cadabra comment приведет к такой же ошибке. То есть точка (.) каким-то образом работает даже в закомментированном виде уже после двух дефисов (--) ! Что еще более удивительно, по заголовкам функций в apache-ошибках можно обнаружить, что в одну из функций попало значение нашей строковой переменной var дополненное еще двумя символами, а именно '99 -- .abra cadabra comment.7' Кстати происхождение числа 7 мне известно. Это номер игрового сервера, который был передан в том же самом HTTP-запросе, однако в совсем другой переменной, никаким модификациям не подвергавшейся. То есть, если я все правильно понимаю, на каком-то этапе обработки данных моя точка спровоцировала конкатенацию строк. Причем произошло это в какой-то другой среде, еще до того, как строка попала в среду MySQL и запрос к БД был выполнен. В пользу этого говорит так же и тот факт, что точка генерирует ошибку даже после двойного дефиса. Хотя может я ошибаюсь и конкатенация изначально была задумана движком. Поправьте если не прав. После этого я попробовал сделать тот же самый запрос, но с двумя точками. var=99 -- ..abra cadabra comment Запрос был обработан и выполнен без ошибок! var=99 ..abra cadabra comment Привел так же привел к корректной обработке без ошибок. То же самое верно для запросов с любым количеством точек от 2-х штук. Независимо от наличия или отсутствия комментариев SQL. Самое главное чтобы за самой первой точкой, следовала еще одна, причем вплотную. При этом все, что следует за двойной точкой будет в SQL-запросе отсечено, как если бы точки были однострочным комментарием. var=99 . .abra cadabra comment Если между точками вставить пробел или любой другой символ ошибка снова появится. Резюмируя проблему: Если одинарную точку, либо точку с другими символами после нее, поставить ДО инъекции, получим вышеуказанную ошибку "host config can not be empty!". Работа скрипта будет прервана так же до выполнения инъекции. Если одинарную точку, либо точку с другими символами после нее, поставить ПОСЛЕ инъекции, то сперва будет выполнена инъекция. Если инъекция привела к SQL-ошибке, получим SQL-ошибку. Если инъекция выполнена без SQL-ошибок - получим "host config can not be empty!" Если одинарную точку, либо точку с другими символами после нее, поставить ВНУТРИ инъекции (например при обращении к information_schema.tables), то вся инъекция после точки будет отсечена. Соответственно мы получим SQL-ошибку "... error in your SQL syntax ... near '...огрызок нашей инъекции до точки' at line 1" Если поставить две точки подряд в любом месте, они сработают как однострочный комментарий. Вопрос: Как получить доступ к information_scherma? Сделать это можно либо разобравшись в свойствах этой загадочной точки и заставить ее работать на нас. Что это за странный баг и с чем его едят? Либо обратится к information_scherma каким-то обходным путем, не используя точек в запросе. Существует ли такой способ? Если да, то как? Оговорюсь сразу что функция char() и HEX-строки здесь не помогут, я пробовал. Они работают со строковыми параметрами в операциях сравнения после WHERE, однако не годятся для обращения к именам таблиц в операторе FROM. Очень надеюсь на вашу помощь)
Действовать в этом случае можно как при инъекциях в MySQL <= 4, т.е. использовать банальный брут названий таблиц и колонок. Ещё можно проверить поведение, при внедрении точки, в других инъекциях. Как раз имеется блайнд в другом параметре. Это вопрос к коду и логике приложения, сложно сказать чего там могли наворотить. Можно попробовать урл кодирование, но вероятность успеха ничтожно мала. Возможность чтения локальных файлов проверялась? При положительном результате, читаем и подстраиваемся. Если отрицательный, то как по мне, выяснение будет сравни гаданию на кофейной гуще
Идея здравая, проверил. Правда я не могу ручаться, что ошибки те же самые, поскольку в другом параметре любая SQL-ошибка приводит к "500 internal Server Error" Если точка внутри запроса - получаем ошибку 500. Если точка после запроса получаем ошибку "Ключ сессии неверный!", о которой я писал еще давно. Что отнюдь не означает отсутствие ошибки "host config can not be empty!". Единственная разница в том, что точка до запроса работает как комментарий, даже в количестве 1 шт (две точки подряд ставить не обязательно). Считаете, что есть шансы найти другой уязвимый параметр, в котором проблем с точкой не будет? Если вы имеете в виду чтение с помощью команды load_file(), то я еще не до конца с ней разобрался. Она у меня все время возвращает NULL. Возможно я неверно указываю путь до файлов. Затрудняюсь определить правильный. А может быть косячу где-то еще. По правде сказать, я почти не уделял этому времени - попробовал пару раз, а затем переключился на другие задачи. Но возможно стоит углубится в изучение данного вопроса. Буду благодарен за какой-нибудь мануал по файлам. А вообще - чтение локальных файлов одна из основных целей атаки. Мне интересно почитать их содержимое и так.
А чё , права есть разве? Я чему не собираюсь читать, слишком много букв . Я вот не пойму, автору делать нечего что ли, или решил цветных тролить тонко? Если так, то не тонко Нифига
Я подумал и тоже решил что брут сперва надо попробовать. Только не делай вручную. Есть инструмент - SQLMap он автоматизирует многие рутинные операции. Например там есть опция --common-tables для версий MySQL сервера < 5.0 в которых нет информайшн_схема он пробует подобрать таблицы по словарю который храниться в файле ./txt/common-tables.txt тебе надо будет найти названия пару таблиц, по стилю именования подправить файл common-tables.txt и пробрутить. или блайнд-инжекцию запустить с кастомным пайлоадом (это SQLMap тоже может) и оставить на ночь он тебе теоретически вынет всю интересующую инфу
Если речь о правах пользователя БД, то на локалхосте права - GRANT ALL PRIVILEGES ON, на атакуемом сайте - не знаю. Но и там, и там результат - NULL. А на основании чего вы решили, что я троль? Спасибо. Когда закончу разбираться с файлами, - гляну что за зверь такой SQLMap. Я в ручную брутил. Вернее составил собственный word-лист из 600 слов, на основе моих субъективных предположений о том, как могут называться таблицы. Добавил к этому вордлисту десяток префиксов и постфиксов, тоже взятых из головы. Получилось около 10.000 слов. Брут по данному листу, нашел четыре таблицы и два десятка столбцов внутри них. SQLMap брутит намного эффективнее?