Как работает SQL-инъекция из комикса XKCD "Таблицы Бобби"?
Просто смотрю на:
(Источник: https://xkcd.com/327/)
Что делает этот SQL:
Robert'); DROP TABLE STUDENTS; --
Я знаю как '
а также --
для комментариев, но не слово DROP
получить комментарий, так как это часть той же строки?
12 ответов
Это опускает стол студентов.
Оригинальный код в школьной программе, вероятно, выглядит примерно так
q = "INSERT INTO Students VALUES ('" + FNMName.Text + "', '" + LName.Text + "')";
Это наивный способ добавить ввод текста в запрос, и, как вы увидите, он очень плохой.
После значений из имени, отчество текстовое поле FNMName.Text (которое Robert'); DROP TABLE STUDENTS; --
) и текстовое поле фамилии LName.Text (назовем его Derper
) объединяются с остальной частью запроса, в результате теперь фактически два запроса разделены оператором- разделителем (точка с запятой). Второй запрос был введен в первый. Когда код выполняет этот запрос к базе данных, он будет выглядеть так
INSERT INTO Students VALUES ('Robert'); DROP TABLE Students; --', 'Derper')
что на простом английском языке примерно переводит на два запроса:
Добавьте новую запись в таблицу учеников со значением имени "Роберт"
а также
Удалить таблицу студентов
Все после второго запроса помечается как комментарий: --', 'Derper')
'
в имени студента это не комментарий, это разделитель закрывающей строки. Поскольку имя студента является строкой, оно необходимо синтаксически для выполнения гипотетического запроса. Атаки с использованием инъекций работают только тогда, когда вводимые ими SQL-запросы приводят к действительному SQL.
Отредактировано снова в соответствии с проницательным комментарием dan04
Допустим, имя было использовано в переменной, $Name
, Затем вы запускаете этот запрос:
INSERT INTO Students VALUES ( '$Name' )
Код ошибочно помещает все, что пользователь указал в качестве переменной. Вы хотели, чтобы SQL был:
ВСТАВЬТЕ В СТОИМОСТЬ СТУДЕНТОВ (" Robert Tables")
Но умный пользователь может предоставить все, что он хочет:
ВСТАВИТЬ В СТОИМОСТЬ студентов (" Роберт"); DROP TABLE Студенты; - ')
Что вы получаете:
INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' )
--
только комментирует остаток строки.
Как уже отмечали все остальные, ');
закрывает исходное утверждение, а затем следует второе утверждение. Большинство фреймворков, в том числе такие языки, как PHP, на данный момент имеют настройки безопасности по умолчанию, которые не позволяют использовать несколько операторов в одной строке SQL. В PHP, например, вы можете запустить несколько операторов только в одной строке SQL, используя mysqli_multi_query
функция.
Однако вы можете манипулировать существующим оператором SQL с помощью SQL-инъекции, не добавляя второй оператор. Допустим, у вас есть система входа в систему, которая проверяет имя пользователя и пароль с помощью этого простого выбора:
$query="SELECT * FROM users WHERE username='" . $_REQUEST['user'] . "' and (password='".$_REQUEST['pass']."')";
$result=mysql_query($query);
Если вы предоставите peter
в качестве имени пользователя и secret
в качестве пароля результирующая строка SQL будет выглядеть так:
SELECT * FROM users WHERE username='peter' and (password='secret')
Все в порядке. Теперь представьте, что вы предоставляете эту строку в качестве пароля:
' OR '1'='1
Тогда результирующая строка SQL будет такой:
SELECT * FROM users WHERE username='peter' and (password='' OR '1'='1')
Это позволит вам войти в любую учетную запись, не зная пароля. Таким образом, вам не нужно иметь возможность использовать два оператора, чтобы использовать SQL-инъекцию, хотя вы можете делать более разрушительные действия, если вы можете предоставить несколько операторов.
Нет, '
это не комментарий в SQL, а разделитель.
Мама предположила, что программист базы данных сделал запрос, похожий на:
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('$firstName', '$lastName');
(например), чтобы добавить нового студента, где $xxx
содержимое переменной было взято непосредственно из HTML-формы, без проверки формата и экранирования специальных символов.
Так что если $firstName
содержит Robert'); DROP TABLE students; --
Программа базы данных выполнит следующий запрос непосредственно в БД:
INSERT INTO 'students' ('first_name', 'last_name') VALUES ('Robert'); DROP TABLE students; --', 'XKCD');
то есть. он преждевременно завершит оператор вставки, выполнит любой вредоносный код, который захочет взломщик, а затем закомментирует любой оставшийся код.
Ммм, я слишком медленный, я вижу уже 8 ответов перед моим в оранжевой полосе...:-) Кажется, популярная тема.
TL; DR
- Приложение принимает ввод, в данном случае "Нэнси", не пытаясь - очистить ввод, например, экранируя специальные символы школа => ВСТАВИТЬ В СТОИМОСТЬ студентов ("Нанси"); ВСТАВИТЬ 0 1 - SQL-инъекция происходит, когда ввод в команду базы данных манипулируется - заставить сервер базы данных выполнять произвольный SQL школа => ВСТАВИТЬ В СТОИМОСТЬ студентов ("Роберт"); DROP TABLE студенты; -'); ВСТАВИТЬ 0 1 DROP TABLE - Студенческие записи сейчас ушли - могло быть и хуже! школа => ВЫБРАТЬ * ОТ студентов; ОШИБКА: отношения "студенты" не существует ЛИНИЯ 1: ВЫБРАТЬ * ОТ СТУДЕНТОВ; ^
Это удаляет (удаляет) таблицу ученика.
(Все примеры кода в этом ответе выполнялись на сервере базы данных PostgreSQL 9.1.2.)
Чтобы было понятно, что происходит, давайте попробуем это с простой таблицей, содержащей только поле имени, и добавим одну строку:
школа => CREATE TABLE студентов (название TEXT PRIMARY KEY); ВНИМАНИЕ: CREATE TABLE / PRIMARY KEY создаст неявный индекс "Students_pkey" для таблицы "Students" СОЗДАТЬ СТОЛ школа => ВСТАВИТЬ В СТОИМОСТЬ студентов ("Джон"); ВСТАВИТЬ 0 1
Предположим, что приложение использует следующий SQL для вставки данных в таблицу:
ВСТАВИТЬ В СТОИМОСТЬ студентов ("foobar");
замещать foobar
с фактическим именем студента. Обычная операция вставки будет выглядеть так:
- Вход: Нэнси школа => ВСТАВИТЬ В СТОИМОСТЬ студентов ("Нанси"); ВСТАВИТЬ 0 1
Когда мы запрашиваем таблицу, мы получаем это:
школа => ВЫБРАТЬ * ОТ студентов; название ------- Джон Нэнси (2 ряда)
Что происходит, когда мы вставляем имя Little Bobby Tables в таблицу?
- вход: Роберт '); DROP TABLE студенты; - школа => ВСТАВИТЬ В СТОИМОСТЬ студентов ("Роберт"); DROP TABLE студенты; -'); ВСТАВИТЬ 0 1 DROP TABLE
Внедрение SQL здесь является результатом того, что имя студента завершает утверждение и включает отдельный DROP TABLE
команда; две черты в конце ввода предназначены для закомментирования любого оставшегося кода, который в противном случае мог бы вызвать ошибку. Последняя строка вывода подтверждает, что сервер базы данных отбросил таблицу.
Важно отметить, что во время INSERT
Во время операции приложение не проверяет ввод на наличие каких-либо специальных символов, и поэтому позволяет вводить произвольный ввод в команду SQL. Это означает, что злонамеренный пользователь может вставить в поле, обычно предназначенное для ввода данных пользователем, специальные символы, такие как кавычки, а также произвольный код SQL, чтобы система базы данных выполнила его, следовательно, SQL- инъекция.
Результат?
школа => ВЫБРАТЬ * ОТ студентов; ОШИБКА: отношения "студенты" не существует ЛИНИЯ 1: ВЫБРАТЬ * ОТ СТУДЕНТОВ; ^
SQL-инъекция является эквивалентом уязвимости удаленного выполнения произвольного кода в операционной системе или приложении. Потенциальное влияние успешной атаки с использованием SQL-инъекции нельзя недооценивать - в зависимости от системы базы данных и конфигурации приложения злоумышленник может использовать ее для потери данных (как в этом случае), получения несанкционированного доступа к данным или даже выполнения произвольный код на самой машине.
Как отмечалось в комиксе XKCD, одним из способов защиты от атак с использованием SQL-инъекций является очистка входных данных базы данных, например, путем экранирования специальных символов, чтобы они не могли изменить базовую команду SQL и, следовательно, не могли вызвать выполнение произвольного кода SQL. Если вы используете параметризованные запросы, например, с помощью SqlParameter
в ADO.NET ввод будет, как минимум, автоматически очищаться для защиты от внедрения SQL.
Однако очистка входных данных на уровне приложений может не остановить более продвинутые методы внедрения SQL. Например, есть способы обойтиmysql_real_escape_string
PHP функция Для дополнительной защиты многие системы баз данных поддерживают подготовленные операторы. При правильной реализации в бэкэнде подготовленные операторы могут сделать внедрение SQL невозможным, обрабатывая ввод данных как семантически отдельный от остальной части команды.
Скажем, вы наивно написали метод создания ученика следующим образом:
void createStudent(String name) {
database.execute("INSERT INTO students (name) VALUES ('" + name + "')");
}
И кто-то вводит имя Robert'); DROP TABLE STUDENTS; --
Что запускается в базе данных это запрос:
INSERT INTO students (name) VALUES ('Robert'); DROP TABLE STUDENTS --')
Точка с запятой завершает команду вставки и запускает другую; - комментирует остальную часть строки. Команда DROP TABLE выполнена...
Вот почему параметры связывания - это хорошо.
Одиночная кавычка - это начало и конец строки. Точка с запятой - это конец утверждения. Так что, если они делают выбор, как это:
Select *
From Students
Where (Name = '<NameGetsInsertedHere>')
SQL станет:
Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
-- ^-------------------------------^
В некоторых системах select
будет побежал первым, а затем drop
заявление! Сообщение: не вставляйте значения в ваш SQL. Вместо этого используйте параметры!
В этом случае 'не является символом комментария. Он используется для разделения строковых литералов. Художник комиксов полагается на идею, что у рассматриваемой школы где-то есть динамический sql, который выглядит примерно так:
$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";
Так что теперь символ 'завершает строковый литерал до того, как программист ожидал этого. В сочетании с; Чтобы завершить оператор, теперь злоумышленник может добавить любой SQL-запрос. Комментарий в конце должен убедиться, что любой оставшийся sql в исходном операторе не препятствует компиляции запроса на сервере.
FWIW, я также думаю, что у рассматриваемого комикса есть неправильная важная деталь: если вы думаете о дезинфекции входных данных вашей базы данных, как предполагает комикс, вы все равно делаете это неправильно. Вместо этого вы должны думать с точки зрения карантина входных данных вашей базы данных, и правильный способ сделать это - через параметризованные запросы.
');
завершает запрос, комментарий не начинается. Затем он удаляет таблицу студентов и комментирует остальную часть запроса, который должен был быть выполнен.
'
символ в SQL используется для строковых констант. В этом случае он используется для окончания строковой константы, а не для комментария.
Автор базы данных, вероятно, сделал
sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);
Если имя студента указано, это делает выбор с именем "Роберт", а затем отбрасывает таблицу. Часть "-" изменяет оставшуюся часть данного запроса в комментарий.
Вот как это работает: предположим, что администратор ищет записи ученика
Robert'); DROP TABLE STUDENTS; --
Поскольку учетная запись администратора имеет высокие привилегии, удаление таблицы из этой учетной записи возможно.
Код для получения имени пользователя из запроса
Теперь запрос будет выглядеть примерно так (для поиска в таблице учеников)
String query="Select * from student where username='"+student_name+"'";
statement.executeQuery(query); //Rest of the code follows
Результирующий запрос становится
Select * from student where username='Robert'); DROP TABLE STUDENTS; --
Поскольку пользовательский ввод не очищен, приведенный выше запрос состоит из 2 частей
Select * from student where username='Robert');
DROP TABLE STUDENTS; --
Двойная черта (-) просто закомментирует оставшуюся часть запроса.
Это опасно, так как может аннулировать аутентификацию по паролю, если она присутствует
Первый сделает нормальный поиск.
Второе исключает ученика из таблицы, если учетная запись имеет достаточные привилегии (как правило, учетная запись администратора школы будет выполнять такой запрос и о привилегиях, о которых говорилось выше).
Вам не нужно вводить данные формы, чтобы выполнить SQL-инъекцию.
Никто раньше на это не указывал, поэтому я могу предупредить некоторых из вас.
В основном мы будем пытаться исправить ввод форм. Но это не единственное место, где вас могут атаковать с помощью SQL-инъекции. Вы можете провести очень простую атаку с помощью URL-адреса, который отправляет данные через запрос GET; Рассмотрим следующий пример:
<a href="/show?id=1">show something</a>
Ваш URL-адрес будет выглядеть http://yoursite.com/show?id=1
Теперь кто-то может попробовать что-то вроде этого
http://yoursite.com/show?id=1;TRUNCATE table_name
Попробуйте заменить table_name настоящим именем таблицы. Если он правильно назвал ваш стол, он опустошит ваш стол! (Очень легко заставить этот URL-адрес с помощью простого скрипта)
Ваш запрос будет выглядеть примерно так...
"SELECT * FROM page WHERE id = 4;TRUNCATE page"
Пример уязвимого кода PHP с использованием PDO:
<?php
...
$id = $_GET['id'];
$pdo = new PDO($database_dsn, $database_user, $database_pass);
$query = "SELECT * FROM page WHERE id = {$id}";
$stmt = $pdo->query($query);
$data = $stmt->fetch();
/************* You have lost your data!!! :( *************/
...
Решение - используйте методы PDO prepare() и bindParam():
<?php
...
$id = $_GET['id'];
$query = 'SELECT * FROM page WHERE id = :idVal';
$stmt = $pdo->prepare($query);
$stmt->bindParam('idVal', $id, PDO::PARAM_INT);
$stmt->execute();
$data = $stmt->fetch();
/************* Your data is safe! :) *************/
...