Лабораторная работа №1 «SQL-инъекции»
Атака может быть реализована против любой СУБД, поддерживающей SQL: MySQL, PostgreSQL, Oracle и т.д.
Далее рассмотрим ряд уязвимостей, приводящих к возможности реализации SQL-инъекции.
Некорректная обработка входных параметров
В случае, когда входные данные некорректно или недостаточно фильтруются, они могут содержать служебные символы и SQL-код. Вследствие этого злоумышленник может передать специально сформированную строку, которая будет подставлена в запрос к БД.
Пример уязвимого кода на PHP: Этот код получает значение параметра username, переданное пользователем. Затем это значение непосредственно используется для запроса к БД без какой-либо дополнительной обработки.
Для реализации SQL-инъекции в данном случае достаточно передать в качестве username следующую строку: ‘ OR ‘1’ = ‘1 . При этом выражение в условии запроса примет вид: username = » OR ‘1’ = ‘1’ . Очевидно, что оно всегда верно, поскольку всегда верно выражение ‘1’ = ‘1’ . Таким образом, запрос вернёт все строки из таблицы users.
Простейшей проверкой на недостаточную фильтрацию входных параметров служит подстановке символа одинарной (реже двойной или обратной) кавычки. Если входные данные обрабатываются некорректно, то такая подстановка гарантированно приведёт к ошибке, поскольку не будет обнаружено закрывающей кавычки. Аномальным поведением считается такое, при котором страницы, получаемые до и после подстановки кавычек, различаются, если при этом не получена страница с информацией о неверном формате параметров.
Несанкционированный доступ к данным
Язык SQL позволяет объединять результаты нескольких запросов при помощи оператора UNION. Это позволяют злоумышленнику получить несанкционированный доступ к данным в таблицах, не используемых в исходном запросе.
Приведённый выше код предназначен для отображения новости с заданным id из таблицы news.
Если передать в качестве id строку -1 UNION ALL SELECT username, password, NULL FROM users , то запрос не вернёт ни одной строки из таблицы news, поскольку строки с идентификатором -1 заведомо не существует. При этом посредством оператора UNION будут выбраны все строки из таблицы users.
Для успешного внедрения оператора UNION количество столбцов в выборках должно совпадать. Если количество столбцов в первой (исходной) выборке больше, то вторую необходимо дополнить соответствующим количеством констант (например, NULL).
Слепая SQL-инъекция
SQL-инъекция называется слепой (англ. blind SQL injection) в том случае, когда результат выполнения запроса недоступен злоумышленнику. При этом уязвимый веб-сайт по-разному реагирует на различные логические выражения, подставляемые в уязвимый параметр. Таким образом, злоумышленник может подобрать значения некоторых параметров (версия СУБД, текущее имя и права пользователя и т. д.), подставляя в запрос соответствующие логические выражения.
Рассмотрим следующий код: Его задача — вывести описание товара из таблицы catalog по указанному id. В случае, если товара с заданным id не существует, будет выведено соответствующее сообшение.
Если в качестве id передать строку 1 AND 1 = 1 , то условие запроса не изменится, поскольку выражение 1 = 1 всегда истинно. Если товар с id равным 1 существует, то в ответ будет получена страница с его описанием. Если затем в качестве id передать строку 1 AND 1 = 2 с заведомо ложным условием, то будет получено сообщение о том, что запрошенный товар не существует. Таким образом, можно подобрать значения некоторых параметров БД, например, условие SUBSTR(@@version, 1, 1) = 5 будет верным только если версия СУБД равна 5.
Особенности реализации SQL-инъекций
Иногда злоумышленник может провести атаку, но не может видеть более одной колонки результатов. В таком случае можно объединить несколько строк в одну при помощи конкатенации:
В некоторых случаях запрос, подверженный SQL-инъекции, имеет структуру, усложняющую или препятствующую внедрению операторов SQL. Например, следующий код: выбирает строки с указанным идентификатором author_id, сортирует их по дате и выводит 20 первых записей. Простая подстановка оператора UNION вместо author_id приведёт к ошибке из-за оставшейся части запроса: ORDER BY date DESC LIMIT 20 . В этом случае часть запроса необходимо экранировать при помощи символов комментария (--, /* или # в зависимости от СУБД). Часть строки, отделённая этими символами, будет проигнорирована и запрос успешно выполнится.
При этом важно помнить, что все скобки и кавычки должны быть закрыты, чтобы не вызывать ошибок при выполнении запроса. Если часть запроса экранируется, то соответствующие символы нужно передавать в явном виде.
Для разделения команд в языке SQL используется символ ; (точка с запятой). Внедряя этот символ в запрос, злоумышленник получает возможность выполнить несколько команд в одном запросе. Это позволяет выполнять операции, отличные от применяемых в исходном запросе, например, вставлять, изменять или удалять строки. Передав в качестве id строку -1; INSERT INTO users(username, password) VALUES (‘foo’, ‘bar’) , злоумышленник может несанкционированно вставить новую строку в таблицу users.
Предотвращение SQL-инъекций
Наиболее общими способами предотвращения SQL-инъекций являются фильтрация, экранирование и проверка всех входных данных. В языке PHP для этого могут применяться такие функции, как addslashes() и mysql_real_escape_string().
addslashes() экранирует специальные символы, добавляя к ним символ \ (обратный слэш). Таким образом, символ ' заменяется на \', " — на \" и т.д. Это позволяет избежать внедрения SQL-кода только в том случае, когда экранируемая строка в запросе заключена в кавычки.
mysql_real_escape_string() работает аналогично addslashes(), но учитывает особенности диалекта MySQL, экранируя некоторые дополнительные символы. Как и addslashes(), она работает только в случае, когда экранируемая строка заключена в кавычки.
Кроме того, в языке PHP существует ряд функций, позволяющих привести значение параметра к определённому типу. Например, функции intval() и floatval() позволяют преобразовать переменную к целому числу и числу с плавающей запятой соответственно. Это делает невозможным подстановку произвольных строк в запрос к БД.
Name already in use
pasta / security / sql-injection.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
SQL-инъекция (внедрение SQL кода) и способы борьбы с ней
SQL-инъекция — это уязвимость, которая возникает, когда у злоумышленника появляется возможность модифицировать SQL запрос в приложении. Она может привести как минимум к краже любых данных из базы (например, списка пользователей). В особо запущенных случаях она позволяет менять данные в базе, читать или создавать файлы с произвольным содержимым на сервере и даже выполнять какие-то команды.
Чтобы понять, как работает эта уязвимость, надо представлять, что такое SQL базы данных, и как обычно приложение с ними работает. База данных — это такое хранилище информации. Работа с ним ведется путем отправки ему запросов на языке SQL. Запросы могут получать или изменять какие-то данные в базе. Например, запрос SELECT * FROM news ORDER BY added_date DESC LIMIT 10 запрашивает из базы данных 10 последних новостей (при условии что колонка added_date хранит дату публикации новости).
Часто у нас появляется необходимость подставлять в запрос пришедшие от пользователя данные. В случае, если это делать неправильно (не используя проверок и экранирования), мы можем создать уязвимость, которая позволит злоумышленнику взломать наше приложение.
SQL инъекции не привязаны к конкретному языку программирования и базе данных. Они могут быть в любом приложении, которое использует SQL запросы. Мы будем рассматривать примеры кода на PHP, работающем с сервером баз данных MySQL.
Допустим, у нас есть страница просмотра архива новостей за определенный год. При переходе по ссылке /archive.php?year=2011 мы должны увидеть заголовки новостей за этот год. Рассмотрим пример неправильного уязвимого кода на PHP, реализующего эту задачу:
Если приглядеться к строчке $sql = «SELECT title FROM news WHERE YEAR(added_date) = $year»; , то видно, что мы подставляем в запрос пришедшие от пользователя данные безо всякой проверки. Тут и кроется уязвимость. Что, если пользователь передаст нам в параметре year такую строку?
В этом случае после подстановки получится SQL запрос:
Первая часть этого запроса не найдет ни одной новости, а вторая (после UNION) — выберет из базы email-ы всех наших пользователей и выведет их вместо заголовков новостей. Разумеется, злоумышленник не будет ограничиваться email-ами — он может по очереди выбрать все данные из нашей базы. Более того, есть программы, автоматизирующие такое извлечение данных — достаточно дать им уязвимую ссылку, а остальное они сделают сами.
В данном случае уязвимости можно было бы избежать, преобразовав $year в число:
Но это не сработает в том случае, если подставляемый параметр — строка. Правильный способ борьбы — использовать подготовленные запросы с плейсхолдерами (о них ниже).
Рассмотрим второй пример уязвимости. Допустим, у нас есть такой неправильный код проверки логина и хеша пароля (мы разумеется не храним в базе сами пароли, а только их соленый хеш):
Здесь мы опять подставляем данные от пользователя в запрос и как следствие получаем уязвимость. Злоумышленник может передать такой логин: login’ — . При подстановке в запрос получается:
Знак — обозначает в языке SQL комментарий и сервер базы данных проигнорирует все, что за ним, таким образом из условия пропадет проверка на совпадение хеша, что позволяет злоумышленнику залогиниться под любым логином, не зная пароля.
В данном случае методом защиты (кроме самого правильного способа — использования плейсхолдеров и подготовленных запросов) могло бы быть экранирование переданной строки. Например, в библиотеке mysqli это делается методом real_escape_string():
В PDO экранирование делается методом quote(), который не только экранирует переданную строку, но еще и заключает ее в нужные кавычки (в зависимости от используемой базы данных):
При экранировании перед спецсимволами вроде символа кавычки (а также двойной кавычки, перевода строки, бекслеша) подставляются бекслеши. Таким образом, переданная злоумышленником строка login’ — преобразуется в login\’ — , и экранированная бекслешем кавычка не закрывает строку, а является ее частью. И символ — тоже воспринимается как часть строки, а не комментарий. Получается запрос WHERE login = ‘login\’ — ‘ AND hash = ‘. ‘ который работает как и задумано. Подробнее про экранирование можно прочесть в мануале по MySQL рус. англ..
Иногда результаты уязвимого запроса не выводятся на экран. Но даже в таком случае злоумышленник может получать данные, используя задержку в выполнении запроса (делая SQL запрос такого вида: если логин администратора начинается на «а», то сделать паузу в 5 секунд). Это называется «слепая» SQL инъекция.
Найдя уязвимость, злоумышленник может полностью прочитать содержимое базы данных, изменять его. Также, в некоторых случаях он получает возможность читать и записывать файлы на сервере за счет соответствующих команд в SQL. Например, MySQL позволяет записать произвольные значения из таблицы в любой файл командой SELECT . INTO OUTFILE ‘/var/www/example.com/file.txt’ , а также прочитать любой файл командой LOAD DATA INFILE ‘/etc/passwd’ . . Конечно, для этого ему еще надо найти доступные на чтение или запись файлы, но я думаю, что при желании это вполне возможно.
Но даже доступ к базе многое дает. Злоумышленник может прочитать данные администратора или создать нового пользователя с такими правами. Так он получит доступ к админке сайта, в которой сможет найти другие уязвимости (например загрузку на сервер произвольных файлов).
В некоторых случаях (зависит от конфигурации сервера) некоторые базы данных позволяют через SQL запрос запустить произвольную программу на сервере.
Также, база данных может содержать какие-то персональные данные. Вот пример, показывающий какие последствия для бизнеса может иметь утечка данных.
В 2015 году злоумышленники взломали платный сайт знакомств «Эшли Мэддисон», который предлагал респектабельным женатым мужчинам возможность найти развлечения на стороне. Хакеры выложили в интернет личные данные пользователей, включавшие в себя имена, email, адрес и географические координаты пользователей. Кроме того, выяснилась еще пара интересных подробностей — во-первых, более 99% активных пользователей сайта были мужчинами, так что успешно познакомиться друг с другом могли разве что представители нетрадиционных ориентаций, во-вторых, несмотря на то, что услуга удаления аккаунта с сайта была платной, сайт в реальности не удалял данные. Очевидно, что после такой утечки сайт фактически прекратил свое существование.
Для борьбы с уязвимостью надо использовать подготовленные запросы, когда данные не вставляются в запрос напрямую, а передаются отдельно. В этом случае сама база данных беспокоится о корректном экранировании и модифицировать запрос невозможно. Вот пример проверки логина с использованием подготовленных запросов в PDO (мануал):
Вот, как можно использовать подготовленные запросы с библиотекой mysqli (мануал):
Код получился немного громоздкий из-за того, что в mysqli все ошибки надо проверять вручную, но суть в общем понятна.
Использование параметризованных запросов позволяет не беспокоиться о безопасности и сохраняет запрос читабельным.
Через плейсхолдеры можно подставлять только числа или строки, но нельзя подставлять имена таблиц или колонок. В этом случае придется вставлять их напрямую в запрос, проверив их по списку разрешенных значений. Например, если нам надо сортировать новости по заголовку или дате в зависимости от переданного параметра, можно поступить так:
Другие способы борьбы
Кроме использования плейсхолдеров, можно вручную экранировать строковые значения через функции вроде real_escape_string() или quote() и intval / floatval для чисел, но это замусоривает код, и появляется вероятность, что кто-то подставит в запрос неэкранированное значение.
Экранирование или проверка данных всегда должны делаться рядом с тем местом, где выполняется запрос (а не в другом месте кода), чтобы с одного взгляда на функцию можно было понять, безопасная она или нет. Подготовленные запросы соответствуют этому требованию.
Не пытайся искать уязвимости на чужих сайтах без разрешения владельца. Это может быть уголовно наказуемым деянием.
Что такое SQL-атаки и как с ними бороться?
Давайте поговорим о наиболее опасной уязвимости, которая может присутствовать у вас в приложении — внедрение SQL-кода.
Внедрение SQL позволяет атакующему коду изменять структуру SQL-запросов приложения с целью кражи данных, их изменения или потенциального выполнения произвольных команд в установленной ОС.
Предположим, например, что БД вашего приложения содержит таблицу Users. В этой таблице есть столбцы Id, Username и Password, которые содержат ID, имя и пароль каждого зарегистрированного пользователя, соответственно:
А на сайте вы предлагаете пользователям форму для ввода их имени и пароля.
Отправляемые пользователем таким образом данные вставляются в SQL-запрос авторизации. Например, если пользователь ввел имя “user” и пароль “password123”, то этот SQL-запрос будет нацелен на поиск ID пользователя с совпадающими Username и Password. В случае обнаружения совпадения ваше приложение авторизует пользователя с соответствующим ID.
Атаки по внедрению SQL-кода
Так в чем же здесь кроется проблема? Ее суть в том, что атакующий может вставить специфичные для языка SQL символы, чтобы вмешаться в логику запроса и выполнить произвольный SQL-код. Например, если атакующий отправит в качестве имени пользователя следующую строку:
Сгенерированный SQL-запрос станет таким:
Последовательность — — обозначает начало комментария. Добавив эти символы в часть username запроса, атакующий закомментирует оставшуюся его часть. В итоге запрос, по сути, станет следующим:
Такой запрос, независимо от переданного пароля, вернет ID пользователя с правами администратора. Внедрившись в SQL-запрос, атакующий обошел систему аутентификации и может авторизоваться как администратор даже без пароля. В этом и заключается суть внедрения SQL — вставляя особые символы и изменяя структуру SQL-запроса, атакующие могут вынудить БД выполнить незапланированный SQL-код.
Варианты внедрения SQL
Обход аутентификации — это не единственное, чего могут добиться атакующие, используя уязвимость SQL. Помимо этого, они могут извлекать данные с сервера, не имея к ним разрешенного доступа.
Предположим, что ваш сайт позволяет пользователям обращаться к списку их электронных писем путем передачи серверу имени пользователя и ключа доступа, подтверждающего идентичность пользователя.
Этот запрос, к примеру, сгенерирует обращение к БД со следующей SQL-инструкцией:
В данном случае атакующие могут задействовать SQL-запрос для чтения данных из других таблиц, к которым текущий ключ доступа не предоставляет.
Давайте немного разделим эту полезную нагрузку. Если атакующий отправляет серверу вышеуказанный ключ доступа, то сервер выполнит следующий SQL-запрос:
Оператор SQL UNION используется для объединения результатов двух разных инструкций SELECT . Этот запрос совмещает результаты первой инструкции SELECT , представляющей электронные письма пользователя, и второй инструкции SELECT , возвращающей из таблицы Users все имена пользователей и пароли. Теперь атакующий может прочесть все имена и пароли пользователей, сохраненные в БД:
Опасность не только в SELECT
Внедрение SQL не ограничивается инструкциями SELECT . Атакующие могут также делать внедрение в инструкции UPDATE для обновления записи, DELETE для их удаления и INSERT для создания в таблице новых записей. Предположим, к примеру, что пользователи могут менять свои пароли, передавая их новые версии через HTTP-форму.
Эта форма приведет к выполнению сервером SQL-запроса UPDATE с новым паролем для текущего авторизованного пользователя, которым в данном случае является пользователем с ID 2 .
Атакующие могут контролировать раздел SET инструкции UPDATE . Что если они отправят новый пароль, например такой:
Этот запрос приведет к следующему изменению UPDATE :
В этом запросе секция WHERE закомментирована, а значит запрос изменит все пароли в таблице Users на “password12345”. После этого атакующий сможет с помощью этого пароля авторизовываться от лица любого пользователя.
Защита от внедрений SQL
Думаю, вы поняли, что возможность такого внедрения SQL-кода является серьезной уязвимостью, которую необходимо устранять. Давайте поговорим о том, как можно защитить от этого ваше web-приложение.
Вы могли слышать о нескольких техниках для предотвращения атак по внедрению SQL, таких как заранее подготовленные инструкции, утвержденный список, приведение типов и чистка. Но в чем сильные и слабые стороны этих методик и в каких случаях они лучше подходят?
- Заранее подготовленные инструкции.
- Утвержденный список.
- Приведение типов.
- Чистка.
Заранее подготовленные инструкции
Начнем с первого варианта: подготовленные инструкции, которые иначе называются параметризованными запросами. Они сводят на нет вероятность внедрения SQL.
Прежде чем углубляться в принцип работы этих инструкций важно понять, как вообще выполняются SQL-запросы. По сути, SQL — это язык программирования, а SQL-запрос — это программа. Когда такая команда поступает на SQL-сервер, он ее анализирует, компилирует и оптимизирует. В завершении сервер эту программу выполняет и возвращает ее результат.
Если же вы вставляете пользовательский ввод в SQL-запросы напрямую, то по факту переписываете программу динамически, используя этот пользовательский ввод. Поэтому атакующий и может передать данные, вмешивающиеся в код программы и изменяющие ее логику.
Подготовленные же инструкции не позволяют изменять передаваемые пользователем данные логики SQL-запроса. Они представляют собой SQL-инструкции, которые отправляются на сервер и компилируются до вставки передаваемых пользователем параметров. Это означает, что вместо передачи заполненного SQL-запроса на сервер для компиляции, вы сначала определяете всю логику SQL, компилируете ее, а затем вставляете в этот запрос параметры уже перед самым его выполнением.
После того, как параметры будут вставлены в итоговый запрос, этот запрос уже не будет анализироваться и перекомпилироваться. Все, что отсутствовало в исходной инструкции, будет рассматриваться как строковые данные, а не исполняемый SQL-код. Поэтому логическая часть программы SQL-запроса останется нетронутой. Это позволяет БД различать SQL-запрос, как состоящий из части с кодом и части с данными, независимо от того, как выглядит пользовательский ввод. Например, вот как можно реализовать подготовленные инструкции на PHP:
В подготовленной инструкции первой определяется структура запроса. Вы прописываете запрос без параметров, и в качестве их плейсхолдера ставите знак вопроса. Затем SQL-сервер скомпилирует эту строку в SQL-код, после чего вы отдельно отправляете параметры запроса. В данном случае ss означает, что передается два параметра, оба представленные строками. После этого вы выполняете запрос.
Здесь также нужно запомнить, что подготовленные инструкции не обязательно на 100% обезопасят сайт от внедрений SQL, так как использовать их нужно продуманно. Например, мы конкатенируем пользовательский ввод с функцией prepare , а затем сразу ее выполняем:
Если не отправить пользовательский ввод отдельно в качестве параметров для подготовленной инструкции, а создать SQL-запрос объединением строк, то уязвимость внедрению SQL сохранится, несмотря на применение этих инструкций.
Утвержденный список
Предположим, что ваша программа позволяет пользователям сортировать их письма по некоторому критерию. Если пользователь отсортирует письма по дате отправки, то приложение для их извлечения выполнит следующий код:
Секция ORDER BY позволяет запросу указывать, по какому столбцу упорядочивать результаты. Этот запрос вернет все письма пользователя в таблице, отсортированные по столбцу Date в порядке убывания.
Но вам нужно, чтобы пользователи могли выбирать, по какому полю сортировать письма. В этом случае вы не можете использовать для защиты запроса подготовленные инструкции. Их можно задействовать только для защиты тех полей, которые не нужны в процессе компиляции. Вы не можете применить подготовленные инструкции к именам столбцов, таблиц, операторов SQL или в секции ORDER BY . Поэтому, если вам нужно использовать в этих полях передаваемый пользователем ввод, то лучше всего для защиты от внедрения SQL подойдет утвержденный список.
Утвержденный список подразумевает прием заведомо действительных входных значений и отказ в случае ввода других. В данной ситуации вместо того, чтобы допускать со стороны пользователя произвольный ввод, можно использовать утвержденный список с названиями столбцов для секции ORDER BY . Предположим, что пользователи могут сортировать письма только по дате или по отправителю. Вы можете проверить, соответствует ли ввод пользователя одному из утвержденных значений, а затем уже вставить его в SQL-инструкцию. Вот как можно реализовать эту защиту на PHP:
У вас также есть возможность отображать передаваемые пользователем значения в предопределенные строки программы, чтобы избежать конкатенации пользовательского ввода в SQL-запросах. Например, если вам нужно, чтобы пользователи сортировали результаты в порядке возрастания либо убывания, то можно позволить им указывать логическое значение, которое будет отображаться в строку для вставки в запрос.
Приведение типов
Если пользовательский ввод должен быть представлен простым типом данных вроде логического значения, числа или даты, то можно также преобразовать строку такого ввода в более безопасный тип данных до ее включения в запрос.
Предположим, например, что вы также позволяете пользователям извлекать письма по ID и при этом знаете, что передаваемый пользователем ID всегда должен быть целым числом.
В этом случае можно преобразовать строку пользовательского ввода в целое число до того, как вставлять ее в SQL-запрос. Это гарантирует, что ввод будет числом и что никакой спецсимвол, способный повлиять на логику SQL-запроса, такое преобразование не переживет.
Возможно, вы заметили, что в этом примере можно было использовать подготовленные инструкции. Так и есть. Я советую при любой возможности задействовать вместо приведения типов подготовленные инструкции, потому что они работают для всех типов данных. Таким образом, вы сможете сохранить согласованный стиль написания кода для всех типов пользовательского ввода. Метод приведения типов должен придерживаться на случай, когда подготовленные инструкции нельзя применить.
Чистка
Последний способ защиты от внедрений SQL — это обезвреживание и чистка пользовательского ввода, что подразумевает шифрование или удаление спецсимволов, способных вмешаться в работу логики SQL. К спецсимволам, которые должны обезвреживаться и удаляться, относятся, например, одиночные и двойные кавычки, которые зачастую позволяют атакующим выходить за строку параметров. Тем не менее для каждого типа БД существуют свои спецсимволы, о которых вам нужно позаботиться.
Эту технику стоит использовать в самом крайнем случае и особо на нее не полагаться, потому что она не гарантирует предотвращение всех SQL-внедрений во всех ситуациях. Очень легко упустить один спецсимвол, который сможет такое внедрение осуществить.
Давайте еще раз взглянем на эти запросы. Что если вам нужно, чтобы пользователи имели возможность сортировать письма по случайному полю? Если вы не можете определить утвержденный список, который в такой ситуации бы подошел, то можете сначала очистить пользовательский ввод, а затем вставить его в запрос.
Защищайтесь эффективно!
Как вы видите, есть немало вариантов защиты от внедрения SQL-кода. Лучший способ — это по возможности использовать везде подготовленные инструкции или же утвержденный список, если такие инструкции применить невозможно. Приведение типов можно рассмотреть в качестве альтернативы утвержденному списку, но опять же, если нет возможности использовать подготовленные инструкции. Чистку ввода не рекомендуется задействовать в качестве единственного метода защиты от внедрений SQL-кода. Этот метод следует применять в совокупности с подготовленными инструкциями и утвержденным списком, чтобы добиться наилучшей безопасности, а также устранить ряд других web-уязвимостей.
SQL injection для начинающих. Часть 1
Приветствую тебя, читатель. Последнее время, я увлекаюсь Web-безопасностью, да и в какой-то степени работа связана с этим. Т.к. я всё чаще и чаще стал замечать темы на различных форумах, с просьбой показать, как это всё работает, решил написать статью. Статья будет рассчитана на тех, кто не сталкивался с подобным, но хотел бы научиться. В сети относительно много статей на данную тематику, но для начинающих они немного сложные. Я постараюсь описать всё понятным языком и подробными примерами.
Предисловие
Для того, чтобы понять данную статью, вам не особо понадобится знания SQL-языка, а хотя бы наличие хорошего терпения и немного мозгов — для запоминания.
Я считаю, что одного прочтения статьи будет мало, т.к. нам нужны живые примеры — как известно практика, в процессе запоминания, не бывает лишней. Поэтому мы будем писать уязвимые скрипты и тренироваться на них.
Что же такое SQL инъекция?
Говоря простым языком — это атака на базу данных, которая позволит выполнить некоторое действие, которое не планировалось создателем скрипта. Пример из жизни:
Отец, написал в записке маме, чтобы она дала Васе 100 рублей и положил её на стол. Переработав это в шуточный SQL язык, мы получим:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе
Так-как отец плохо написал записку (Корявый почерк), и оставил её на столе, её увидел брат Васи — Петя. Петя, будучи хакер, дописал там «ИЛИ Пете» и получился такой запрос:
ДОСТАНЬ ИЗ кошелька 100 РУБЛЕЙ И ДАЙ ИХ Васе ИЛИ Пете
Мама прочитав записку, решила, что Васе она давала деньги вчера и дала 100 рублей Пете. Вот простой пример SQL инъекции из жизни 🙂 Не фильтруя данные (Мама еле разобрала почерк), Петя добился профита.
Подготовка
Для практики, Вам понадобится архив с исходными скриптами данной статьи. Скачайте его и распакуйте на сервере. Также импортируйте базу данных и установите данные в файле cfg.php
Поиск SQL injection
Как Вы уже поняли, инъекция появляется из входящих данных, которые не фильтруются. Самая распространенная ошибка — это не фильтрация передаваемого ID. Ну грубо говоря подставлять во все поля кавычки. Будь это GET/POST запрос и даже Cookie!
Числовой входящий параметр
Для практики нам понадобится скрипт index1.php. Как я уже говорил выше, подставляем кавычки в ID новости.
Т.к. у нас запрос не имеет фильтрации:
Скрипт поймет это как
И выдаст нам ошибку:
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. В данном файле, запрос имеет вид:
Тут мы делаем выборку новости по имени пользователя, и опять же — не фильтруем.
Опять посылаем запрос с кавычкой:
Выдало ошибку. Ок! Значит уязвимость есть. Для начала нам хватит — приступим к практике.
Приступаем к действиям
Немного теории
Наверно Вам уже не терпится извлечь что-то из этого, кроме ошибок. Для начала усвойте, что знак » — » считается комментарием в языке SQL.
ВНИМАНИЕ! Перед и после него обязательно должны стоять пробелы. В URL они передаются как %20
Всё, что идет после комментария — будет отброшено То есть запрос:
SELECT * FROM news WHERE user=’AlexanderPHP’ — habrahabra
Выполнится удачно. Можете попробовать это на скрипте index2.php, послав такой запрос:
Выучите параметр UNION. В языке SQL ключевое слово UNION применяется для объединения результатов двух SQL-запросов в единую таблицу. То есть для того, чтобы вытащить что-то нам нужное из другой таблицы.
Извлекаем из этого пользу
Если параметр «Числовой», то в запросе нам не нужно посылать кавычку и естественно ставить комментарий в конце. Вернемся к скрипту index1.php.
Обратимся к скрипту sqlinj/index1.php?id=1 UNION SELECT 1 . Запрос к БД у нас получается вот таким:
SELECT * FROM news WHERE 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.
Нам нужно достать Информацию о пользователе с >
Следовательно построим такой запрос:
sqlinj/index1.php?id=-1 UNION SELECT 1,2,3,4,5 FROM users WHERE >
Скрипт также продолжает выводить
Для этого, мы подставим название полей, за место цифр 1 и 3
sqlinj/index1.php?id=-1 UNION SELECT name,2,pass,4,5 FROM users WHERE >
Получили то — что требовалось!
Для «строкового входящего параметра», как в скрипте index2.php нужно добавлять кавычку в начале и знак комментария в конце. Пример:
sqlinj/index2.php?user=-1′ UNION SELECT name,2,pass,4,5 FROM users WHERE —%20
Чтение/Запись файлов
Для чтения и записи файлов, у пользователя БД должны быть права FILE_PRIV.
Запись файлов
Чтение файлов
Чтение файлов производится еще легче, чем запись. Достаточно просто использовать функцию LOAD_FILE, за место того поля, которое мы выбираем:
sqlinj/index2.php?user=-1′ UNION SELECT 1,LOAD_FILE(‘1.php’),3,4,5 —%20
Таким образом, мы прочитали предыдущий записанный файл.
Способы защиты
Защититься еще проще, чем использовать уязвимость. Просто фильтруйте данные. Если Вы передаёте числа, используйте
Как подсказал пользователь malroc. Защищаться использованием PDO или prepared statements.