Безопасность, о которой все так много говорят…

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

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

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

Одна из самых распространённых атак (я не беру 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 и не ломать голову с запросом. Очевидно, что если скрипт делает выборку не только по id, но и по другому полю (например, WHERE `id` = 'id' AND `member_id` = 'member_id'), то простая подмена числа не сработает. А инъекция будет работать.

Сама по себе техника простая, но очень эффективная и деструктивная. Когда я работал над сайтом uk-swingers.com (исправлял баги), я показал заказчику, как из его базы можно было воровать… номера кредитных карточек с кодами CVV, информацией о владельце и прочими данными. То, что хранение подобной информации в БД запрещено, это другая история… Да, он шифровал информацию в БД (при помощи алгоритма RC4 :-) ). Да, он стирал 4 средние цифры из номера кредитки (через несколько часов после выполнения транзакции), но был ли в этом смысл? Дело в том, что данные могли быть зашифрованы как угодно, но браузеру они передавались в открытом виде. А номер кредитной карточки можно легко восстановить — во-первых, можно угадать BIN, исходя из территориальной близости к банку, во-вторых, перебором можно вычислить возможные номера карточки (для валидации номера используется формула Луна). На другом сайте (имя пока открыть не могу) так можно было читать чужие письма и воровать почтовые адреса и пароли (и устраивать кучу других гадостей).

Немного сложнее со строками. Я провел исследование, и выяснил, что если строка передается на сервер из <input type="hidden">, то она далеко не всегда экранируется (особенно, если авторство кода принадлежит индусам).

Пример с реального сайта (кстати, писали даже не индусы, а вьетнамцы):

[-]
View Code PHP
    $name = $_REQUEST['member'];
    $query = "SELECT * FROM {$prefix}profiles WHERE user = '$name'";
    $res = mysql_query($query, $link);

script.php?member=testdavid

[-]
View Code MySQL
SELECT * FROM tuvinh_profiles WHERE user = 'testdavid'

Инъекция проходит аналогично: так как строки в MySQL заключаются в одинарные или двойные кавычки, то запрос модифицируется так, чтобы закрыть кавычку:

[-]
View Code MySQL
SELECT * FROM tuvinh_profiles WHERE user = '' OR 1 /* " OR 1 /*
[-]
View Code MySQL
SELECT * FROM tuvinh_profiles WHERE user = "' OR 1 /* " OR 1 /*

Аналогично вышеприведённому примеру, в запрос можно добавить LIMIT и ORDER BY.

Ладно, хватит голой теории, переходим к практике :-)
Большую популярность получили сайты, публикующие различные статьи различных авторов: владельцы сайтов получают контент, авторы — обратную ссылку на свой сайт и возможность показывать свою рекламу. Таких сайтов много: articledashboard.com, articledirectory.com, webarticles.com и т.п.

Один из таких сайтов (articledashboard.com) писали индусы :-) Заинтересовали они меня тем (articledashboard.com, не индусы), что они предлагали скачать "exact software that powers Article Dashboard for FREE". Кто не любит халяву :-) Хотя PHP-код зашифрован ionCube, индусское авторство доказывается наличием огромного файла functions.php (я подобное видел во всех проектах, когда приходилось переделывать код после индусов). А индусы не любят делать addslashes(). Тоже из опыта.

Приведу несколько скриншотов, показывающих наличие уязвимостей на сайте.

success_1.png

На сайте есть хорошая функция — загрузка своей аватарки. Казалось бы, что здесь можно сломать? Тем не менее, и описываемая ниже уязвимость сводится к недостаточной обработке входных данных.

Есть очень хороший extension для FireFox, который позволяет подделывать . То есть фактически мы можем проинструктировать FireFox посылать произвольный файл (например, php) с , соответствующим изображению (например, image/png). В том случае, если сервер проверяет тип файла только по данным, которые пришли со стороны пользователя, скрипт обманывает сам себя.

success2.png

screen2a.png

И на закуску: все эти сайты работают под ArticleDirectory.

А вы говорите, безопасность…

Мораль сей басни такова: никогда (еще раз подчёркиваю, никогда) нельзя доверять данным, которые пришли со стороны пользователя. Все данные нужно проверять и при проверках предполагать худшее. Естественно, это увеличивает размер кода и время написания программы. Но лучше заказчику потратить пару сотен долларов на безопасность, чем рвать волосы на голове после обнаружения взлома по факту.

Добавить в закладки

Связанные записи

Автор: Vladimir; опубликовано в: Безопасность; метки: mime-type, SQL-инъекция, атака, безопасность, спуфинг, уязвимость
11
Март
2008

RSS Комментарии к статье «Безопасность, о которой все так много говорят…» (12)  »

  1. Понадобился маленький магазинчик. Бесплатный и с минимумом функционала. Ни мадженто, ни ос-коммерс совершенно не подходят.
    Малюток нашёл штуки три, из них один работал на файлах и все три в 1251.
    Остановился на ShopScript free edition от WebAsyst. Полез в код и код практически сразу удручающее впечатление произвёл. А потом я добрался до формирования заказа.

    if (isset($_SESSION["gids"]) && $c) {
    db_query(“insert into “.ORDERS_TABLE.” (order_time, cust_firstname, и так далее) values (‘”.get_current_time().”‘,’”.$_POST["first_name"].”‘,’”.$_POST["last_name"].”‘,’”.$_POST["email"].”‘,’”и так далее”‘);”) or die (db_error());

    $q = db_query(“SELECT name, Price, product_code FROM “.PRODUCTS_TABLE.” WHERE productID=’”.$_SESSION["gids"][$i].”‘”) or die (db_error()); }

    Нигде предварительно массивы POST и GET не обрабатываются и не проверяются, если не считать километровых if(is_set())

    Как, например, в индексе это сделано:
    if (isset($_GET["register"]) || isset($_POST["register"]))
    $register = isset($_GET["register"]) ? $_GET["register"] : $_POST["register"];
    И так с полдюжины переменных.

    На форуме мне подсказали, что в про и премиум аналогичные дыры.

Оставить комментарий к записи «Безопасность, о которой все так много говорят…»

Вы должны быть авторизованы, чтобы иметь возможность оставить комментарий.

Подписаться, не комментируя