Пишем 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 это бага при котором взломщиком модифицируется оригинальный запрос к БД таким образом чтобы при выполнении запроса была выведена нужная ему информация из БД. Рассмотрим пример:
Код:
<?php
...
mysql_query("SELECT * FROM news WHERE id='".$_GET['id']."'");
...
?>
Ну вообщем по идее разработчика запросом должны возвращаться все записи из таблицы где их значение в столбце id равно переменной
$_GET['id']. Но переменная
$_GET['id'] не проходит фильтрации на содержание одинарной кавычки что дает взломщику возможность модифицировать этот запрос скажем до вот такого:
Код:
SELECT * FROM news WHERE id='-1' UNION SELECT 1,login,password,4,5,6 FROM admins /* '
Вообще на тему SQL injection я уже писал статью http://forum.antichat.ru/showthread.php?p=407239
Защита:
Использовать функцию
mysql_escape_string() Пример:
Код:
<?php
...
mysql_query("SELECT * FROM news WHERE id='".mysql_escape_string($_GET['id'])."'");
...
?>
Также стараться не использовать оператор LIKE без фильтрации символов % и _ в тех местах где это не нужно. Не использовать в запросах переменные без обрамляющих их кавычек.
2. PHP-including
Опасные функции:
include() include_once() require() require_once()
Описание:
Данные функции позволяют включить в тело выполняемого скрипта какой нибудь файл с php-кодом. Отсутствие фильтрации либо плохая фильтрация символов может привести к так называемому инклудингу файлов. Пример:
Код:
<?php
...
include('modules/'.$_GET['mod'].'.php');
...
?>
Отсюда видно что переменная
$_GET['mod'] вставляется в параметр без фильтрации что само по себе дает инклудинг файлов с расширением .php а при отсутствии фильтрации нулевого байта (обозночает конец строки) это чтение файлов на которые хватает прав на сервере, а при включенной директиве allow_url_open и при отсутствии строки 'modules/' перед переменной как и вообще любой другой строки возможен удаленный инклуд, то есть инклуд своего проивольного php кода, тогда опасность этой баги возрастает на порядок.
Защита:
Самое простое это функция
basename(). Она извлекает из пути файла его имя. Плюс, не побоюсь этого слова, обрезание расширения, либо удаление точки. Пример:
Код:
<?php
...
$mod=str_replace('.','',basename($_GET['mod']));
include('modules/'. $mod.'.php');
...
?>
3. Чтение произвольных файлов
Опасные функции:
fopen() file() readfile() show_source() highlight_file()
Описание:
Ну думаю понятно что это за бага из ее названия ;). Рассмотрим пример:
Код:
<?php
...
readfile('content/'.$_GET['action'].'.html');
...
?>
Возможности эксплуатирования этой баги варьируются в зависимости от включены ли magic_qoutes в php.ini или нет. Если да то ничего полезного кроме, как чтение любых html файлов нам не вытрясти так, как нулевой символ будет слешироваться, а если нет, то эта уязвимость приведет к чтению любых доступных файлов на сервере.
Защита:
Тоже самое как и при инклудинге:
Код:
<?php
...
$action=str_replace('.','',basename($_GET['action']));
readfile('content/'. $action.'.html');
...
?>
4. Обход авторизации/отсутствие авторизации
Описание:
Все что называется данной багой рассмотреть просто не возможно, но некоторые вещи я опишу. Очень часто программисты допускают "детские" уязвимости, например после прохождения авторизации в адресную строку дописывается переменная которая говорит о том что пользователь успешно прошел авторизацию и в остальных скриптах проверяется существует ли такая переменная, например http://site/script.php?auth=1. Разумеется так делать нельзя. Либо вот такая ошибка:
Код:
<?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, еле-еле придумал что сюда можно написать ;) ):
Код:
<?php
...
if(is_array($_GET)) {
foreach($_GET as $k => $v){
eval('if(empty($'.$k.'))$'.$k.'="'.$v.'";<br>');
}
}
...
?>
Как видим любое значение любой переменной вставляется в строку и следовательно может быть произведена модификация исходного php кода в функции eval путем подобного обращения к скрипту
http://site/script.php?bla=1"; phpinfo();// что разумеется выведет нам phpinfo().
Защита:
Использование слеширования опасных символов функцией
addslashes(). Пример:
Код:
<?php
...
if(is_array($_GET)) {
foreach($_GET as $k => $v){
eval('if(empty($'.addslashes($k).'))$'.$k.'="'.addslashes($v).'";<br>');
}
}
...
?>
7. Загрузка и выполнение произвольного php кода
Описание:
Данная уязвимость возникает при загрузки файла на сервер при отсутствии, либо неполной фильтрации типа загружаемого файла. Например:
Код:
<?php
...
$attach = $_FILES['attach'];
if (!empty($attach['name']) && is_uploaded_file($attach['tmp_name'])) {
move_uploaded_file($attach['tmp_name'], "upload/".$attach['name']);
}
...
?>
Либо проверка присутствует но она проходит по его mime типу получаемому от браузера который можно легко подделать. Пример
Код:
<?php
...
$attach = $_FILES['attach'];
if (!empty($attach['name']) && is_uploaded_file($attach['tmp_name'])) {
if(substr($attach['type'],0,5)==='image')
move_uploaded_file($attach['tmp_name'], "upload/".$attach['name']);
}
...
?>
Защита:
Решением будет использование вот такого кода:
Код:
<?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 и т.д. Что приводит к непредсказуемым последствиям ;). Ну вот пример:
Код:
<?php
...
import_request_variables ("GPC");
...
if(!empty($_SESSION['user']))
{
echo('Добрый день пользователь '. $_SESSION['user']);
...
}
...
?>
Если обратиться к этому скрипту вот так
http://site/script.php?_SESSION[user]=гыгы…лол то мы получим интересный результат ;)
Защита:
Использовать функцию
extract с флагом EXTR_SKIP вот пример:
Код:
<?php
…
extract($_GET, EXTR_SKIP);
extract($_POST, EXTR_SKIP);
…
?>
10.XSS
Описание:
Cross Site Scripting (Межсайтовый скриптинг). Собственно принцип этой баги заключается в выводе значения нефильтрованной переменной с целью изменения исходного кода страницы. В основном это вписывание java скрипта отправляющего cookies пользователя на сниффер взломщика. Но на самом деле применений этой баги много. Пример:
Код:
<?php
...
echo('Открыта страница номер '.$_GET['id']);
...
?>
Обратившись к скрипту так
http://site/script.php?id=<script>alert(document.cookie)</script> мы увидим свои cookies.
Защита:
Использовать функцию
htmlspecialchars() с флагом ENT_QUOTES. Пример:
Код:
<?php
...
echo('Открыта страница номер '.htmlspecialchars($_GET['id'],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. Рассмотрим на примере:
Код:
<?php
...
if(super_mega_proverka()){
if($_GET['action']==='changepass')change_pass($_POST['new_pass']);
...
}
...
?>
Где функция super_mega_proverka() проверяет залогинен ли юзер или нет, а функция change_pass() меняет текущий пароль юзера на новый. Теперь мы создаем другую страницу
Код:
<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. Таким же образом можно заставить и админа выполнить какие-либо действия в админ панели.
Защита:
Использовать индетификатор юзера. Примеры
Код:
<?php
...
//Генерируем индетификатор юзера на основе его пароля
//и который мы будем вставлять во все ссылки.
$uid=md5(substr(md5(get_user_password()),0,16));
...
?>
И собственно второй скрипт где этот индетификатор проверяется:
Код:
<?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. Машину глючит, а человека тем более, поэтому буду рад услышать объективные комментарии по теме.