Sqli
Содержание:
- SQL injection classification
- How to Prevent an SQL Injection
- How to Prevent SQL Injections (SQLi) – Generic Tips
- Эксплуатации SQL-инъекции
- How SQL Injection Works
- SQL-инъекция для новичков
- Возможные проблемы при сканировании sqlmap
- Неэффективные способы защиты от SQL-инъекций
- Экранирование значений
- Демонстрация атаки с использованием SQL-инъекции
- Приведение к целочисленному типу
SQL injection classification
-
union based sqli
-
error based sqli — you can see database error output
-
blind sqli — you can see some differences between successfull query and unsuccessfull:
- any visible in the page source code differences (different numbers of in document, different news posts depending on querry, etc)
-
you may have an opportunity to destinguish types of database errors, but not its content, so you can not return data in error
this is a mutated vector called error based blind sqli
-
double blind sqli (time-based) — there is absolutely no other means to destinguish successfull and unsuccessfull query, but you can use or or some hard mathematical computation to make successfull query work significantly longer.
Typical sql-injection workflow:
- detect accessible databases
- found table names
- found amount of columns, names and types of columns
- found contence of tables
Typical points of injection:
- select from where by limit
- insert into (a, , c) values (1, , 3), (1, 2, 3)
- update set = where
- delete from where = by limit
SQL injection mitigation:
- Use prepared statements
- SQL injection Prevention Cheat Sheet
This mitigation does work if implemented correctly but it is NOT correct mitigation:
- typecast expected integer values
- escape expected strings with mysql_real_escape_string and embed them with quotes
Any differences in databases syntax or semantics help defining database type.
How to Prevent an SQL Injection
The only sure way to prevent SQL Injection attacks is input validation and parametrized queries including prepared statements. The application code should never use the input directly. The developer must sanitize all input, not only web form inputs such as login forms. They must remove potential malicious code elements such as single quotes. It is also a good idea to turn off the visibility of database errors on your production sites. Database errors can be used with SQL Injection to gain information about your database.
If you discover an SQL Injection vulnerability, for example using an Acunetix scan, you may be unable to fix it immediately. For example, the vulnerability may be in open source code. In such cases, you can use a web application firewall to sanitize your input temporarily.
To learn how to prevent SQL Injection attacks in the PHP language, see: Preventing SQL Injection Vulnerabilities in PHP Applications and Fixing Them. To find out how to do it in many other different programming languages, refer to the Bobby Tables guide to preventing SQL Injection.
How to Prevent SQL Injections (SQLi) – Generic Tips
Preventing SQL Injection vulnerabilities is not easy. Specific prevention techniques depend on the subtype of SQLi vulnerability, on the SQL database engine, and on the programming language. However, there are certain general strategic principles that you should follow to keep your web application safe.
To keep your web application safe, everyone involved in building the web application must be aware of the risks associated with SQL Injections. You should provide suitable security training to all your developers, QA staff, DevOps, and SysAdmins. You can start by referring them to this page. |
|
Treat all user input as untrusted. Any user input that is used in an SQL query introduces a risk of an SQL Injection. Treat input from authenticated and/or internal users the same way that you treat public input. |
|
Don’t filter user input based on blacklists. A clever attacker will almost always find a way to circumvent your blacklist. If possible, verify and filter user input using strict whitelists only. |
|
Older web development technologies don’t have SQLi protection. Use the latest version of the development environment and language and the latest technologies associated with that environment/language. For example, in PHP use PDO instead of MySQLi. |
|
Don’t try to build SQLi protection from scratch. Most modern development technologies can offer you mechanisms to protect against SQLi. Use such mechanisms instead of trying to reinvent the wheel. For example, use parameterized queries or stored procedures. |
|
Step 6: Scan regularly (with Acunetix)SQL Injections may be introduced by your developers or through external libraries/modules/software. You should regularly scan your web applications using a web vulnerability scanner such as Acunetix. If you use Jenkins, you should install the Acunetix plugin to automatically scan every build. |
Эксплуатации SQL-инъекции
Каждый раз с любым приложением, где бы не эксплуатировалась SQL-инъекция, используются следующие три базовых правила внедрения:
- Балансировка
- Внедрение
- Комментирование
Балансировка заключается в том, что количество открывающих и закрывающих кавычек и скобок должно быть одинаковым, чтобы не вызвать ошибку синтаксиса. При исследовании ошибки нужно определить, используются, и если используются, то какие кавычки и скобки.
Внедрение заключается в дополнении запроса в зависимости от информации, которую мы хотим получить.
Комментирование позволяет отсечь заключительную часть запроса, чтобы она не нарушала синтаксис.
Комментарии в MySQL начинаются с символов:
- #
- —
- /*
Т.е. вместо
Demo' --
можно было бы ввести
Demo' #
Обратите внимание, что после двойной черты обязательно нужен пробел, а после # пробел необязателен.
Можно продолжить менять логику запроса, если в качестве имени пользователя вставить:
Demo' OR 1 --
то получится запрос
SELECT `name`, `status`, `books` FROM `members` WHERE name = ' Demo' OR 1 -- ' AND password ='111'
Уберём закомментированную часть:
SELECT `name`, `status`, `books` FROM `members` WHERE name = ' Demo' OR 1
Мы используем логическое ИЛИ (OR). Логическое ИЛИ возвращает true (истину) если хотя бы одно из выражений является истиной. В данном случае второе выражение 1 всегда является истинной. Следовательно, в результаты попадут вообще все записи таблицы. В реальном веб-приложении можно достичь результата, когда будут выведены данные всех пользователей, несмотря на то, что атакующий не знал ни их логины, ни пароли.
В нашем примере после введённого значения Demo мы ставили одинарную кавычку (‘), чтобы запрос оставался правильным с точки зрения синтаксиса. Запрос может быть написан по-разному, например, все следующие формы возвращают одинаковый результат.
Для запросов с цифрой:
SELECT * FROM table_name WHERE id=1 SELECT * FROM table_name WHERE id='1' SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id=(1) SELECT * FROM table_name WHERE id=('1') SELECT * FROM table_name WHERE id=("1")
Для запросов со строкой:
SELECT * FROM table_name WHERE id='1' SELECT * FROM table_name WHERE id="1" SELECT * FROM table_name WHERE id=('1') SELECT * FROM table_name WHERE id=("1")
В зависимости от того, как составлен запрос, нужно использовать соответствующие символы парные закрывающие символы, чтобы не происходило нарушения синтаксиса. Например, если бы запрос был записан так (в нём вместо одинарных кавычек, используются двойные):
SELECT * FROM `members` WHERE name = "$name" AND password = "$password"
то имя пользователя
Demo' #
не возымело бы действия и не вызвало бы ошибку. Для обозначения конца введённого имени нужно использовать закрывающую двойную кавычку, т.е.:
Demo" #
Для такого запроса (используются одинарные кавычки и круглые скобки):
SELECT * FROM `members` WHERE name = ('$name') AND password = ('$password')
нужно также закрывать круглые скобки, т.е. для эксплуатации SQL-инъекции нужно ввести что-то вроде
Demo') #
Главными признаками наличия 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
Ошибка в MSSQL ASPX:
Server Error in '/' Application
Ошибка в MSAccess (Apache PHP):
Fatal error: Uncaught exception 'com_exception' with message Source: Microsoft JET Database Engine
Ошибка в MSAccesss (IIS ASP):
Microsoft JET Database Engine error '80040e14'
Ошибка в Oracle:
ORA-00933: SQL command not properly ended
Ошибка в ODBC:
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
Ошибка в PostgreSQL:
PSQLException: ERROR: unterminated quoted string at or near "'" Position: 1 или Query failed: ERROR: syntax error at or near "'" at character 56 in /www/site/test.php on line 121.
Ошибка в MS SQL Server:
Microsoft SQL Native Client error %u201880040e14%u2019 Unclosed quotation mark after the character string
Информация об СУБД также используется определения, какие символы или последовательности символов можно использовать в качестве комментариев.
How SQL Injection Works
The primary form of SQL injection consists of direct insertion of code into user-input variables that are concatenated with SQL commands and executed. A less direct attack injects malicious code into strings that are destined for storage in a table or as metadata. When the stored strings are subsequently concatenated into a dynamic SQL command, the malicious code is executed.
The injection process works by prematurely terminating a text string and appending a new command. Because the inserted command may have additional strings appended to it before it is executed, the malefactor terminates the injected string with a comment mark «—«. Subsequent text is ignored at execution time.
The following script shows a simple SQL injection. The script builds an SQL query by concatenating hard-coded strings together with a string entered by the user:
The user is prompted to enter the name of a city. If she enters , the query assembled by the script looks similar to the following:
However, assume that the user enters the following:
In this case, the following query is assembled by the script:
The semicolon (;) denotes the end of one query and the start of another. The double hyphen (—) indicates that the rest of the current line is a comment and should be ignored. If the modified code is syntactically correct, it will be executed by the server. When SQL Server processes this statement, SQL Server will first select all records in where is . Then, SQL Server will drop .
As long as injected SQL code is syntactically correct, tampering cannot be detected programmatically. Therefore, you must validate all user input and carefully review code that executes constructed SQL commands in the server that you are using. Coding best practices are described in the following sections in this topic.
SQL-инъекция для новичков
SQL-инъекция – это опасная уязвимость, которая возникает из-за недостаточной фильтрации вводимых пользователем данных, что позволяет модифицировать запросы к базам данных. Результатом эксплуатации SQL-инъекции является получение доступа к данным, к которым в обычных условиях у пользователя не было бы доступа.
Обычно SQLi находят в веб-приложениях. Но на самом деле, SQL-инъекции могут быть подвержены любые программы, использующие разные базы данных (не только MySQL/MariaDB).
В качестве примера, рассмотрим приложение, которое обращается к базе данных со следующим запросом:
SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo' AND password ='111'
Запрос похож на естественный язык (английский), и его значение довольно просто интерпретировать:
Выбрать (SELECT) поля `name`, `status`, `books` из (FROM) таблицы `members` где (WHERE) значение поля name равно величине Demo (name = ‘Demo’) и (AND) значение поля password равно величине 111 (password =’111′).
Этот запрос вызывает обход таблицы, в результате которого делается сравнение с каждой строкой, и если условие name = ‘Demo’ AND password =’111′ является для какой-либо строки истиной, то она попадает в результаты. В данном случае, результаты будут только если и введённое имя пользователя, и пароль в точности совпадают с теми, которые хранятся в таблице.
При этом значения «Demo» и «111» приложение получает от пользователя – например, в форме входа на сайт.
Предположим, что вместо Demo пользователь ввёл такую строку:
Demo' --
Тогда запрос к базе данных будет иметь вид:
SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo' -- ' AND password ='111'
Две чёрточки (—) – означают комментарий до конца строки, т.е. всё, что за ними, больше не учитывается. Следовательно, из выражения условия «исчезает» часть ‘ AND password =’111’
Поскольку в комментарии осталась закрывающая кавычка, то она также была введена с именем пользователя, чтобы не сломать синтаксис и не вызвать ошибку, в результате, фактически, к базе данных делался следующий запрос:
SELECT `name`, `status`, `books` FROM `members` WHERE name = 'Demo'
В нём была нарушена логика работы программы, заложенная разработчиками. Т.е. теперь поиск в таблице производится только по имени. И если имя совпало, то строка попадает в результаты независимо от введённого пароля. Это и есть пример эксплуатации SQL-инъекции. В реальной ситуации, такая ошибка может быть использована на веб-сайте для входа под учётной записью администратора, для которой достаточно знать только имя, а пароль становится ненужным.
Кроме обхода аутентификации, SQL-инъекция используется для извлечения информации из баз данных, вызова отказа в обслуживании (DoS), эксплуатацию других уязвимостей (вроде XSS) и т.п.
Возможные проблемы при сканировании sqlmap
Могут появиться следующей ошибки:
connection timed out to the target URL. sqlmap is going to retry the request(s) if the problem persists please check that the provided target URL is valid. In case that it is, you can try to rerun with the switch '--random-agent' turned on and/or proxy switches ('--ignore-proxy', '--proxy',...)
Она означает, что веб-сайт не хочет «разговаривать» с sqlmap. В качестве варианта нам предлагают использовать —random-agent. Если в браузере вы можете наблюдать сайт, а sqlmap пишет о невозможности подключиться, значит сайт игнорирует запросы, ориентируясь на пользовательский агент. Опция —random-agent меняет стандартное значение sqlmap на произвольные:
sqlmap -u http://www.wellerpools.com/news-read.php?id=22 --random-agent
Ещё одной причиной такой ошибки может быть блокировка вашего IP веб-сайтом — тогда нужно использовать прокси. Если вы уже используете прокси и появляется эта ошибка, то это может означать, что у прокси проблемы со связью и стоит попробовать без него.
Неэффективные способы защиты от SQL-инъекций
Очевидно, самый худший вариант — не иметь никакой защиты от SQL инъекций и передавать данные, полученные от пользователя, напрямую в SQL-запрос.
Никогда так не делай! Любые данные перед подстановкой в SQL-запрос должны проходить фильтрацию и/или валидацию.
1. Функция htmlspecialchars()
Время от времени встречаю статьи, где авторы используют функцию htmlspecialchars() для экранирования данных:
Это опасно! Штука в том, что функция htmlspecialchars() пропускает без экранирования опасные символы: \ (слеш), \0 (nul-байт) и \b (backspace).
Вот полный пример кода, демонстрирующего уязвимость:
В итоге SQL-запрос будет таким:
С помощью / экранируется кавычка, идущая сразу после $login. `login` = ‘$login’ по факту превращается в `login` = ‘\’ AND `password` = ‘. После этого любой код, который мы напишем, будет выполнен, в нашем случае это просто OR 1=1. В конце добавляем # (комментарий), чтобы скрыть последнюю кавычку.
2. Фильтрация по чёрному списку символов
По каким-то непонятным мне причинам ещё существуют разработчики, использующие чёрные списки символов:
Все символы, входящие в чёрный список, удаляются из строки перед вставкой в базу.
Я не хочу сказать, что этот подход не будет работать, но его применение под большим вопросом:
- Зачем вообще составлять какие-то списки, если есть более простые и надёжные способы защиты?
- Нужно знать все потенциально опасные символы.
- Что делать если нужно разрешить пользователям использовать какие-либо символы из списка?
Кроме этого, я считаю фильтрацию в SQL-запросах плохой идеей. Если в строке есть недопустимые символы — лучше сообщить о них пользователю и попросить исправить, а не просто обрезать часть контента.
К примеру, пользователь хочет использовать логин ~!Mega_!Pihar!_!9000!~, а после регистрации оказывается, что его ник превратился в MegaPihar9000.
Я считаю, лучше уточнить у пользователя, нравится ли ему такой отфильтрованный логин или он хотел бы что-то поменять. Короче, я за валидацию по белому списку вместо фильтрации по чёрному.
3. Функция stripslashes()
Редко, но встречается код, использующий stripslashes() перед записью в базу. Поскольку новички до сих пор копируют этот код в свои проекты, объясню, зачем эта функция нужна.
Раньше в PHP была такая штука как волшебные кавычки (Документация). Если эта директива была включена, то все данные, содержащиеся в $_GET, $_POST и $_COOKIE автоматически экранировались.
Сделано это было для защиты новичков, которые подставляли данные напрямую в SQL-запросы. На практике это было не самое удачное решение:
- Не очень удобно, когда все данные по-умолчанию экранируются, ведь зачастую они нужны в исходном виде.
- В идеале экранирование должно учитывать кодировку соединения с базой данных, о чём мы поговорим чуть позже. Из-за этого разработчикам приходилось убирать экранирование функцией stripslashes() и затем опять экранировать данные более подходящими функциями, в случае MySQL это была mysql_real_escape_string().
Вот почему функцию stripslashes() можно встретить в старых учебниках. Чтобы отменить экранирование символов и получить исходную строку.
Начиная с PHP 5.4 функционал волшебных кавычек удалён, поэтому использовать stripslashes() перед записью в базу нет никакого смысла.
4. Функция addslashes()
В некоторых книгах ещё можно встретить рекомендации экранировать данные функцией addslashes().
Эта функция надёжней, чем htmlspecialchars(), поскольку экранирует и обратный слеш, и nul-байт. Однако эта функция хуже, чем mysql_real_escape_string, поскольку не учитывает кодировку текущего соединения с базой.
Поэтому даже в документации прямо написано, что эту функцию не нужно использовать для защиты от SQL инъекций.
Вырезка из документации про функцию addslashes()
Экранирование значений
Что делать, если в SQL запрос требуется подставить строковое значение? Например, на сайте есть возможность поиска города по его названию. Форма поиска передаст поисковый запрос в GET-параметр, а мы используем его в SQL-запросе:
Но если в параметре будет символ кавычки, то смысл запроса можно кардинально изменить. Передав в search_text значение , мы выполним запрос, что выведет список всех городов:
Смысл запроса поменялся, потому что кавычка из параметра запроса считается управляющим символом: MySQL определяет окончание значение по символу кавычки после него, поэтому сами значения кавычки содержать не должны.
Очевидно, приведение к числовому типу не подходит для строковых значений. Поэтому, чтобы обезопасить строковое значение, используют операцию экранирования.
Экранирование добавляет в строке перед кавычками (и другими спецсимволами) знак обратного слэша .
Такая обработка лишает кавычки их статуса — они больше не определяют конец значения и не могут повлиять на логику SQL-выражения.
За экранирование значений отвечает функция .
Этот код обработает значение из параметра, сделав его безопасным для использования в запросе:
Демонстрация атаки с использованием SQL-инъекции
Как показано на , SQL-инъекция использует введенные пользователем некорректные данные для получения разрешения на прямое взаимодействие с внутренней базой данных. Теперь проведем эксперимент на веб-сервере. В качестве площадки для проведения демонстрационного тестирования мы будем использовать веб-сайт вымышленного банка Altoro Mutual
(). Банк применяет производственную версию веб-сервера, которая имеет естественные уязвимости.
Для начала откройте в своем веб-браузере этот целевой сайт. Вы увидите страницу приветствия, показанную на . В правой части верхней строки находится ссылка Sign In, которая является нашей целью в этом примере. При нажатии на эту ссылку вы попадаете на страницу входа в систему.
Рисунок 2. Веб-сайта банка Altoro Mutual: начальная страница
На странице входа в систему () с помощью плагина Firebug браузера Mozilla Firefox можно увидеть, что веб-сайт выполняет валидацию полей с помощью JavaScript-функции .
Рисунок 3. Изучение исходного кода страницы с помощью плагина Firebug
При попытке ввести пробелы в любом из этих полей появляется сообщение о недопустимости использования пробелов для полей. Проработаем эту идею более детально. Попробуйте ввести в поле пароля произвольное имя пользователя и специальный символ, чтобы увидеть реакцию приложения. В данном случае (см. ), я ввел тестовое имя пользователя () и пароль, состоящий из символа (‘).
Рисунок 4. Сообщение об ошибке при вводе символа (‘) в поле Password
Вместо краткого сообщения о некорректном сочетании имени пользователя и пароля веб-сервер великодушно предоставляет подробную информацию о SQL-запросе. В частности, использование символа (‘) нарушило синтаксис запроса для параметра
к внутренней базе данных. Вы можете увидеть ошибку в конструкции , которая показана в следующем SQL-запросе:
query = SELECT User FROM Users WHERE Username = 'donald' AND Password = '''
Вы можете воспользоваться этим знанием, чтобы оценить уязвимость этого процесса. В языке SQL символы () служат признаком комментария. Признак комментария дает указание SQL-базе данных проигнорировать остальную часть текущей строки.
С целью успешного выполнения запроса без знания пароля можно попытаться использовать SQL-комментарий для отбрасывания фрагмента запроса — а именно, фрагмента с паролем. Из предыдущего примера мы знаем, что ввод символа (‘) нарушил запрос. С учетом этого измените запрос, вставив определенный SQL-код в поле Username. В данном случае я ввел в поле Username, а затем произвольную информацию в поле Password. В результате веб-сервер выдал сообщение об ошибке, показанное на .
Рисунок 5. Сообщение об ошибке после вставки SQL-кода в поле Username
Вместо того чтобы вызывать исключение (как я сделал на ),
теперь я изменяю запрос таким образом, чтобы он воспрепятствовал намерениям разработчика. Теперь внутри системы этот запрос будет выглядеть следующим образом:
query = SELECT User FROM Users WHERE Username = 'don'--' AND Password = '*'
Как показано в этом примере, простое добавление символов (‘—) в поле
Username сделало фрагмент запроса с паролем несущественным, тем не менее, этот запрос передается в базу данных как успешный. Опираясь на эту информацию, предположим, что в системе имеется административная учетная запись с именем
admin. Если вы выполните эту же операцию, но с именем пользователя admin’—, то сможете успешно войти в систему по этой учетной записи и увидеть имя пользователя admin, найти другие сведения об учетной записи admin и использовать учетную запись admin в качестве собственной учетной записи.
Итак, с помощью скромных познаний в области SQL вы успешно обошли внутреннюю проверку на ошибки незащищенного веб-сайта и аутентифицировали себя в качестве его администратора без знания соответствующего пароля. Если учетная запись admin не существует (или недоступна извне), мы можем получить доступ к первой учетной записи в таблице с помощью немного усложненного имени пользователя. В этом случае мы изменим запрос таким образом, чтобы предоставляемое условие всегда имело значение true. Этот результат достигается вводом имени пользователя вида
. Хотя эта уязвимость и не предоставит вам привилегий администратора, она обеспечит более глубокий доступ к веб-сайту и возможность поиска дальнейших уязвимостей.
Приведение к целочисленному типу
В SQL-запросы часто подставляются целочисленные значения, полученные от пользователя. В примерах выше использовался идентификатор города, полученный из параметров запроса. Этот идентификатор можно принудительно привести к числу. Так мы исключим появление в нём опасных выражений. Если хакер передаст в этом параметре вместо числа SQL код, то результатом приведения будет ноль, и логика всего SQL-запроса не изменится.
PHP умеет присваивать переменной новый тип. Этот код принудительно назначит переменной целочисленный тип:
После преобразования переменную можно без опаски использовать в SQL-запросах.