Пишем PHP скрипты без багов. Автор: Dr.Z3r0 Написанно специально для antichat.ru 0.INTRO Для начала хотелось бы рассказать вкратце о чем эта статья. В этой статье будут описаны все функции неверное использование которых может привести к уязвимостям в ваших скриптах, также будут рассмотрены функции которые могут защитить от уязвимостей, к каждому пункту буду стараться приводить самый простой пример. Здесь не будут рассмотрены любые шаманства с php.ini, .htacsess-ами и прочее так это совсем другая тема. Для усвоения данной информации вам бы не помешало бы иметь следующее: что нибудь в голове (имеется ввиду серое вещество, а не пустота ), знание PHP и желательно прямые руки. Ну что же начнем… 1.SQL injection Опасные функции: mysql_query() mssql_query() и др. Описание: Сейчас многие скрипты будь то форумы, гостевые, портальные системы в своем большинстве при работе используют базы данных (Чаще всего MySQL, MsSQL, PostgreSQL, OracleSQL). Принцип этой работы основывается на запросах к этой БД. Что то типа SELECT * FROM tables WHERE column='[value]'. Так вот SQL injection это бага при котором взломщиком модифицируется оригинальный запрос к БД таким образом чтобы при выполнении запроса была выведена нужная ему информация из БД. Рассмотрим пример: Code: <?php ... mysql_query("SELECT * FROM news WHERE id='".[b]$_GET['id'][/b]."'"); ... ?> Ну вообщем по идее разработчика запросом должны возвращаться все записи из таблицы где их значение в столбце id равно переменной $_GET['id']. Но переменная $_GET['id'] не проходит фильтрации на содержание одинарной кавычки что дает взломщику возможность модифицировать этот запрос скажем до вот такого: Code: SELECT * FROM news WHERE id='[color=orange]-1' UNION SELECT 1,login,password,4,5,6 FROM admins /* [/color]' Вообще на тему SQL injection я уже писал статью http://forum.antichat.ru/showthread.php?p=407239 Защита: Использовать функцию mysql_escape_string() Пример: Code: <?php ... mysql_query("SELECT * FROM news WHERE id='".[color=white]mysql_escape_string([b]$_GET['id'][/b])[/color]."'"); ... ?> Также стараться не использовать оператор LIKE без фильтрации символов % и _ в тех местах где это не нужно. Не использовать в запросах переменные без обрамляющих их кавычек. 2. PHP-including Опасные функции: include() include_once() require() require_once() Описание: Данные функции позволяют включить в тело выполняемого скрипта какой нибудь файл с php-кодом. Отсутствие фильтрации либо плохая фильтрация символов может привести к так называемому инклудингу файлов. Пример: Code: <?php ... [color=red]include[/color]('modules/'.[b]$_GET['mod'][/b].'.php'); ... ?> Отсюда видно что переменная $_GET['mod'] вставляется в параметр без фильтрации что само по себе дает инклудинг файлов с расширением .php а при отсутствии фильтрации нулевого байта (обозночает конец строки) это чтение файлов на которые хватает прав на сервере, а при включенной директиве allow_url_open и при отсутствии строки 'modules/' перед переменной как и вообще любой другой строки возможен удаленный инклуд, то есть инклуд своего проивольного php кода, тогда опасность этой баги возрастает на порядок. Защита: Самое простое это функция basename(). Она извлекает из пути файла его имя. Плюс, не побоюсь этого слова, обрезание расширения, либо удаление точки. Пример: Code: <?php ... [color=white]$mod=str_replace('.','',basename([b]$_GET['mod'][/b]));[/color] [color=green]include[/color]('modules/'. [color=white]$mod[/color].'.php'); ... ?> 3. Чтение произвольных файлов Опасные функции: fopen() file() readfile() show_source() highlight_file() Описание: Ну думаю понятно что это за бага из ее названия . Рассмотрим пример: Code: <?php ... [color=red]readfile[/color]('content/'.[b]$_GET['action'][/b].'.html'); ... ?> Возможности эксплуатирования этой баги варьируются в зависимости от включены ли magic_qoutes в php.ini или нет. Если да то ничего полезного кроме, как чтение любых html файлов нам не вытрясти так, как нулевой символ будет слешироваться, а если нет, то эта уязвимость приведет к чтению любых доступных файлов на сервере. Защита: Тоже самое как и при инклудинге: Code: <?php ... [color=white]$action=str_replace('.','',basename([b]$_GET['action'][/b]));[/color] [color=green]readfile[/color]('content/'. [color=white]$action[/color].'.html'); ... ?> 4. Обход авторизации/отсутствие авторизации Описание: Все что называется данной багой рассмотреть просто не возможно, но некоторые вещи я опишу. Очень часто программисты допускают "детские" уязвимости, например после прохождения авторизации в адресную строку дописывается переменная которая говорит о том что пользователь успешно прошел авторизацию и в остальных скриптах проверяется существует ли такая переменная, например http://site/script.php?auth=1. Разумеется так делать нельзя. Либо вот такая ошибка: Code: <?php ... if(super_mega_proverka())$user=true; ... if($user)echo("Вы успешно залогинились ;)"); ... ?> При включенной директиве register_globals в php.ini мы имеем возможность "успешно залогинится" написав в адресной строке, что-то типа http://site/script.php?user=1 Очень часто встречаются незапароленные админки, которые почему-то запрещены к индексации поисковиками в файлике robots.txt. Это не значит, что не надо вписывать незапароленные админки в этот файлик, это значит, что нужно писать авторизацию. Люди не ленитесь пишите проверку на авторизацию! Не так уж это и сложно… 5. Выполнение произвольного кода в командной строке. Опасные функции: exec() shell_exec() system() passthru() Описание: Очень-очень редкая бага встретить практически которую просто невозможно. Блин даже придумать не могу как и где ее можно использовать. Но думаю понятно к чему это может привести. Защита: Использовать функцию escapeshellcmd(). 6. Выполнение php кода Опасные функции: eval() Описание: Ну как вам известно эта функция интерпретирует переданный ей параметр как php код. Ну вот допустим пример (функция для объявления переменных если register_globals=off, еле-еле придумал что сюда можно написать ): Code: <?php ... if(is_array($_GET)) { foreach($_GET as $k => $v){ [color=red]eval[/color]('if(empty($'.$k.'))$'.$k.'="'.$v.'";<br>'); } } ... ?> Как видим любое значение любой переменной вставляется в строку и следовательно может быть произведена модификация исходного php кода в функции eval путем подобного обращения к скрипту http://site/script.php?bla=1"; phpinfo();// что разумеется выведет нам phpinfo(). Защита: Использование слеширования опасных символов функцией addslashes(). Пример: Code: <?php ... if(is_array($_GET)) { foreach($_GET as $k => $v){ [color=green]eval[/color]('if(empty($'.[color=white]addslashes[/color]($k).'))$'.$k.'="'.[color=white]addslashes[/color]($v).'";<br>'); } } ... ?> 7. Загрузка и выполнение произвольного php кода Описание: Данная уязвимость возникает при загрузки файла на сервер при отсутствии, либо неполной фильтрации типа загружаемого файла. Например: Code: <?php ... $attach = $_FILES['attach']; if (!empty($attach['name']) && is_uploaded_file($attach['tmp_name'])) { move_uploaded_file($attach['tmp_name'], "upload/".$attach['name']); } ... ?> Либо проверка присутствует но она проходит по его mime типу получаемому от браузера который можно легко подделать. Пример Code: <?php ... $attach = $_FILES['attach']; if (!empty($attach['name']) && is_uploaded_file($attach['tmp_name'])) { [color=red]if(substr($attach['type'],0,5)==='image')[/color] move_uploaded_file($attach['tmp_name'], "upload/".$attach['name']); } ... ?> Защита: Решением будет использование вот такого кода: Code: <?php ... $loadext=Array('bmp','jpeg','jpg','png'); $attach = $_FILES['attach']; if ((!empty($attach['name'])) && (is_uploaded_file($attach['tmp_name']))) { $attach_name=explode('.',$attach['name']); $attach_ext=$attach_name[count($attach_name)-1]; $attach_name=$attach_name[0]; if(in_array($attach_ext, $loadext)) { if((preg_match("/^[-0-9A-Z_\[\]]+$/i", $attach_name))&&($attach['size'] <=5000)) { move_uploaded_file($attach['tmp_name'], "upload/".$attach_name.$attach_ext); } } } ... ?> Будут отсеяны файлы у которых расширение не bmp, jpeg, jpg или png, размер которых больше 5000 байт и имя которого не соответствует регулярному выражению "/^[-0-9A-Z_\[\]]+$/i". Таким образом мы убиваем двух зайцев, мы не даем грузится файлам не нужного типа, имени, размера и мы убираем всякие лишние, так сказать вторые расширения у файла, так, как благодаря им файл может интерпретироваться как php код. Хотелось бы сказать поподробнее о двойных расширениях. Дело в том что файл blaa.php.rar будет интерпретирован как php-файл если последнее расширение не внесено в mime.types. Только к сожалению данный способ не спасет от загрузки php кода с разрешенным расширением, что при наличии инклудинга может привести к взлому вашего сайта. 8. Раскрытие пути Описание: Данную багу трудно назвать багой, но это порой необходимая вещь при взломе. Благодаря ей взломщик может узнать путь от корня сервера до папки где находится сайт. Собственно возникает она при отображении ошибок. Защита: В самом начале скрипта вписать error_reporting(0); скроет отображение всех ошибок, причем лучше писать сам код скрипта так чтобы никаких ошибок не появлялось, а эту директиву вписывать на всякий случай. 9. Перезапись переменных Опасные функции: import_request_variables() extract() parse_str() Описание: Эти функции - почти замена включенной директиве register_globals. И все они позволяют переписать любые переменные объявленные раньше будь то простая переменная либо переменная из массивов _SESSION, _SERVER и т.д. Что приводит к непредсказуемым последствиям . Ну вот пример: Code: <?php ... [color=red]import_request_variables[/color] ("GPC"); ... if(!empty($_SESSION['user'])) { echo('Добрый день пользователь '. $_SESSION['user']); ... } ... ?> Если обратиться к этому скрипту вот так http://site/script.php?_SESSION[user]=гыгы…лол то мы получим интересный результат Защита: Использовать функцию extract с флагом EXTR_SKIP вот пример: Code: <?php … [color=green]extract[/color]($_GET, EXTR_SKIP); [color=green]extract[/color]($_POST, EXTR_SKIP); … ?> 10.XSS Описание: Cross Site Scripting (Межсайтовый скриптинг). Собственно принцип этой баги заключается в выводе значения нефильтрованной переменной с целью изменения исходного кода страницы. В основном это вписывание java скрипта отправляющего cookies пользователя на сниффер взломщика. Но на самом деле применений этой баги много. Пример: Code: <?php ... echo('Открыта страница номер '.[b]$_GET['id'][/b]); ... ?> Обратившись к скрипту так http://site/script.php?id=<script>alert(document.cookie)</script> мы увидим свои cookies. Защита: Использовать функцию htmlspecialchars() с флагом ENT_QUOTES. Пример: Code: <?php ... echo('Открыта страница номер '.[color=white]htmlspecialchars[/color]([b]$_GET['id'][/b],ENT_QUOTES)); ... ?> Опять откроем [/b]http://site/script.php?id=<script>alert(document.cookie)</script>[/b] в браузере и мы увидим на экране мирную надпись "Открыта страница номер <script>alert(document.cookie)</script>" 11. Подбор пароля Описание: Во многих форумах и портальных системах есть возможность подобрать пароль какого-нибудь юзера через форму авторизации либо через cookies(про cookies очень часто кодеры забывают), то есть при проверке пароля/логина нет, допустим, картинки с числом или задержки между неверным вводом пароля. Защита: Прикрутить временную задержку между неверными вводами пароля и неверных авторизаций с помощью cookies. Плюс банить тех кто неверно регестрировался больше определнного количества раз. Спасибо groundhog за совет... 12. Соц. инженерия Описание: Не знаю как назвать правильно . Нигде еще ни читал о таком методе атаки. Вообщем вспоминаем те форумы/cms где допустим можно сменить пароль в профиле без ввода старого, либо вспоминаем про панель администратора где для авторизации достаточно только cookies. Рассмотрим на примере: Code: <?php ... if(super_mega_proverka()){ if($_GET['action']==='changepass')change_pass($_POST['new_pass']); ... } ... ?> Где функция super_mega_proverka() проверяет залогинен ли юзер или нет, а функция change_pass() меняет текущий пароль юзера на новый. Теперь мы создаем другую страницу Code: <form name='abra' action='http://site/script.php?action=changepass' method='post'> <input type='hidden' name='new_pass' value='123456'> </form> <script>this.abra.submit();</script> И заставим жертву посетить эту страницу. После посещения у жертвы в профиле пароль изменится на 123456. Таким же образом можно заставить и админа выполнить какие-либо действия в админ панели. Защита: Использовать индетификатор юзера. Примеры Code: <?php ... //Генерируем индетификатор юзера на основе его пароля //и который мы будем вставлять во все ссылки. $uid=md5(substr(md5(get_user_password()),0,16)); ... ?> И собственно второй скрипт где этот индетификатор проверяется: Code: <?php //Ну и собственно проверяем этот индетификатор на валидность if(!empty($_GET[' uid '])){ if($_GET[' uid ']!= md5(substr(md5(get_user_password()),0,16))) { header("Location: index.php"); die(); } }else{ header("Location: index.php"); die(); } ... ?> Теперь пояснение. Вообщем в первом скрипте собственно генерируется индитефикатор юзера который будет вставляться во все ссылки для проверки валидности индитификатора, который и будет обозначать что юзер пришел не от куда то там со стороны а выполняет то что он сам хочет . Кто не понял может приглядеться как работает админ панель в cms PHP fusion. 13. OUTRO Старайтесь по мере возможности проверять переменные на то: 1) Существуют ли они? (empty() is_set()) 2) Подходит ли тип переменной? (is_int() is_str() is_array()) Используйте error_reporting(0) -полезная вещь. Проверяйте значение referer, пусть можно и подделать, но это доставит неприятность взломщику. Вот я думаю и все. Я надеюсь эта статья вам чем то помогла. Ну что много уязвимостей нашли у себя в скриптах по прочтению ? Надеюсь, нет. P.S. Машину глючит, а человека тем более, поэтому буду рад услышать объективные комментарии по теме.
Не выход. Оно не всегда спасает... Лучше если параметр запроса целое число - приводить его к целому с помощью intval, если параметр строка - регулярные выражения в руку. Т.е. задать шаблон, который определяет, что входящие символы в параметрах это цифры и латинские буквы... htmlspecialchars($_GET['id'], ENT_QUOTES); Лучше вести счётчик неудачных попыток захода, и банить по достижении какого-то количества. Твой метод с задержкой - просто несколько замедлит этот процесс. Лучше использовать хеш + хорошую соль, при достойном пароле, и отсутствии сведений о соли у злоумышленника все попытки будут безуспешными. empty - не проверяет на существование. Оно проверяет на пустоту значения.
Перед функциями работы с MySQL ставьте '@'. Даже если где-то упустите какой-то параметр, ломать вслепую хакеру доставит мало удловольствия.
!empty($var) - проверяет на непустоту! И ненадо лохматить бабушку. Если переменной $var не существует это спровоцирует Warning. Например, empty вернёт true когда переменная существует и она пуста - скажем строка (""), или целое число, но оно 0. Имхо не имхо, но тот флаг, на который я тебе указал вынуждает обрабатывать и одинарные и двойные кавычки. По умолчанию обрабатываются только двойные кавычки. Теперь уверен что этого от xss достаточно? Скажем, если в коде атрибуты заключаются в одинарные кавычки?
Да хоть на двести... Я тысячи раз видел такую ситуацию, не зная, или не понимая сущности функции, некоторые индивидуумы её используют (как пример - empty), при этом log_error=on, а display_error=off... И в результате что? Логи на гигабайты - Warning: use of undefined variable...
Расскажи мне... Я везде юзаю эту функцию со включенным отображением ошибок... и че то нигде до сих пор не видил варнинга по этому поводу...
Хм... ХЗ... Вроде и правда работает как is_set... Хотя судя по http://ru.php.net/manual/ru/function.empty.php оно автоматом заменяет выражение на NULL, а уже empty от NULL работает корректно... В общем ладно, тут я тогда настаивать не буду, оформим это как замечание по технике программирования...
empty возвращает false, если переменная существует и имеет не пустое или не нулевое значение; true в обратном случае. Т.е. если переменной нет или она есть и её значение пустое или нулевое. ->true Отсюда вывод: однозначно существование переменной она не проверяет! PS groundhog прав - проверяет на НЕПУСОТУ! Т.е. если переменная не существует или её значение пустое или 0, то результат одинаков.
По поводу баги №6 Code: Защита: Использование слеширования опасных символов функцией addslashes(). А если в строку прописать stripslashes($_GET PHP: )&php=(тот_что_ты_показывал)[/B] :D
можно ещё и так: PHP: <? if(is_file("modules/".$_GET['module'])) include("modules/".$_GET['module']; ?>
Ага, а если я в кач-ве $module передам ../../../../../etc/passwd ? или ты хочешь сказать, что при проверке is_file файла passwd функция возвратит false?
n1†R0x, ИМХО апач тебя пошлёт, он кажись не обрабатывает такие пути ну в смысле ты указываешь папку а потом из неё возвращаешся, нипаймёт он тебя modules/../../../../../etc/passwd
I-I()/Ib и groundhog Красавцы, оба шарите, молодцы. Как говорится в споре рождается истина... обоим по +5 поставил
Вообще, как мне кажется, данную функцию будет обрабатывать PHP, а не апач. И мне не кажется в любом случае, что меня пошлют) разве что прав не хватит, но это уже другой разговор... Если бы апач (точнее, пхп) посылал, не было бы php-инклудов. А относительные пути с движением вверх поддерживаются еще со времен ms-dos
n1†R0x, ты прав, при такой реализации: Можно проинклудить незапланированное файло, ну, конечно, если не накладывается никаких дополнительных ограничений.
Да? Х_Х Ну а как от этого защититься-то?! if(is_file("./modules/".$_GET['module'])) include("./modules/".$_GET['module']; Так чтоли?