Как работает SQL-инъекция из комикса XKCD «Таблицы Бобби»?

1094

Просто смотрю на:

XKCD Strip (Источник: https://xkcd.com/327/ )

Что делает этот SQL:

Robert'); DROP TABLE STUDENTS; --

Я знаю и то, 'и другое --для комментариев, но разве это слово тоже не DROPкомментируется, поскольку оно является частью одной строки?

Бланкмэн
источник
16
Если вы слушаете подкаст Stack Overflow № 31 (27 ноября 2008 г.), они на самом деле обсуждают это.
EBGreen
93
В MySQL 'нет комментариев . Даже если бы это было, перед ним нет пробела, поэтому он может заканчивать только строку, предшествующую ему.
Гонки легкости на орбите
45
Что касается XKCD, если есть какие-либо вопросы по поводу некоторых комиксов, вы всегда можете перейти к объяснению XKCD и выяснить свой ответ. Существует даже вики XKCD , которая очень полезна для некоторых хитрых комиксов, таких как геохэшинг XKCD
Анатолий
13
Я считаю, что эта ссылка должна быть записана здесь: bobby-tables.com
'
2
beta.companieshouse.gov.uk/company/10542519 - это регистрация для консультанта с именем; DROP TABLE "КОМПАНИИ"; - ООО
Алексей

Ответы:

1117

Это опускает стол студентов.

Оригинальный код в школьной программе, вероятно, выглядит примерно так

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

Will
источник
3
Ммм, WHERE с круглыми скобками вокруг аргументов довольно необычен, но, по крайней мере, избегает синтаксической ошибки ... :-)
PhiLho
60
@PhiLho: Если бы оригинальное утверждение было INSERT, тогда круглые скобки имели бы больше смысла. Это также объясняет, почему соединение с базой данных не находится в режиме только для чтения.
Ден04
3
Как объясняет @ dan04, скобки имеют больше смысла с INSERT. Оглядываясь назад, SELECTони все равно не будут работать, так как «Вставка маленьких столиков Бобби» в таблицу уже уронит стол.
ypercubeᵀᴹ
10
На самом деле, в этом примере первый запрос («добавить новую запись ...») потерпит неудачу, так как Studentsожидает больше, чем один столбец (исходный / правильный оператор содержит два столбца). Тем не менее, наличие второго столбца полезно, чтобы показать, почему комментирование требуется; и поскольку нельзя поменять имя Бобби, вероятно, лучше оставить его как есть, добавив немного больше, чем это замечание, в качестве сноски.
eggyal
7
Фамилия Бобби - или, по крайней мере, его матери, Робертс , согласно Объяснению XKCD . Я не уверен, что исправление, которое улучшило бы ясность ответа, все же.
WBT
611

Допустим, имя использовалось в переменной $Name.

Затем вы запускаете этот запрос :

INSERT INTO Students VALUES ( '$Name' )

Код ошибочно помещает все, что пользователь указал в качестве переменной.

Вы хотели, чтобы SQL был:

