Введение
Всех привествую.
Начать хотелось бы с небольшого отступления. Возможно среди читателей есть те, кто хоть немного следит за тем что я пишу, и в этом случае вы могли заметить, что все мои статьи ранее обьединяло то, что каждая конкретная статья была разбором какой-то одной определенной веб-уязвимости. Началось все около года назад со статьи о XSS, а крайняя статья была о NoSQL-инъекциях.
Все эти статьи были рассчитаны в первую очередь на новичков и имели плюс-минус одну структуру – описание, теория, практика, автоматизация.
Однако, как по мне, такой формат себя немного исчерпал. Поэтому, эта статья будет последней в таком стиле. Но, это совсем не значит что я прекращу писать в целом. Я вижу неплохой фидбэк, и в основном у меня получается раскрыть ту тему, которую я задумал. И все-таки, похоже, что пора начинать переходить к более серьезным вещам) Так что, в ближайшее время я планирую немного изменить подачу материла и посмотреть что из этого выйдет.
Основной темой все так же останутся веб-уязвимости, но изменится сама структура и темы для статей.
Теперь по поводу самой статьи – сама тема стара как мир, и я до последнего не хотел ее затрагивать, так как тема SQL-иньекций казалась мне достаточно избитой, и большое количество материала именно об этой уязвимости лишь укрепляли это мнение.
Однако, я все же решил написать – и вот почему:
Во-первых, раз уж я решил описывать большинство веб-уязвимостей предпочтительно для новичков – обойти эту тему в некотором смысле было бы кощунством. Да, материала в сети много, на как известно – вся разница в подаче. Может быть именно эта статья поможет кому-то наконец разложить все по полочкам у себя в голове, касательно этой уязвимости.
Во-вторых, мне эта статья показалась хорошей возможностью поставить точку с серии “веб-уязвимости для новичков”, собрав таким образом некоторую полку в библиотеке с описанием основных базовых веб-уязвимостей, на которых по сути все и строится.
Так что, встречайте – заключающая статья в этой серии, SQL-инъекции. Что это, с чем едят, и что с этим можно поделать.
Посмотрим на виды иньекций, на то как их можно автоматизировать, что можно добиться и как это все выглядит.
Основная часть
Как бы я не хотел затрагивать стартовые темы по типу “чем опасно составление запросов конкатенацией” или “что означает кавычка в параметре” – боюсь что без этого не обойтись. Все таки, конкретно эта статья расчитанна не тех людей, которые о SQL-иньекциях ранее не слышали вообще, да и в целом - раз уж собрались идти по порядку, пойдем по порядку. Здесь пишу для совсемеще не смешариков новичков.
Понятно что затронуть тут все, начиная от основ и до последних уровней не выйдет, иначе статья превратится в книгу, поэтому тут разбираемся только с основной базой, на которой все строится. А копать глубже - кто знает, может уже в рамках нового цикла статей будет что-то про продвинутые иньекции)
Общее описание.
Итак, давайте рассмотрим как вообще допускаются классические SQL-инъекции и как это все вообщем выглядит.
У нас есть приложение, которое работает с данными. Предположим, это какая-нибудь соцсеть. В этом случае, приложению обязательно необходимо хранить данные как минимум одной бизнес-сущности – пользователя, так как вокруг этих персонажей и строится все приложение. Их пол, возраст, имя и фамилия, емейлы, пароли и другая информация – все это необходимо где-то хранить.
Для этого и существую базы данных – некое приложение, которое существует ровно для того, чтобы хранить данные других приложений.
Если сразу смотреть по сути, чтобы не путаться потом в терминологии – База Данных, это общее название для некой абстрактной хреновины, внутри которой хранятся данные.
Но еще есть такая штука как СУБД или Система Управления Базами Данных. И вот это уже по сути имя собственное. СУБД и представляет из себя определенную программу, которая отвечает за то, чтобы доступ к данным можно было легко обеспечить, чтобы туда не лез кто попало и прочие важные функции
Проводя аналогию – База Данных, это набор книг в библиотеке, а СУБД – это директор библиотеки, который раздает указания кому какую книгу выдать, где кому ничего не выдавать, что можно передать в детский дом и что в школу, а еще какие книги вообще можно сжечь.
И при этом библиотеки могут быть разных названий – Пушкинская, Центральная, Библиотека МГУ и т.д. Но функции у них плюс минус одни и те же и каждая из них - библиотека. Надеюсь, аналогия ясна.
Сами же базы данных бывают двух типов -реляционные и нереляционные. Конкретно здесь нас будут интересовать только первые, релиционные или как раз же SQL базы данных. Про другие, нереляционные (NoSQL) Бд и их приколы я тоже уже делал материал, найти его можно здесь. Но, если вы новичок, и абсолютно все что я сейчас здесь описываю вам в новинку (мало ли) – лучше сначала ознакомьтесь с текущей статьей.
Так что же представляют из себя релиционные БД ? По сути, каждая база - это просто набор таблиц, содержащих в себе набор данных о каком-то одном определенном обьекте.
Таблицы могут быть связаны между собой, для того чтобы привязать к одной сущности данные других – например к пользователю список его постов.
Для того чтобы работать с этими данными используется особенный язык запросов.
Он так и называется – SQL. Его синтаксис чем-то напоминает обычный английский в приказном тоне.
Например, стандартная команда по типу
достаточно просто и интуитивно переводится как: выбери мне все записи из пользователей где имя – Джон. То есть, выбрать в табличке всех Джонов и вернуть их данные. Ничего сложного.
Так в чем же проблема и как возникает сама уязвимость, о которой так много говорят ?
Суть самой атаки заключается в том, что тот самый запрос, через который приложение обращается к базе, при определенном раздолбайстве разработчика пользователь может начать контролировать. Рассмотрим как это присходит на двух самых популряных примерах.
Обход авторизации
Представим ситуацию – у нас есть приложение, в котором реализована аутентификация по логину и паролю:
Все стандартно. Для того, чтобы понять где здесь может быть SQL-иньекция, нужно себе представить как вообще происходит процесс аутентификации.
А происходит он примерно так:
Проблемы, в случае SQL-иньекций возникают на этапе первых двух пунктов. Как вообще проверить наличие пользователя в базе ? Логично, что нужно составить запрос, который вернет пользователя с нужным логином и паролем. То есть, запрос будет выглядеть примерно так:
Где user – логин, а password – пароль, введеные пользователем. Ну и казалось бы, в чем проблема ? Берем от пользователя данные и просто подставляем их в запрос., примерно вот таким вот образом:
Но, проблема есть. И вот в чем она заключается:
Представьте себе ситуацию, что пользователь введет не просто свое имя, а добавит в конец свого логина символ ‘
И если мы подставим его логин напрямую в запрос, запрос станет выглядеть вот так:
Видите ? Кавычки стало две – одну добавил пользователь, а вторую мы добавили сами, когда создавали строку запроса.
Язык SQL очень чувствителен к таким вещам и такой прикол с лишней кавычкой в запросе ему не понравится. Это вызовет ошибку запроса к бд, запрос выполнен не будет, а это в свою очередь приведет к ошибке на сервере:
А значит, первое что мы можем провернуть при помощи такой атаки – вызывать ошибку на стороне сервера. Это прикольно, но пользы как таковой не несет.
А что если мы подставим в запрос не только кавычку ?
В языке SQL, как и в ЯП, есть, например, знаки комментариев, которые делают часть кода невидимой для обработчика.
А теперь представим что мы добавляем вот такой символ вместе с кавычкой:
test’ --
Таким образом мы получаем уже вот такой запрос. И что же будет, если мы попытаемся выполнить запрос?
Неожиданно мы авторизируемся:
Но как так вышло, если мы ввели только имя пользователя, а пароль был неверным? Еще раз взглянем на запрос – по сути, наш символ кавычки сначала закрыл ту секцию, куда попадал логин, а затем наш комментарий отбросил весь оставшийся код вот таким образом:
Вытворяем всякие штуки с базой
Но приложения ходят в базу не только для того, чтобы вытащить оттуда данные пользователей для авторизации.
Второй популярный пример – поиск. Предположим у нас есть прилжение, которое хранит новости. И по новостям реализован поиск. Достаточно тривиальный пример:
Для начала попробуем представить, как будет выглядеть запрос в таком случае. А выглядеть он будет примерно так:
Или, если провести аналогию – выбрать все значения из таблицы news, где в заголовке будет присутствовать слово text в любой позиции.
Логично, что уязвимым местом в данном случае будет выступать text, так как его задает пользователь, когда ищет новости
Казалось бы – ну и что здесь можно сделать ? Если попробовать подставить иньекцию из прошлого примера, то мы ничего не добьемся. Параметр то один и закрывать что-то еще нам без надобности. Но на самом деле, на этом этапе все только начинается.
Давайте представим что мы подставили иньекцию из прошлого примера. Запрос тогда будет выглядеть так:
А теперь обратите внимание на выделенную желтым область. Перед ней закрывается параметр, а после нее стоит комментарий, отбрасывающий весь остальной запрос.
И вот как мы можем это использовать – в языке SQL в одном запросе мы можем записать несколько команд – например вытащить записи из одной таблицы, а потом сразу из другой.
Такие команды в рамках одного запроса разделяются точкой с запятой. Именно поэтому она стоит в конце каждой команды.
Представим что мы подставим в иньекцию следующий набор символов:
‘;#
И наш запрос примет вид:
И снова внимание на выделенную желтым область – теперь мы можем записывать туда любой запрос и он будет выполнен. Таким обаразом мы можем творить с базой все что захотим.
Разберемся по порядку. Обычно вся атака присходит в несколько этапов:
Этап 1 – поиск уязвимого места
Первое что мы попробуем сделать – ввести какой-нибудь критерий поиска, и добавим к нему кавычку:
Как видим – сайт это сломало, он выдал нам 500 ошибку:
Это уже первый признак того, что что-то пошло не так. Если мы закроем оставшуюся часть запроса комментарием, то все снова заработает:
А значит место между кавычкой и коментарием и будет нашей “точкой входа”
Небольшое "но"
Этап 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-запрос:
Теперь посмотрим как подставить этот запрос в наш пейлоад, так чтобы все подошло и сложилось как конструктор. Здесь нам и поможет 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.
Вот так выглядит наша цель:
Нас здесь интересует только одна последняя строчка, которая содержит данные типа application/form, которые передаются post запросом.
У нас здесь только один параметр. Для инструмента нужны вообще по сути две вещи – URL и data. Data – это та самая строка, которую мы только что получили.
Попробуем запустить инструмент следующей командой:
Инструмент проводит некоторые тесты и...
Ничего не находит. Но мы ведь точно знаем что страница уязвима, как же так получилось ? Однако, рано расстраиваться. Если мы посмотрим варнинги, то увидим некоторые полезные советы, которые утилита дает нам, на случай если мы не хотим угомониться.
Один из таких советов – через ключ dbms указать субд. Попробуем проверить что будет, если мы укажем sqlite:
Здесь мы добиваемся уже большего успеха – инструмент подтверждает что база SQLite, и что, хоть мы и получили пачку ошибок от сервера, успеха мы добились.
Результаты можно посмотреть в локальной папке инструмента, о чем нам так же любезно сообщает утилита:
Отлично. Первый успех есть – попробуем вытащить имена баз данных. Для этого в sqlmap используется ключ –dbs
Добавляем его к команде:
И
тут инструмент снова вежливо подсказывает в ворнинге, что так не получится – sqlite имеет определенные особенности в том, что база изнгачально представляет из себя отдельный файл, поэтому так просто вытащить их не получится. Но тут же sqlmap советует нам, чтобы мы вытаскивали сразу таблицы. Чтож, попробуем так и сделать:
И бинго – утилита вытаскивает нам имена таблиц из базы:
Ну и далее дело техники – вытащить данные из таблицы можно указав саму таблицу через ключ -T и добавив –dump:
Эта команда вернет нам содержимое таблицы.
Естественно это далеко не все возможности инструмента. Он умеет гораздо больше, и если описывать все его возможности – статья рискует превратиться в список на рулоне. Однако, статья итак получилась очень обьемной. Я думаю информации в ней достаточно чтобы понять куда копать, если возникнет желание разобраться глубже.
Заключение
Как я и сказал в начале статьи – sql-иньекций стало гораздо меньше за последнее время. Большое количество библиотек и фреймворков под самые разные языки со встроенной защитой этому способствуют.
Однако это не значит что они исчезли совсем. Если посмотреть те же отчеты с bugbounty программ, можно найти много интересного и далеко не классического.
Впрочем, это уже тема за пределами сегодняшней статьи об основах (а может и тема для отдельной, кто знает)
Целью этой статьи было плюс минус сгруппировать общую информацию об иньекциях, чтобы люди, которые только хотят разобраться, могли понять основы и выяснить куда стоит копать дальше в случае чего.
Ну а я на этом заканчиваю статью об SQL-инъекциях, а заодно и закрываю этим цикл статей “веб-уязвимости для новичков”. Ну а дальше – больше. Увидимся.
Всем добра !
© Urob0ros, специально для форума xss.pro
Всех привествую.
Начать хотелось бы с небольшого отступления. Возможно среди читателей есть те, кто хоть немного следит за тем что я пишу, и в этом случае вы могли заметить, что все мои статьи ранее обьединяло то, что каждая конкретная статья была разбором какой-то одной определенной веб-уязвимости. Началось все около года назад со статьи о XSS, а крайняя статья была о NoSQL-инъекциях.
Все эти статьи были рассчитаны в первую очередь на новичков и имели плюс-минус одну структуру – описание, теория, практика, автоматизация.
Однако, как по мне, такой формат себя немного исчерпал. Поэтому, эта статья будет последней в таком стиле. Но, это совсем не значит что я прекращу писать в целом. Я вижу неплохой фидбэк, и в основном у меня получается раскрыть ту тему, которую я задумал. И все-таки, похоже, что пора начинать переходить к более серьезным вещам) Так что, в ближайшее время я планирую немного изменить подачу материла и посмотреть что из этого выйдет.
Основной темой все так же останутся веб-уязвимости, но изменится сама структура и темы для статей.
Теперь по поводу самой статьи – сама тема стара как мир, и я до последнего не хотел ее затрагивать, так как тема SQL-иньекций казалась мне достаточно избитой, и большое количество материала именно об этой уязвимости лишь укрепляли это мнение.
Однако, я все же решил написать – и вот почему:
Во-первых, раз уж я решил описывать большинство веб-уязвимостей предпочтительно для новичков – обойти эту тему в некотором смысле было бы кощунством. Да, материала в сети много, на как известно – вся разница в подаче. Может быть именно эта статья поможет кому-то наконец разложить все по полочкам у себя в голове, касательно этой уязвимости.
Во-вторых, мне эта статья показалась хорошей возможностью поставить точку с серии “веб-уязвимости для новичков”, собрав таким образом некоторую полку в библиотеке с описанием основных базовых веб-уязвимостей, на которых по сути все и строится.
Так что, встречайте – заключающая статья в этой серии, SQL-инъекции. Что это, с чем едят, и что с этим можно поделать.
Посмотрим на виды иньекций, на то как их можно автоматизировать, что можно добиться и как это все выглядит.
Основная часть
Как бы я не хотел затрагивать стартовые темы по типу “чем опасно составление запросов конкатенацией” или “что означает кавычка в параметре” – боюсь что без этого не обойтись. Все таки, конкретно эта статья расчитанна не тех людей, которые о SQL-иньекциях ранее не слышали вообще, да и в целом - раз уж собрались идти по порядку, пойдем по порядку. Здесь пишу для совсем
Понятно что затронуть тут все, начиная от основ и до последних уровней не выйдет, иначе статья превратится в книгу, поэтому тут разбираемся только с основной базой, на которой все строится. А копать глубже - кто знает, может уже в рамках нового цикла статей будет что-то про продвинутые иньекции)
Общее описание.
Итак, давайте рассмотрим как вообще допускаются классические SQL-инъекции и как это все вообщем выглядит.
У нас есть приложение, которое работает с данными. Предположим, это какая-нибудь соцсеть. В этом случае, приложению обязательно необходимо хранить данные как минимум одной бизнес-сущности – пользователя, так как вокруг этих персонажей и строится все приложение. Их пол, возраст, имя и фамилия, емейлы, пароли и другая информация – все это необходимо где-то хранить.
Для этого и существую базы данных – некое приложение, которое существует ровно для того, чтобы хранить данные других приложений.
Если сразу смотреть по сути, чтобы не путаться потом в терминологии – База Данных, это общее название для некой абстрактной хреновины, внутри которой хранятся данные.
Но еще есть такая штука как СУБД или Система Управления Базами Данных. И вот это уже по сути имя собственное. СУБД и представляет из себя определенную программу, которая отвечает за то, чтобы доступ к данным можно было легко обеспечить, чтобы туда не лез кто попало и прочие важные функции
Проводя аналогию – База Данных, это набор книг в библиотеке, а СУБД – это директор библиотеки, который раздает указания кому какую книгу выдать, где кому ничего не выдавать, что можно передать в детский дом и что в школу, а еще какие книги вообще можно сжечь.
И при этом библиотеки могут быть разных названий – Пушкинская, Центральная, Библиотека МГУ и т.д. Но функции у них плюс минус одни и те же и каждая из них - библиотека. Надеюсь, аналогия ясна.
Сами же базы данных бывают двух типов -реляционные и нереляционные. Конкретно здесь нас будут интересовать только первые, релиционные или как раз же SQL базы данных. Про другие, нереляционные (NoSQL) Бд и их приколы я тоже уже делал материал, найти его можно здесь. Но, если вы новичок, и абсолютно все что я сейчас здесь описываю вам в новинку (мало ли) – лучше сначала ознакомьтесь с текущей статьей.
Так что же представляют из себя релиционные БД ? По сути, каждая база - это просто набор таблиц, содержащих в себе набор данных о каком-то одном определенном обьекте.
Таблицы могут быть связаны между собой, для того чтобы привязать к одной сущности данные других – например к пользователю список его постов.
Для того чтобы работать с этими данными используется особенный язык запросов.
Он так и называется – SQL. Его синтаксис чем-то напоминает обычный английский в приказном тоне.
Например, стандартная команда по типу
SQL:
SELECT ALL FROM USERS WHERE name = ‘John’;
достаточно просто и интуитивно переводится как: выбери мне все записи из пользователей где имя – Джон. То есть, выбрать в табличке всех Джонов и вернуть их данные. Ничего сложного.
Так в чем же проблема и как возникает сама уязвимость, о которой так много говорят ?
Суть самой атаки заключается в том, что тот самый запрос, через который приложение обращается к базе, при определенном раздолбайстве разработчика пользователь может начать контролировать. Рассмотрим как это присходит на двух самых популряных примерах.
Обход авторизации
Представим ситуацию – у нас есть приложение, в котором реализована аутентификация по логину и паролю:
Все стандартно. Для того, чтобы понять где здесь может быть SQL-иньекция, нужно себе представить как вообще происходит процесс аутентификации.
А происходит он примерно так:
- Пользователь передает логин и пароль
- Приложение сверяет эти данные с записями в базе
- Если есть запись, в которой пристутсвуют этот логин и пароль – программа разрешает доступ
- Если записи нет – нет и доступа
Проблемы, в случае SQL-иньекций возникают на этапе первых двух пунктов. Как вообще проверить наличие пользователя в базе ? Логично, что нужно составить запрос, который вернет пользователя с нужным логином и паролем. То есть, запрос будет выглядеть примерно так:
SQL:
SELECT * FROM Users WHERE login=’user’ AND password=’1234’;
Где user – логин, а password – пароль, введеные пользователем. Ну и казалось бы, в чем проблема ? Берем от пользователя данные и просто подставляем их в запрос., примерно вот таким вот образом:
Но, проблема есть. И вот в чем она заключается:
Представьте себе ситуацию, что пользователь введет не просто свое имя, а добавит в конец свого логина символ ‘
И если мы подставим его логин напрямую в запрос, запрос станет выглядеть вот так:
SQL:
SELECT ALL FROM Users WHERE login=’user’’ AND password=’1234’;
Видите ? Кавычки стало две – одну добавил пользователь, а вторую мы добавили сами, когда создавали строку запроса.
Язык SQL очень чувствителен к таким вещам и такой прикол с лишней кавычкой в запросе ему не понравится. Это вызовет ошибку запроса к бд, запрос выполнен не будет, а это в свою очередь приведет к ошибке на сервере:
А значит, первое что мы можем провернуть при помощи такой атаки – вызывать ошибку на стороне сервера. Это прикольно, но пользы как таковой не несет.
А что если мы подставим в запрос не только кавычку ?
В языке SQL, как и в ЯП, есть, например, знаки комментариев, которые делают часть кода невидимой для обработчика.
А теперь представим что мы добавляем вот такой символ вместе с кавычкой:
test’ --
Таким образом мы получаем уже вот такой запрос. И что же будет, если мы попытаемся выполнить запрос?
Неожиданно мы авторизируемся:
Но как так вышло, если мы ввели только имя пользователя, а пароль был неверным? Еще раз взглянем на запрос – по сути, наш символ кавычки сначала закрыл ту секцию, куда попадал логин, а затем наш комментарий отбросил весь оставшийся код вот таким образом:
SQL:
SELECT ALL FROM Users WHERE login=’user’ -- ’ AND password=’1234’;
Вытворяем всякие штуки с базой
Но приложения ходят в базу не только для того, чтобы вытащить оттуда данные пользователей для авторизации.
Второй популярный пример – поиск. Предположим у нас есть прилжение, которое хранит новости. И по новостям реализован поиск. Достаточно тривиальный пример:
Для начала попробуем представить, как будет выглядеть запрос в таком случае. А выглядеть он будет примерно так:
SQL:
SELECT ALL FROM news WHERE title LIKE '%text%';
Или, если провести аналогию – выбрать все значения из таблицы news, где в заголовке будет присутствовать слово text в любой позиции.
Логично, что уязвимым местом в данном случае будет выступать text, так как его задает пользователь, когда ищет новости
Казалось бы – ну и что здесь можно сделать ? Если попробовать подставить иньекцию из прошлого примера, то мы ничего не добьемся. Параметр то один и закрывать что-то еще нам без надобности. Но на самом деле, на этом этапе все только начинается.
Давайте представим что мы подставили иньекцию из прошлого примера. Запрос тогда будет выглядеть так:
SQL:
SELECT ALL FROM news WHERE title LIKE ‘%text’ #%';
А теперь обратите внимание на выделенную желтым область. Перед ней закрывается параметр, а после нее стоит комментарий, отбрасывающий весь остальной запрос.
И вот как мы можем это использовать – в языке SQL в одном запросе мы можем записать несколько команд – например вытащить записи из одной таблицы, а потом сразу из другой.
Такие команды в рамках одного запроса разделяются точкой с запятой. Именно поэтому она стоит в конце каждой команды.
Представим что мы подставим в иньекцию следующий набор символов:
‘;#
И наш запрос примет вид:
SQL:
SELECT ALL FROM news WHERE title LIKE ‘%text’; #%';
И снова внимание на выделенную желтым область – теперь мы можем записывать туда любой запрос и он будет выполнен. Таким обаразом мы можем творить с базой все что захотим.
Разберемся по порядку. Обычно вся атака присходит в несколько этапов:
- Определение уязвимого места и расчистка промежутка
- Определение количества полей, которые приложение запрашивает из БД
- Определение отображения полей из предыдущего пункта на странице
- Определение цели и структуры таблицы
- Непосредственно, иньекция
Этап 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-запрос:
SQL:
SELECT name FROM sqlite_master;
Вспоминаем какие значения у нас выводятся на страницу – это 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.
Вот так выглядит наша цель:
Нас здесь интересует только одна последняя строчка, которая содержит данные типа application/form, которые передаются post запросом.
У нас здесь только один параметр. Для инструмента нужны вообще по сути две вещи – URL и data. 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Эта команда вернет нам содержимое таблицы.
Естественно это далеко не все возможности инструмента. Он умеет гораздо больше, и если описывать все его возможности – статья рискует превратиться в список на рулоне. Однако, статья итак получилась очень обьемной. Я думаю информации в ней достаточно чтобы понять куда копать, если возникнет желание разобраться глубже.
Заключение
Как я и сказал в начале статьи – sql-иньекций стало гораздо меньше за последнее время. Большое количество библиотек и фреймворков под самые разные языки со встроенной защитой этому способствуют.
Однако это не значит что они исчезли совсем. Если посмотреть те же отчеты с bugbounty программ, можно найти много интересного и далеко не классического.
Впрочем, это уже тема за пределами сегодняшней статьи об основах (а может и тема для отдельной, кто знает)
Целью этой статьи было плюс минус сгруппировать общую информацию об иньекциях, чтобы люди, которые только хотят разобраться, могли понять основы и выяснить куда стоит копать дальше в случае чего.
Ну а я на этом заканчиваю статью об SQL-инъекциях, а заодно и закрываю этим цикл статей “веб-уязвимости для новичков”. Ну а дальше – больше. Увидимся.
Всем добра !
© Urob0ros, специально для форума xss.pro
Последнее редактирование: