SQL-инъекции: простое объяснение для начинающих

SQL-инъекции: простое объяснение для начинающих

Информация от канала https://t.me/darkvebs

Доброго времени суток, господа. Сегодня на повестке дня у нас начало серии постов про SQL-инъекции. Приятного чтения.

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

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

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

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

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

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

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h2>Для доступа к Бобруйской районной библиотеке введите Ваши данные:</h2>
<form method="get" action="?">
<p>Введите ваше имя</p>
<input name="name" type="text">
<p>Введите ваш пароль</p>
<input name="password" type="text"><br />
<input type="submit">
</form>
<?php
$mysqli = new mysqli("localhost", "root", "", "db_library");
if (mysqli_connect_errno()) {
printf("Не удалось подключиться: %s\n", mysqli_connect_error());
exit();
} else {
$mysqli->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 "<p><b>Ваше имя: </b> $obj->name</p>
<p><b>Ваш статус:</b> $obj->status</p>
<p><b>Доступные для Вас книги:</b> $obj->books</p><hr />";
}
} else {
printf("Ошибка: %s\n", $mysqli->error);
}
$mysqli->close();
?>
</body>
</html>

Вы намного больше поймёте, если будете всё делать вместе со мной. Поэтому вот ссылка на архив. В нём два файла: 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'

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

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

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

' 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

Результат:

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


Report Page