ВСТАВЬТЕ В СТОИМОСТЬ СТУДЕНТОВ (' Robert Tables`)

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

ВСТАВЬТЕ В СТОИМОСТЬ СТУДЕНТОВ (' Robert'); DROP TABLE Students; --')

Что вы получаете:

INSERT INTO Students VALUES ( 'Robert' );  DROP TABLE STUDENTS; --' )

--Только комментарии остаток строки.

sinoth
источник
87
Это намного лучше, чем голосование за наибольшее количество голосов, потому что оно объясняет закрывающую скобку.
Тим Бюте
1
Кстати, школьный директор в комиксах не может знать об этом или XSS, поскольку таблица учеников удалена, он не может знать, кто это сделал.
xryl669
@ xryl669 Журналы очень полезны в подобных ситуациях ... Иногда все запросы регистрируются, а иногда другая зарегистрированная информация может помочь вам определить виновника.
Инеманья
165

Как уже отмечали все остальные, ');первоначальное утверждение закрывается, а затем следует второе. Большинство фреймворков, в том числе такие языки, как 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-инъекцию, хотя вы можете делать более разрушительные действия, если вы можете предоставить несколько операторов.

Йоханнес Фаренкруг
источник
71

Нет, 'это не комментарий в 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 ответов перед моим в оранжевой полосе ... :-) Кажется, популярная тема.

PhiLho
источник
39

TL; DR

- Приложение принимает ввод, в данном случае «Нэнси», не пытаясь - санировать ввод, например, экранируя специальные символы
 
school => INSERT INTO студентов VALUES ( «Нэнси» ); ВСТАВИТЬ 0 1   
  

- SQL-инъекция происходит, когда вводом в команду базы данных манипулируют, чтобы - заставить сервер базы данных выполнить произвольную 
школу SQL => INSERT INTO студентов VALUES ( 'Robert' );
    DROP TABLE студенты ; - '); INSERT 0 1 DROP TABLE  
  
 

- Студенческие записи сейчас ушли - могло быть и хуже! 
школа => ВЫБРАТЬ * ОТ студентов ; 
ОШИБКА :   соотношению «студенты» вовсе не существует   
ЛИНИЯ 1 : ВЫБРАТЬ * ОТ СТУДЕНТОВ ; ^   
                      

Это удаляет (удаляет) таблицу ученика.

( Все примеры кода в этом ответе выполнялись на сервере базы данных PostgreSQL 9.1.2. )

Чтобы было понятно, что происходит, давайте попробуем это с простой таблицей, содержащей только поле имени, и добавим одну строку:

школа => CREATE TABLE студентов ( название TEXT PRIMARY KEY ); 
ВНИМАНИЕ : CREATE TABLE / PRIMARY KEY будет создавать неявную индекс "students_pkey" для таблицы "студентов" CREATE TABLE 
школы =>             
   INSERT INTO студентов ЗНАЧЕНИЯ ( 'Джон' ); ВСТАВИТЬ 0 1  
  

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

INSERT INTO студентов ЗНАЧЕНИЯ ( 'Foobar' );  

Заменить foobarна настоящее имя студента. Обычная операция вставки будет выглядеть так:

- Вход: Nancy 
школа => INSERT INTO студентов ЗНАЧЕНИЯ ( 'Нэнси' ); ВСТАВИТЬ 0 1   
  

Когда мы запрашиваем таблицу, мы получаем это:

школа => ВЫБРАТЬ * ОТ студентов ;   
 имя
-------
 Джон
 Нэнси
( 2 ряда ) 

Что происходит, когда мы вставляем имя Little Bobby Tables в таблицу?

- вход: Роберт '); DROP TABLE студенты; - 
школа => INSERT INTO студентов ЗНАЧЕНИЯ ( 'Роберт' ); DROP TABLE студенты ; - '); INSERT 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 невозможным, обрабатывая ввод данных как семантически отдельный от остальной части команды.

bwDraco
источник
30

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

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 выполнена ...

Вот почему параметры связывания - это хорошо.

Дэн Винтон
источник
26

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

Select *
From Students
Where (Name = '<NameGetsInsertedHere>')

SQL станет:

Select *
From Students
Where (Name = 'Robert'); DROP TABLE STUDENTS; --')
--             ^-------------------------------^

В некоторых системах selectсначала запускается команда с последующим dropоператором! Сообщение: НЕ ВСТАВЛЯЙТЕ ЗНАЧЕНИЯ В ВАШ SQL. Вместо этого используйте параметры!

CodeAndCats
источник
18

На ');торцах запроса, он не начинает комментарий. Затем он удаляет таблицу студентов и комментирует оставшуюся часть запроса, который должен был быть выполнен.

Йорн
источник
17

Автор базы данных, вероятно, сделал

sql = "SELECT * FROM STUDENTS WHERE (STUDENT_NAME = '" + student_name + "') AND other stuff";
execute(sql);

Если имя студента указано, это делает выбор с именем «Роберт», а затем отбрасывает таблицу. Часть "-" изменяет оставшуюся часть данного запроса в комментарий.

Пол Томблин
источник
Это была моя первая мысль, но вы получили синтаксическую ошибку с завершающими закрывающими скобками, нет?
PhiLho
3
Вот почему в конце есть знак, указывающий, что оставшийся текст является комментарием и должен игнорироваться.
17

В этом случае 'не является комментарием. Он используется для разделения строковых литералов. Художник комиксов полагается на идею, что у рассматриваемой школы есть динамический sql, который выглядит примерно так:

$sql = "INSERT INTO `Students` (FirstName, LastName) VALUES ('" . $fname . "', '" . $lname . "')";

Так что теперь символ 'завершает строковый литерал до того, как программист ожидал этого. В сочетании с; Чтобы завершить оператор, теперь злоумышленник может добавить любой SQL-запрос. Комментарий в конце должен убедиться, что любой оставшийся sql в исходном операторе не препятствует компиляции запроса на сервере.

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

Джоэл Коухорн
источник
16

Символ 'в SQL используется для строковых констант. В этом случае он используется для окончания строковой константы, а не для комментария.

Rockcoder
источник
7

Вот как это работает: предположим, что администратор ищет записи ученика

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; --

Двойная черта (-) просто закомментирует оставшуюся часть запроса.

Это опасно, так как может аннулировать аутентификацию по паролю, если она есть

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

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

Без имени
источник
SELECT* FROM sutdents ...- вы забыли "с". Это то, что вы бросаете. DROP TABLE STUDENTS;
DevWL
4

Вам не нужно вводить данные формы, чтобы сделать 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! :) *************/
...
DevWL
источник