Перед вами продолжение вчерашней статьи.
Всем салют, дорогие друзья! Перед вами продолжение вчерашней статьи. Для начала мы хотели бы поблагодарить за ваш фидбек, рады, что вам зашла первая часть 🔥
Кстати, если вы по каким-либо причинам пропустили первую часть, то самое время наверстать упущенное:
Ну, а теперь давайте продолжим!
Этап 1 – поиск уязвимого места
Первое что мы попробуем сделать – ввести какой-нибудь критерий поиска, и добавим к нему кавычку:
Как видим – сайт это сломало, он выдал нам 500 ошибку:
Это уже первый признак того, что что-то пошло не так. Если мы закроем оставшуюся часть запроса комментарием, то все снова заработает:
А значит место между кавычкой и комментарием и будет нашей “точкой входа”
Небольшое "НО":
Комментарии могут быть разными для разных СУБД. Например, для MySQL подойдет “#”, но для используемой в нашем примере SQLite такое не прокатит. Для нее мы используем символ комментария “ -- “ Если будете его использовать, учитывайте что два тире должны быть обозначены пробелами с двух сторон.
Этап 2 – определение количества запрашиваемых значений
Далее нам нужно будет определить сколько значений приложение берет из БД.
Вот что это значит: так выглядит наш запрос к базе данных, который вытаскивает сущности новостей. Здесь есть оператор * (ALL), а значит результатом на запрос будут строки, содержащие в себе все три поля: id, title, text, так как именно этот набор полей содержит таблица NEWS:
Но со стороны пользователя запрос мы не видим, а значит не можем изначально определить сколько полей вытаскивает приложение.
Для того, чтобы определить это – используем такую штуку как order by <x>. Эта команда используется для сортировки, и в нашем случае может помочь выяснить то, что нам нужно. Делается это так – в нашу точку входа подставляем конструкцию:
ORDER BY X
Где x – предполагаемое количество полей. В нашем случае верное значение x = 3, так как выше мы уже определились что в нашем запросе возвращается три поля.
Если мы попробуем передать неверное x (большее, чем количество запрашиваемых полей) – то получим ошибку:
Таким образом, нехитро перебирая значения x, до тех пор пока мы не получим ошибку – мы сможем выяснить интересующее нас количество полей.
Еще раз – пока значение order by x не превышает количество запрашиваемых полей – ошибки не будет. Как только получим ошибку – значит количество будет равно x-1
Этап 3 – Определение отображения
После того, как мы выяснили какое количество полей вытаскивает наше приложение – нам следует узнать в какие именно поля на странице выводится результат какого поля. Звучит не совсем понятно, поэтому смотрим снова на пример:
Благодаря предыдущему этапу мы знаем что количество запрашиваемых полей – три. И теперь мы можем сформировать вот такую конструкцию:
union select 1,2,3 –
Попробуем выполнить такой запрос:
И что нам это дало? А вот что: мы видим, что там где выводился заголовок появилась цифра 2, а там где выводился текст новости – появилась цифра 3.
Для того, чтобы понять что вообще произошло вспоминаем что мы запрашивали из БД – id(1 – первое поле), title(2 – второе поле), text(3 – третье поле). Далее первое поле пользователю знать не обязательно, а вот значения второго и третьего разработчик вывел на страницу.
И важно знание, которое мы получили, что на страницу выводятся значения полей 2 и 3. Это важно и пригодится в следующем шаге.
Этап 4 – определение структуры БД.
Далее, при помощи уже полученных знаний, нам нужно каким-то образом выяснить общее представление о базе, в которую мы теперь имеем доступ. И этот этап очень плотно пересекается с этапом определения цели, и вот почему – все дальнейшие действия будут зависеть от того, что именно мы хотим сделать.
Целей может быть много: вытащить данные определенного пользователя или записи, слить всю базу, а может вообще дропнуть все к чертям.
И зависимости от того, какая задача перед нами стоит, и будут зависеть наши дальнейшие действия.
Давайте для начала определимся что наша цель в данном случае – вытащить данные о всех пользователях и после этого дропнуть всю таблицу с пользователями.
С чего нам стоит начать в таком случае? А вот с чего – нам нужно для начала выяснить в какой таблице хранятся данные о наших пользователях. Для нас уже очевидно что таблица называется Users, но если бы мы были в черной коробке, то это было бы не очевидно. Разработчики могли бы назвать таблицу и “users_main”, и “user”, и бог знает как еще. Поэтому, сначала нам надо узнать как называется таблица, которая содержит информацию о пользователях.
Чтобы это сделать есть несколько путей. И каждый из них будет очень сильно зависеть от того, с какой СУБД работает приложение. И это нужно выяснить.
Способы для этого опять же, разные.
Например, наше уязвимое приложение использует SQLite. У этой СУБД есть встроенная функция “sqlite_version()”, которая характерна только для нее и сработает только в этой СУБД. И теперь, как же ее выполнить? Опять же, очень просто – мы знаем что вторая и третья колонки отображаются на странице. Подставим функцию заместо поля 2, вот таким вот образом:
test’ union select 1,sqlite_version(),3 --
В итоге – на месте поля под номером два мы увидим результат выполнения функции - версию базы данных:
Отсюда мы делаем два важных вывода – во первых, СУБД, которую использует приложение, действительно – slqite, а во вторых – мы можем использовать поля 2 и 3 для вывода тех данных, которые нам нужны.
К сожалению, sqlite, в отличие от своих больших братьев, таких как например MySQL не содержит в себе системных таблиц, которые содержат информацию о базах. Связано это с тем, что каждая база представляет из себя отдельный файл.
Но, есть системная таблица, которая содержит информацию о других таблицах.
Это самый простой и в то же время удобный способ вытащить данные о том, какие таблицы есть в базе.
Итак, в SQLite есть таблица – sqlite_master. Она то как раз и содержит всю нужную нам информацию.
Вот так она выглядит:
Как видим, эта табличка содержит много полезной информации о таблицах, их именах, и даже sql-команды для создания этих таблиц.
Конкретно сейчас нас интересуют имена таблиц. Для того чтобы вытащить их, нам нужно использовать следующий sql-запрос:
SELECT name FROM sqlite_master;
Теперь посмотрим как подставить этот запрос в наш пейлоад, так чтобы все подошло и сложилось как конструктор. Здесь нам и поможет union select.
Вспоминаем какие значения у нас выводятся на страницу – это 2 и 3. Значит заместо этих значений мы можем вывести нужные нам поля.
test’ union select 1,name,3 from sqlite_master --
В итоге получаем имена таблиц из базы:
Такие таблицы, содержащие информацию о других таблицах, существуют во всех популярных СУБД. Проблема в том, что администратор может (а по сути и должен) ограничить доступ к этим таблицам.
И в таком случае придется немного потыкаться вслепую. Начнем с предположения. Нам нужны данные пользователей, значит логично будет предположить, что таблица называется схожим образом. Предположим что имя таблицы – user.
Как нам проверить правильность предположения? Давайте пойдем следующим образом – при помощи uniuon select мы можем выбирать данные из разных таблиц, поэтому попробуем добавить в конец нашего union предполагаемое имя таблицы. И теперь наш запрос будет выглядеть вот так:
union select 1,2,3 from user --
Если мы попробуем выполнить такой запрос – сервер вернет нам ошибку:
Это происходит так как такой таблицы не существует, а значит наше предположение неверно.
Идем дальше. Предположим, что таблица называется Users. Делаем примерно то же самое, только меняем имя таблицы:
И запрос отрабатывает. Ошибки мы не получаем. И хоть никаких данных мы из таблицы не получили (пока что) – сервер ошибку не вернул, а значит такая таблица существует.
Далее мы можем переходить к непосредственно выполнению первого этапа нашего плана. А именно вытаскивать данные пользователей. Здесь опять же встает проблема, которая решается почти точно так же как и предыдущая – либо методом проб и ошибок, либо через метаданные, а именно – вычисление полей.
Как узнать поля? Вспомним нашу таблицу с метаданными. Если вы еще раз посмотрите на скрин – увидите что у нас там есть поле sql, которое содержит команду для создания такой таблицы. И эта команда содержит в том числе и имена полей. Попробуем вывести в заголовок имя таблицы (name), а в описание – эту самую команду из sql:
test’ union select 1,name,sql from sqlite_master --
И вот что мы увидим в результате:
Ну и после того как мы узнали всю необходимую информацию – можем переходить к финальному этапу. Сделаем это при помощи следующего запроса:
test‘ union select 1,name,password from Users --
И по итогу получаем список всех пользователей и паролей. На месте заголовка – имя пользователя, на месте текста – пароль. Первая задача выполнена:
Пример, разумеется, сильно упрощен. Чувствительные данные, по типу паролей, в базах данных всегда хранятся в зашифрованном виде. Поэтому, скорее всего, если вам удастся провернуть что-то подобное, вытащить пароли так просто не получится, так как заместо них вы вытащите хеши паролей, которые потом придется брутить.
Теперь вернемся ко второй поставленной задаче – снести таблицу пользователей. Здесь все одновременно проще и сложнее.
Во первых, вся та конструкция, что мы выстраивали в несколько этапов выше нам не пригодится. Она нужна именно для ручного извлечения данных. Для удаления же мы будем использовать следующую конструкцию:
test’; drop table Users --
И вот что теперь происходит при попытке обратится к таблице пользователи:
Вот и все. Вторая задача выполнена, мы снесли таблицу пользователей.
Разумеется, на этом возможности SQL-инъекций не заканчиваются.
Но, если вы хотите понять эту атаку в совершенстве – сначала придется в совершенстве понять SQL. Так как именно от понимания того, каким хитрым запросом можно получить желаемый результат, будет зависеть дальнейший успех. Ну и держать руку на пульсе последний технологий, связанных с реляционными БД, конечно, тоже придется.
Немного протираем розовые очки. Примеры, приведенные выше имеют место быть, но вероятность встретить что-то такое сейчас довольно мала.
И в первую очередь потому что существует такая штука как слепая инъекция (blind sql-injection)
Что это такое и как это усложняет нам жизнь? А вот как:
Если вы вспомните примеры, то поймете, что большую роль играло возвращение ошибок сервером. Каждый раз, когда сервере падал с ошибкой, мы понимали что что-то не так и надо что-то менять.
А что, если сервер не выдавал бы ошибку? Например, в случае с поиском, представьте что заместо 500 сервер бы просто вел бы себя, как будто поиск не увенчался успехом, то есть просто возвращал бы страницу, на которой бы было бы написано что ничего не найдено. Причем возвращал бы ее всегда.
Как в таком случае можно было бы определить большинство векторов развития атаки, да и в принципе определить что страница уязвима. Если вы помните, принципом определения уязвимости страницы была ошибка в ответ на кавычку в запросе.
И вот здесь уже действительно придется строить из себя слепого котенка. Но, как показывает практика – и на такие виды находится управа. Например, несмотря на то, что ошибка возвращаться не будет, можно понять что перед нами уязвимая страница, если подставить кавычку и комментарий в запрос, то есть, создав пустую инъекцию:
Страница в этом случае проигнорирует все эти символы и запрос отработает как надо. Это уже показатель того, что спец символы попали в запрос.
Автоматизация при помощи SQLMAP
Ну и по классике – после того, как прошлись по ручным основам, можно перейти и к автоматизации. Понятное дело, что ручная раскрутка таких инъекций дело весьма утомительное. Поэтому, пусть вкалывают роботы.
Для автоматизации SQL-инъекций есть замечательный, зарекомендовавший себя среди как мамкиных “хакеров”, так и среди серьезных дядь, инструмент – sqlmap.
Эта штуковина по сути мейнстрим среди инструментов подобного рода и умеет очень многое. Предлагаю для начала опробовать ее на примерах, которые я приводил выше – авторизации и поиске. Заодно и разберемся как настраивать инструмент, как натравливать на цель и что вообще со всем этим делать.
Сразу начну с того, что для того, чтобы нормально работать с инструментом понадобятся запросы, которые летят на сервер. Для их перехвата и удобной обработки есть много разных инструментов. Даже стандартная панель разработчика в браузере подойдет. Но я все же предпочту чуть более подходящий для этого Burp.
Итак, начнем со страницы поиска, так как она дает больше всего возможностей. Перехватим запрос, который летит к серверу после нажатия на кнопку search.
У нас здесь только один параметр. Для инструмента нужны, по сути, всего две вещи – URL и data.
Попробуем запустить инструмент следующей командой:
sqlmap -u [URL]http://127.0.0.1:8081/search[/URL] –data="search=test"
Инструмент проводит некоторые тесты и...
Ничего не находит. Но мы ведь точно знаем, что страница уязвима, как же так получилось? Однако, рано расстраиваться. Если мы посмотрим варнинги, то увидим некоторые полезные советы, которые утилита дает нам, на случай если мы не хотим угомониться.
Один из таких советов – через ключ dbms указать СУБД. Попробуем проверить что будет, если мы укажем sqlite:
sqlmap -u [URL]http://127.0.0.1:8081/search[/URL] --data="search=test" –dbms=sqlite
Здесь мы добиваемся уже большего успеха – инструмент подтверждает что база SQLite, и что, хоть мы и получили пачку ошибок от сервера, успеха мы добились.
Результаты можно посмотреть в локальной папке инструмента, о чем нам так же любезно сообщает утилита:
Отлично. Первый успех есть – попробуем вытащить имена баз данных. Для этого в sqlmap используется ключ –dbs
Добавляем его к команде:
sqlmap -u [URL]http://127.0.0.1:8081/search[/URL] --data="search=test" --dbms=sqlite -dbs
И тут инструмент снова вежливо подсказывает в ворнинге, что так не получится – sqlite имеет определенные особенности в том, что база изначально представляет из себя отдельный файл, поэтому так просто вытащить их не получится. Но тут же sqlmap советует нам, чтобы мы вытаскивали сразу таблицы. Что же, попробуем так и сделать:
sqlmap -u [URL]http://127.0.0.1:8081/search[/URL] --data="search=test" --tables –dbms=sqlite
И бинго – утилита вытаскивает нам имена таблиц из базы:
Ну и далее дело техники – вытащить данные из таблицы можно указав саму таблицу через ключ -T и добавив –dump:
sqlmap -u [URL]http://127.0.0.1:8081/search[/URL] --data="search=test" -T Users –dbms=sqlite
Эта команда вернет нам содержимое таблицы.
Естественно это далеко не все возможности инструмента. Он умеет гораздо больше, и если описывать все его возможности – статья рискует превратиться в список на рулоне. Статья и так состоит из 2-х частей и получилась очень объёмной. Я уверен, что в ней достаточно информации, чтобы понять куда копать, если возникнет желание разобраться глубже.
Заключение
SQL-инъекций за последнее время стало гораздо меньше, т.к. большое количество библиотек и фреймворков под самые разные языки со встроенной защитой этому способствуют.
Однако это вовсе не значит что они исчезли. Если посмотреть те же отчеты с Bug Bounty программ, то можно найти много интересного и далеко не классического.
На этом у меня все! Желаю продуктивной охоты❤️
Comments