Защита от 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-инъекции (а также php-инъекций, xml-инъекций и т.д.) состоит в том, чтобы найти приложение второго вида и заставить его выполнить некоторое полезное нам действие, возможность которого не планировалась программистом.

К примеру, есть некий скрипт articles.php. И туда методом Get передается значение параметра id. Кодер когда писал свое приложение думал примерно так:

«Кроче, берем id из адресной строки. Пихаем иво в скуль-запрос. Если в базе есть такой id, то выводим страницу, которая к энтому Id прикручена, а ежеле нету, то тохда ибись оно в рот и ничово не выводем.»

PHP

<?php //articles.php

include(«db_config»);
$id = $_GET;
$sql = «SELECT * FROM articles WHERE id = ‘$id’ «;
$result = mysql_query($query) or die(mysql_error());
$row = mysql_fetch_array($result) or die(«НИЧОВО НЕТУ!!!»);

//…

1
2
3
4
5
6
7
8
9
10

<?php
//articles.php
 

include(«db_config»);

$id=$_GET’id’;

$sql=»SELECT * FROM articles WHERE id = ‘$id’ «;

$result=mysql_query($query)ordie(mysql_error());

$row=mysql_fetch_array($result)ordie(«НИЧОВО НЕТУ!!!»);

 
//…

В зависимости от того, что мы будем передавать в параметр id, приложение будет возвращать разный результат. Но любой результат можно свести к одному из трех вариантов:

  • Пустой ответ
  • Нормальный ответ
  • Ошибка

Теперь давайте посмотрим, что получится, если мы будем пихать разные значения в параметр id:

Параметр/значение Ожидания кодера Суровая действительность
id=1 Страница 1 Страница 1
id=2 Страница 2 Страница 2
id=3 Страница 3 Страница 3
id=4000000 Пустой ответ Пустой ответ
id=0 Пустой ответ Пустой ответ
id=1kkdka Пустой ответ Пустой ответ
id=asdf Пустой ответ Пустой ответ
id=-1 Пустой ответ Пустой ответ
id=9999.9 Пустой ответ Пустой ответ
id=2-1 Пустой ответ Пустой ответ
id=-1′ Пустой ответ Ошибка
id=-1\ Пустой ответ Ошибка
id=9999.9′ or id=’2 Пустой ответ Страница 2

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

Остается дело за малым  — раскрутить эту самую инъекцию (попробовать вывести данные из бд, прочитать файлы, залить шелл и т.д.)

Стоит сразу отметить 2 нюанса:

  1. Если ошибки не выводятся, это совсем не значит, что в приложении нет SQL-инъекции.
  2. Помните, что есть существенная разница между выводом ошибок от интерпретатора и выводом ошибок от СУБД.

Если вы ни черта не поняли из того что было написано в этой части статьи — либо я очень херово все описал (и это ведь только начало!), либо вам надо учить матчасть.

Эксплуатации 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

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

Защита от атак типа внедрение SQL-кода[ | код]

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

Фильтрация строковых параметров | код

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

statement := 'SELECT * FROM users WHERE name = "' + userName + '";';

Чтобы внедрение кода (закрытие строки, начинающейся с кавычки, другой кавычкой до её завершения текущей закрывающей кавычкой для разделения запроса на две части) было невозможно, для некоторых СУБД, в том числе, для MySQL, требуется брать в кавычки все строковые параметры. В само́м параметре заменяют кавычки на \», апостроф — на \’, обратную косую черту — на \\ (это называется «экранировать спецсимволы»). Это можно делать таким кодом:

statement := 'SELECT * FROM users WHERE name = ' + QuoteParam(userName) + ';';
function QuoteParam(s  string)  string;
{ на входе — строка; на выходе — строка в кавычках и с заменёнными спецсимволами }
var
  i  integer;
  Dest  string;
