Любая атака на сайт - это побуждение скрипта на непредусмотренные действия. То есть нарушение запланированного сценария. По этому, основой безопасности является четкая и правильная работа с данными, которые так или иначе могут проникнуть в скриптизвне.
И вот тут начинаются обычные ошибки. Самая большая и распространенная - фильтрация на "опасные" символы.
Не бывает опасных символов, это нужно понять и усвоить. Если бы символы были опасными, их небыло бы ни в спецификациях ни даже на клавиатуре.
Есть ситуации, в которых определенная комбинация символов может нарушить запланированный сценарий. Но это совсем не значит, что их нужно фильтровать (то есть запрещать).
В php давным давно все эти ситуации просчитаны и предусмотрены. Нужно только правильно обрабатывать данные в нужных местах.
Ну а теперь самые распространенные ошибки в плане безопасности.
1. Включена директива register_globals.
Еще живы учебники, в которых прием данных рекомендуется осуществлять из массива $GLOBALS. Это опасно тем, что переменные легко переназначить и осуществить взлом. Эту директиву нужно установить в положение off от греха по дальше, а переменные инициализировать из нужных массивов. Примерно так:
$var = isset($_POST['var'])?$_POST['var']:NULL;
2. SQL-инъекции. Все уже знают, что это опасно, но мало кто из начинающих представляет себе механизм такой атаки. Инъекция - это введение в SQL запрос несанкционированных команд. Вот допустим такой запрос
$res = mysql_query("SELECT * FROM `table` WHERE login='". $login ."'");Вроде бы всё в порядке, переменная $login обрамлена апострофами, а значит все что есть в ней, не может попасть в тело запроса. То есть такой запрос не страшен:
$res = mysql_query("SELECT * FROM `table` WHERE `login`='my DELETE FROM `table` '");
Потому что жуткая команда на удаление будет записана в ячейку таблицы и ничем навредить не сможет. Так что всегда обрамляйте литеральные константы (текстовые данные) в запросе апострофами.
Но. Другое дело, если сделать как то так:
$res = mysql_query("SELECT * FROM `table` WHERE `login`='my'; DELETE FROM `table` WHERE `login`!='0'");
То есть на лицо уже два запроса. А нужно то было всего навсего ввести в поле "логин" такой текст:
'; DELETE FROM `table` WHERE `login`!='0
Первый апостроф в тексте закрыл поле и позволил вывести текст непосредственно в тело. А текст - ни что иное, как другой, крайне вредный запрос.
Вот и вся инъекция.
И хотя этот пример работать не будет (начинающим хакерам просьба не радоваться :D ), принцип атаки строится именно на этом.
Для того, что бы от неё защититься, достаточно не пустить в текст апостроф в чистом виде. Его нужно заэкранировать.
Вот тут важный момент. Многие пытаются сделать это один раз, на приемке данных. Мол что мучаться каждый раз, залепим универсальную функцию обработки и бум чпать спокойно. Это не очень то хорошая практика. Так как затрудняет восприятие кода, а самое главное - приводит к искажению данных, которые могут понадобится и не только в запросе.
Так что лучше делать так. Литеральные константы обрамлять апострофами и обрабатывать функцией mysql_real_escape_string() непосредственно в самом запросе.
Целочисленные данные лучше приводить в соответствие функцией intval() или языковой конструкцией (int), что в принципе одно и тоже. И не обрамлять апострофами.
Причем делать это нужно всегда, совершенно не важно, откуда взялись данные. Даже если возможность хакерской атаки исключена полностью, апостроф может попасть и из вполне безобидного текста. И испортить запрос.
То есть вот в такой запрос можно сунуть все что угодно и ничего страшного никогда не произойдет.
$res =mysql_query("UPDATE `table`
SET `login`='". mysql_real_escape_string($login) ."'
WHERE `id`=". (int)$id );
И не нужно греть голову, чем и как заменить и какие символы отфильтровать. Да пусть они все хранятся в базе, на то они и символы. Главное правильно обработать данные для запроса.
3. XSS
Сам по себе этот вид атаки за редким исключением, не наносит вреда самому сайту. Но он позволяет получить доступ к данным, обладая которыми, жулик может уже натворить беды. Допустим он стащит пароль в админку.
Механизм этой атаки чем то похож на SQL инъекцию. Есть даже такой термин - html-инъекция. Смысл её заключается в том, что нужно выбраться в тело тега и написать туда нужные команды на JS. Или закрыть тег и написать дальше свой. Фрейм к примеру. Или допустим прочитать куки и отправить их на снифер.
На примере такого простого фрагмента, можно убедиться, как это работает:
<form action="?" method="post">если ввести в поле допустим такой текст:
<input type="text" name="text" value="<?[SPAN=darling]php[/SPAN] echo $_POST['text'] ?>" /><br />
<input name="ok" type="submit" />
</form>
"/> <iframe src="http://www.ruporno.org" />
Попробуйте, будет очень интересно.
Тут мы закрыли тег <input> и открыли более другой.
Бороться с этим видом атаки очень просто. Нужно данные, перед выводом в поток (в браузер) обработать функцией htmlspecialchars(). Она заменит символы разметки на эквиваленты, которые не смогут влиять на HTML и соответственно не смогут спровоцировать нежелательные действия программы.
4. Такой финт (атаку) можно осуществить с любым тегом. А отсюда вытекает еще одна ошибка, это неправильная обработка bb тегов.
Это одно из самых слабых мест сайта, потому что позволяет юзеру влиять на разметку. А соответственно тут должен быть глаз да глаз.
Во первых нельзя пускать в тег ничего из POST. И вообще лучше поостеречься с регулярками. Пользоваться прямыми заменами функцией str_replace() из списка разрешенных параметров. Потому что вот такой код:
$text = !empty($_POST['text'])?$_POST['text']:NULL;
$text = preg_replace("#\[fоnt=([^\]]*?)\]([^\[]+?)\[/fоnt\]#", "<spаn style=\"$1\">$2</spаn>", $text);
echo $text;
<form action="?" method="post">
<input type="text" name="text" /><br />
<input name="ok" type="submit" />
</form>
на первый взгляд ничего опасного не содержит. Ну пропишем мы в стиль спана цвет, ну и что? Вот так примерно:
[fоnt=color:red]Красненько[/font]
и будет у нас чудесный BB редактор. Но если написать так:
[fоnt=" onmouseover="alert(document.cookie)]Красненько[/font]
то можете сами убедиться, что из этого выйдет, если помотать над надписью мышко. Только не нужно копипастить, тутя буквы заменил, иначе форум не пускает.
Тут на форуме день через день злые хацкеры пытаются провернуть такую афёру.
По этому не нужно ничего пускать в тег извне. Хоть на триста раз отфильтрованно и "обезопасенно".
Вот в таком случае никак уже в тег не пробраться:
$text = !empty($_POST['text'])?$_POST['text']:NULL;
$bb_tag = array(
'[fоnt=color:rеd]',
'[/fоnt]'
);
$tag = array(
'<span style="color:red">',
'</span>'
);
$text = str_replace($bb_tag, $tag, $text);
echo $text;
<form action="?" method="post">хотя эффект тот же самый.
<input onmouseover="" type="text" name="text" /><br />
<input name="ok" type="submit" />
</form>
Особенно часто такую ошибку допускают при bb-генерации ссылок и картинок. Там немного сложнее, потому что путь нужно указывать каждый раз свой и прямая замена уже не поможет. Допустим такой вариант
$text = !empty($_POST['text'])?$_POST['text']:NULL;
$text = preg_replace('#\[url\](.+?)\[/url]#',"<a href=\"$1\" >$1</a>",$text);
echo $text;
<form action="?" method="post">
<input onmouseover="" type="text" name="text" /><br />
<input name="ok" type="submit" />
</form>
запросто пропустит такую ссылку
[ URL]javascript: alert(document.cookie)[/URL]
И отфильтровать какие то символы тут не получится, да и не нужно это. Нужно просто определить условия, допустим не пускать ничего, что не начинается с http:// А внешние ссылки нас особо не волнуют. Это же касается и картинок:
$text = !empty($_POST['text'])?$_POST['text']:NULL;
$text = preg_replace('#\[url\]http://(.+?)\[/url]#',"<a href=\"http://$1\" >$1</a>",$text);
$text = preg_replace('#\[img\]http://(.+?)\[/img]#',"<img src=\"http://$1\" border=\"0\" />",$text);
echo $text;
<form action="?" method="post">
<input type="text" name="text" /><br />
<input name="ok" type="submit" />
</form>
Однако и этого явно не достаточно, так как можно запостить допустим такой текст:
http://"></a><script src="http://zloy.hacker.com/trojan.js"></script>И тогда все наши попытки защитить тег окажутся напрасными. Поэтому вернемся опять к самому главному правилу безопасности - ничего нельзя выдавать в поток необработанным должным образом. А значит тут в очередной раз нам поможет функция htmlspecialchars(). А чтобы все работало корректно, можно воспользоваться вот такой конструкцией:
<?php
$text = isset($_POST['text']) ? $_POST['text'] : '';
/**
* Функции генерации ссылок
* @param array $match
* @return string
*/
function createLink($match)
{
$match[2] = str_replace("\n", "", $match[2]);
return '<a href="http'. $match[1] .'://'. htmlspecialchars($match[2])
. '" target="_blanck" >'. htmlspecialchars($match[2]) .'</a>';
}
$text = preg_replace_callback('#\[url\]http(s*)://(.+?)\[/url\]#si', 'createLink', $text);
echo $text;
?>
<form action="?" method="post">
<input type="text" name="text" /><br />
<input name="ok" type="submit" />
</form>
Аналогично и с картинками. Тогда можно будет спать спокойно.
5. Поехали дальше. Подключение файлов ( php- инъекция).
Атаки такого плана основаны на подключении своих файлов с вредоносным кодом, методом внедрения его пути в конструкцию inсlude (require), когда этот путь подставляется из внешнего источника.
Допустим такой код будет уязвим при отключенных магичкских кавычках (да и при включенных тоже)
$path = isset($_GET['file'])?$_GET['file']:null;
include $path .'.php';
Я не буду вдаваться в подробности про NUL байт, нормализацию и усечение путей, информации полно в сети. Просто знайте, что это опасно.
Вообще крайне редко требуется подключение файлов с другого сервера, по этому, если нет такой необходимости, директиву allow_url_include лучше держать в положении off.
Но успокаиваться на этом не стоит. Потому что по этой схеме можно либо просканировать сайт, что тоже ни к чему хорошему не приведет, либо подключить как то за ранее залитый (о неправильной загрузке файлов позже).
По этому самым толковым способом защиты будет полное исключение возможности проникнуть в инклюд извне. А для этого нужно создать массив из "добропорядочных" путей до файлов и подставлять уже их. То есть вот в такую конструкцию уже никак не проникнуть:
$path = isset($_GET['file'])?$_GET['file']:null;
$paths = array(
'file1' => './file1',
'file2' => './file2',
'file3' => './file3'
);
include $paths[$path] .'.php';
можно так же для этих целей использовать переключатель switch(), это даже эфективнее:
$path = isset($_GET['file'])?$_GET['file']:null;
switch($path)
{
case 'file1':
include './file1.php';
break;
case 'file2':
include './file2.php';
break;
case 'file3':
include './file3.php';
break;
default:
include './error.php';
break;
}
6. Безопасность почтовых скриптов.
Обычно к отправке почты с сайта относятся весьма легкомысленно, довольствуясь простой функцией mail(). Причем пихают в неё данные прямо из POST или GET. На самом деле не все так просто. Допустим вот такой скрипт:
if(isset($_POST['ok']))
mail($_POST['email'], 'Question from web-site', $_POST['message'],
"\nContent-Type: text/plain; charset=windows-1251\nContent-Transfer-Encoding: 8bit");
<form action="?" method="post">
<input type="text" name="email" /><br />
<textarea name="message" cols="40" rows="10"></textarea><br />
<input name="ok" type="submit" />
</form>
который практикуется на лево и право - огромные ворота для спамеров. Они не применут возможностью использовать его как полигон для рассылки своих произведений, пожирая Ваш трафик и ресурс сервера. Дело в том, что в поле email можно через запятую внести целую кучу адресов и все они получат по письму. Причем с Вашим обратным адресом.
Того же эффекта можно добиться, прописав в поле subject адреса под видом скрытой копии. Вот так:
Cc: sss@mail.ru
Cc: ffff@yandex.ru
Cc: uuu@xx.ru
.
.
.
и так далее</span>
Почтовая программа добросовестно примется рассылать гнусный спам от Вашего имени. Вообще лазеек очень много, если почта отправляется не подготовленной, голой функцией mail(). Это может привести к неприятным беседам с хостером и армией разьяренных получателей спама.
Часто можно увидеть такого рода "защиты"
$mess = substr(htmlspecialchars(trim($_POST['mess'])), 0, 1000000);
то есть напихано непонятно что и непонятно для чего. Так вот это мертвому припарка.
Защититься можно только грамотно, по стандарту RFC, формируя почтовое отправление. Подробно почитать об этом можно здесь. Если не полениться, и разобраться с этим раз и на всегда, то Вы избавитесь не только от этих неприятностей, но и от разных сбоев и казусов.
7. Загрузка файлов.
Самая огромная дыра, ворота с хлебом-солью для хакеров - всевозможные аплоадеры. То есть возможность для юзера заливать на сервер свои файлы. Тут чуть чуть зазеваешься и все - что жил, то зря.
Если дать возможность грузить все подряд, без проверок, то запросто можно получить на хостинг исполняемый файл, а в нем все что угодно. Причем проверять нужно не только расширение, но и пути. Потому что можно исхитриться и перезаписать какой нибудь безобидный на первый взгляд, но подключаемый текстовичек или шаблон и все - код будет исполнен.
Предусмотреть чего пускать нельзя - очень сложно, гораздо проще разрешить то, что можно, а остальное запретить. Допустим можно архивы, так и нужно делать проверку:
$types = array( 'zip', '7z', 'rar', 'tar');
if(in_array(mb_strtolower($ext), $types))
{
// Всё отличненько .
}
else
{
// Оёёй! Низя!
}
А расширение файла лучше узнать так( функция от kirik'a):
function getExtension($filename)
{
$ext = pathinfo($filename, PATHINFO_EXTENSION);
// Если файл не имеет расширения (.htaccess, .htpasswd) - вернем null
if('.' . $ext == $filename)
{
return null;
}
else
{
return $ext;
}
}
То есть если это не архив, то нечего делать. Так же можно проверить и другие, допустим звуковые файлы. Или флэш.
Но если с ними еще как то сносно, то с изображениями несколько сложнее.
Во первых, в файл с благочестивым на первый взгляд расширением .gif или .jpeg можно написать вредоносный скрипт. И своенравный шестой ослик (IE) исполнит его прямо из файла.
А во вторых, можно напихать мусора под видом картинок и испортить галерею допустим. По этому у картинок нужно проверять не только расширение, но и другие признаки картинок. Допустим ширину.
Правда и это не панацея, потому что можно оставить в файле нормальный заголовок и все равно напихать мусора или скриптов. При этом будет определен и тип и ширина и MIME. По этому самым радикальным методом проверки является помещение файла в "карантин"(временную директорию) с последующим перемещением его оттуда с помощью графической библиотеки. Если библиотека не справится с файлом, знать он не картинка.
Вот тут есть класс аплоадера изображений, где это как раз реализовано.
Continuation follows.
В планах - защита от спам-ботов и автозаполнения форм.. Мож кто чего добавит?
_____________
Если вам недостаточно собственных заблуждений, можно расширить их мнениями экспертов.
Нужно уважать мнение оппонета. Ведь заблуждаться - его святое право.
Настаивал, настаиваю и буду настаивать на своем. На кедровых орешках.