Ars Longa, Vita Brevis

Почему никогда нельзя доверять пользователю, если Вас беспокоит безопасность

Специалисты по безопасности, пожалуй, всем уже проели плешь, говоря о том, что все данные, приходящие от пользователя, нужно тщательно проверять… Казалось бы, "азбучные истины", этому должны учить в школе :-) . Мне стало интересно: а многие ли сайты действительно защищены?

Внимание: материал предоставлен только в ознакомительных/образовательных целях! Автор не несёт ответственности за всё, что может случиться. Данная статья не должна рассматриваться, как практическое руководство по взлому; статья является обзором наиболее типичных ошибок программистов и объясняет, как именно невнимательность разработчиков может служить источником дыр безопасности.

Одна из самых распространённых атак (я не беру DoS, DDoS и иже с ними) — это SQL Injection Attack.

Как пишет Wikipedia,

Инъекция SQL (англ. SQL injection) — один из распространённых способов взлома сайтов и программ, работающих с базами данных, основанный на внедрении в запрос произвольного SQL-кода.

Инъекция SQL, в зависимости от типа используемой СУБД и условий инъекции, может дать возможность атакующему выполнить произвольный запрос к базе данных (например, прочитать содержимое любых таблиц, удалить, изменить или добавить данные), получить возможность чтения и/или записи локальных файлов и выполнения произвольных команд на атакуемом сервере.

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

Как уже было сказано, такие атаки проходят из-за недостаточной обработки входных параметров скрипта. Нередки случаи, когда программисты-новички, полагают, что если ID записи — это число, то скрипт всегда будет получать именно число, а не строку. Простой пример реального кода:

[-]
View Code PHP
    $id = $_GET['id'];
    $query = "SELECT * FROM {$members} WHERE id = $id LIMIT 1";
    $res = mysql_query($query, $link);

Всем хорош код, когда $id — это число. Например, если скрипт был вызван "script.php?id=123", то запрос принял бы следуюший вид:

[-]
View Code MySQL
SELECT * FROM members WHERE id = 123 LIMIT 1

Всё хорошо, но встречаются нехорошие дяди, которые вместо числа могут скормить скрипту всякую каку: для данного простого примера достаточно передать скрипту в параметрах script.php?id=0%20OR%201, чтобы заставить его выбрать все записи (о том, как обойти LIMIT, чуть ниже). Фактически, запрос будет переписан в виде

[-]
View Code MySQL
SELECT * FROM members WHERE id = 0 OR 1 LIMIT 1

Так как практически всегда для ID используются автоинкрементные поля, то выражение (id = 0) будет всегда ложно. А (FALSE OR 1) — всегда истинно.

Даже если запрос содержит LIMIT, это не спасает от выборки произвольной записи: script.php?id=0%20OR%201%20LIMIT%20A,B/*%20--

Вместо A и B — значения для offset и count соответственно, например:

[-]
View Code MySQL
SELECT * FROM members WHERE id = 0 OR 1 LIMIT 5,1/*-- LIMIT 1

Что примечательно: MySQL успешно выполнит запрос не смотря на то, что комментарий-то не закрыт. А так как /* */ — многострочный комментарий, то запрос отлично выполнится, даже если он многострочный.

Есть и другой метод — более простой, но не всегда рабочий — просто передать скрипту нужный id и не ломать голов