begin
  Dest := '"';
  for i:=1 to length(s) do
    case si of
      ''''  Dest := Dest + '\''';
      '"'  Dest := Dest + '\"';
      '\'  Dest := Dest + '\\';
    else Dest := Dest + si;
    end; 
  QuoteParam := Dest + '"';
end;

Для PHP фильтрация может быть такой:

$query = "SELECT * FROM users WHERE user='" . mysqli_real_escape_string($user) . "';";

Фильтрация целочисленных параметров | код

Возьмём другой запрос:

statement := 'SELECT * FROM users WHERE id = ' + id + ';';

В данном случае поле имеет числовой тип, и его чаще всего не берут в кавычки. Поэтому «закавычивание» и замена спецсимволов на escape-последовательности не проходит. В таком случае помогает проверка типа; если переменная не является числом, запрос вообще не должен выполняться.

Например, на Delphi для противодействия таким инъекциям помогает код:

if TryStrToInt(id, id_int) then
  statement := Format('SELECT * FROM users WHERE id =%0:d;', id_int]);

Для PHP этот метод будет выглядеть так:

 $query = 'SELECT * FROM users WHERE id = ' . (int)$id;

Усечение входных параметров | код

Для внесения изменений в логику выполнения SQL-запроса требуется внедрение достаточно длинных строк. Так, минимальная длина внедряемой строки в вышеприведённых примерах составляет 8 символов («1 OR 1=1»). Если максимальная длина корректного значения параметра невелика, то одним из методов защиты может быть максимальное усечение значений входных параметров.

Например, если известно, что поле в вышеприведённых примерах может принимать значения не более 9999, можно «отрезать лишние» символы, оставив не более четырёх:

statement := 'SELECT * FROM users WHERE id = ' + LeftStr(id, 4) + ';';

Использование параметризованных запросов | код

Многие серверы баз данных поддерживают возможность отправки параметризованных запросов (подготовленные выражения). При этом параметры внешнего происхождения отправляются на сервер отдельно от самого запроса либо автоматически экранируются клиентской библиотекой. Для этого используются

на Delphi — свойство TQuery.Params;

Например

var
  sql, param  string
  
begin
  sql := 'select :text as value from dual';
  param := 'alpha';  
  Query1.Sql.Text := sql;
  Query1.ParamByName('text').AsString := param;
  Query1.Open;
  ShowMessage(Query1'value']);  
end;
  • на Perl — через или ;
  • на Java — через класс ;
  • на C# — свойство ;
  • на PHP — MySQLi (при работе с MySQL), .

Защита от SQL инъекций

В целях безопасности в MySQL строка запроса mysql_query () поддерживает только 1 запрос к базе данных, второй не пройдет. Поэтому работоспособен в MySQL только UNION взлом. А в других базах данных все вышеперечисленные атаки можно сделать. Это по официальной документации.

По неофиц. данным функция mysql_query может исполнить 2 запроса. Для этого надо сделать так (с использование констант):

mysql_connect('localhost','login','password',65536); //используем константу 65536
mysql_query('');

С использованием данной константы возможно проведение двух запросов в MySQL.

Для защиты от SQL инъекций, рекомендую следующее:

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

Фильтрация данных

Используйте спец функции, встроенные в mysql:

$email = mysql_real_escape_string($_POST'email']);

Отключите директиву PHP magic_quotes_gpc вручную в файле .htaccess.

Дополнительная защита сайта от взлома

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

1. Запретите прямой доступ к служебным файлам

Все файлы на сайте можно открыть или скачать, если знать их адрес. Например, можно попытаться скачать файл header.php, чтобы найти там уязвимости (если скачивание будет удачным).

Поэтому лучше все подключаемые файлы переместить в отдельную директорию и запретить к ней прямой доступ. Например, можно создать папку includes и поместить туда:

  • отдельные блоки сайта;
  • библиотеки пользовательских функций;
  • файл подключения к базе данных;
  • обработчики форм и так далее.

Для этого создайте в этой папке файл .htaccess и добавьте туда такую строчку:

deny from all

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

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

Ничто не должно намекать на то, какой тематики сайт, какой у него адрес, на каком он хостинге и так далее. Чем больше информации вы добровольно расскажете, тем выше риск быть взломанным.

3. Проверяйте копипасту

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

В идеале лучше вручную переписывать код — так вы точно заметите все подозрительные команды.

4. Отключите вывод ошибок

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

Отключить вывод можно в файле .htaccess, добавив туда следующие строки:

php_flag display_errors off

php_value error_reporting 0

Кроме того, уберите вывод ошибок, который вы прописали в самом коде.

5. Ограничьте права пользователя базы данных

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

Пока ни одна галочка не отмечена — поставьте ее на пункт «Данные». Так можно будет оперировать существующими данными, но нельзя будет создать новую таблицу или удалить старую. Так вы защититесь в том числе и от атаки с помощью команды DROP, которая может удалить все статьи или комментарии с сайта.

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

Также лучше поставить пароль на root, чтобы взломщик не смог подключиться через него.

6. Установите последнюю версию языка

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

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

7. Используйте сложный пароль

Банально, но простой пароль можно подобрать за несколько секунд. Особенно если он содержит персональные данные:

  • дату рождения;
  • кличку питомца;
  • годовщину свадьбы;
  • девичью фамилию матери и так далее.

Усугубляет положение то, что эту информацию мы часто выкладываем в свободный доступ.

Причины SQL-инъекции

  • Отсутствие фильтрации
  • Неправильная обработка типов
  • Уязвимости в базе данных сервера
  • Условные ошибки

1. Отсутствие фильтрации

SQL-запрос

<?php $sqlStatement = "SELECT * FROM users WHERE username = '" + $username + "' AND email = '" + $email + "' ";

?>

user@hostname.com’; DROP TABLE users; SELECT * FROM customers WHERE name LIKE ‘%

<?php $sqlStatement = "SELECT * FROM users WHERE username = 'james' AND email = 'user@hostname.com'; DROP TABLE users; SELECT * FROM customers WHERE name LIKE '%'";

?>

аутентификацию

20; DROP TABLE users

<?php $sqlStatement = "SELECT * FROM customers WHERE age = 20; DROP TABLE users;";

?>

$ageValue

Защита

Из нативных средств можно выделить разного рода валидаторы или преобразователи входящих данных. Они могут быть как самописными, так и использовать функции языка программирования. Например, в среде php распространено использование следующих функций:

  • mysql_escape_string — экранирует строку для использования в mysql_query;
  • addslashes — экранирует спецсимволы в строке;
  • htmlspecialchars — преобразует специальные символы в HTML сущности;
  • mysql_real_escape_string — экранирует специальные символы в unescaped_string;
  • intval — функция приведения типа.

Большинство этих функций направлено на то, чтобы провести преобразование «опасных» символов, исходя из контекст использования. Наиболее эффективным средством защиты из вышеперечисленного является Intval.

В «надстроенных» средствах защиты можно выделить два направления — защита веб приложения средствами веб-приложения (фреймворк), либо защита с помощью сторонних средств, например в виде web application firewall. В качестве первого примера можно привести использование HTMLPurifier: эта библиотека очищает html код от всех вредоносных, невалидных, запрещенных (вашей конфигурацией) частей кода, в том числе отдельные атрибуты.

Эксплуатация уязвимости

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

Но даже доступ к базе многое дает. Злоумышленник может прочитать данные администратора или создать нового пользователя с такими правами. Так он получит доступ к админке сайта, в которой сможет найти другие уязвимости (например загрузку на сервер произвольных файлов).

В некоторых случаях (зависит от конфигурации сервера) некоторые базы данных позволяют через SQL запрос запустить произвольную программу на сервере.

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

Что такое sql-инъекции и как они происходят?

Что здесь видит разработчик? Подстановку данных в строку. С точки зрения PHP здесь ничего опасного нет. С точки зрения неопытного PHP-разработчика, проверяющего свой код — код работает, ему ведь не пришло в голову написать что-то странное в логин.

Приключения начинаются, когда кто-то вместо логина вводит . Здесь необходимо напомнить, что SQL — штука изначально текстовая. Что после конкатенации на PHP получит СУБД?

Это одна строка непрерывного текста. Как СУБД должна понять, что этот запрос отличается от задуманного? Это запрос, он синтаксически корректен, его можно выполнить — СУБД его и выполняет. Но запрос уже делает не то, что хотел сказать разработчик.

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

Ссылка на основную публикацию