Авторские статьи Пишем Php скрипты без багов

Discussion in 'Статьи' started by Dr.Z3r0, 16 Sep 2007.

  1. Dr.Z3r0

    Dr.Z3r0 Leaders of the World

    Joined:
    6 Jul 2007
    Messages:
    284
    Likes Received:
    595
    Reputations:
    567
    Пишем 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. Машину глючит, а человека тем более, поэтому буду рад услышать объективные комментарии по теме.
     
    #1 Dr.Z3r0, 16 Sep 2007
    Last edited: 6 May 2010
    14 people like this.
  2. guest3297

    guest3297 Banned

    Joined:
    27 Jun 2006
    Messages:
    1,246
    Likes Received:
    639
    Reputations:
    817
    Пишем PHP скрипты без багов. Оно тебе надо Что бы без багов?
     
    2 people like this.
  3. Dr.Z3r0

    Dr.Z3r0 Leaders of the World

    Joined:
    6 Jul 2007
    Messages:
    284
    Likes Received:
    595
    Reputations:
    567
    Ну так интереснее ломать будет))
     
  4. groundhog

    groundhog Elder - Старейшина

    Joined:
    12 May 2007
    Messages:
    1,159
    Likes Received:
    425
    Reputations:
    180
    Не выход. Оно не всегда спасает... Лучше если параметр запроса целое число - приводить его к целому с помощью intval, если параметр строка - регулярные выражения в руку. Т.е. задать шаблон, который определяет, что входящие символы в параметрах это цифры и латинские буквы...

    htmlspecialchars($_GET['id'], ENT_QUOTES);

    Лучше вести счётчик неудачных попыток захода, и банить по достижении какого-то количества. Твой метод с задержкой - просто несколько замедлит этот процесс. Лучше использовать хеш + хорошую соль, при достойном пароле, и отсутствии сведений о соли у злоумышленника все попытки будут безуспешными.

    empty - не проверяет на существование. Оно проверяет на пустоту значения.
     
  5. Dr.Z3r0

    Dr.Z3r0 Leaders of the World

    Joined:
    6 Jul 2007
    Messages:
    284
    Likes Received:
    595
    Reputations:
    567
    !empty()- проверяет на существование...)))

    имхо для защиты от XSS достаточно и без этого флага...
     
    #5 Dr.Z3r0, 16 Sep 2007
    Last edited: 6 May 2010
  6. Joker-jar

    Joker-jar Elder - Старейшина

    Joined:
    11 Mar 2007
    Messages:
    581
    Likes Received:
    205
    Reputations:
    37
    Перед функциями работы с MySQL ставьте '@'. Даже если где-то упустите какой-то параметр, ломать вслепую хакеру доставит мало удловольствия.
     
  7. groundhog

    groundhog Elder - Старейшина

    Joined:
    12 May 2007
    Messages:
    1,159
    Likes Received:
    425
    Reputations:
    180
    !empty($var) - проверяет на непустоту! И ненадо лохматить бабушку. Если переменной $var не существует это спровоцирует Warning. Например, empty вернёт true когда переменная существует и она пуста - скажем строка (""), или целое число, но оно 0.

    Имхо не имхо, но тот флаг, на который я тебе указал вынуждает обрабатывать и одинарные и двойные кавычки. По умолчанию обрабатываются только двойные кавычки. Теперь уверен что этого от xss достаточно? Скажем, если в коде атрибуты заключаются в одинарные кавычки?
     
  8. Dr.Z3r0

    Dr.Z3r0 Leaders of the World

    Joined:
    6 Jul 2007
    Messages:
    284
    Likes Received:
    595
    Reputations:
    567
    поспорим на сто рублей?))

    Хм... ладно переубедил....
     
    #8 Dr.Z3r0, 16 Sep 2007
    Last edited: 31 May 2011
  9. groundhog

    groundhog Elder - Старейшина

    Joined:
    12 May 2007
    Messages:
    1,159
    Likes Received:
    425
    Reputations:
    180
    Да хоть на двести... Я тысячи раз видел такую ситуацию, не зная, или не понимая сущности функции, некоторые индивидуумы её используют (как пример - empty), при этом log_error=on, а display_error=off... И в результате что? Логи на гигабайты - Warning: use of undefined variable...
     
    1 person likes this.
  10. Dr.Z3r0

    Dr.Z3r0 Leaders of the World

    Joined:
    6 Jul 2007
    Messages:
    284
    Likes Received:
    595
    Reputations:
    567
    Расскажи мне... Я везде юзаю эту функцию со включенным отображением ошибок... и че то нигде до сих пор не видил варнинга по этому поводу...
     
  11. groundhog

    groundhog Elder - Старейшина

    Joined:
    12 May 2007
    Messages:
    1,159
    Likes Received:
    425
    Reputations:
    180
    Хм... ХЗ... Вроде и правда работает как is_set... Хотя судя по http://ru.php.net/manual/ru/function.empty.php оно автоматом заменяет выражение на NULL, а уже empty от NULL работает корректно... В общем ладно, тут я тогда настаивать не буду, оформим это как замечание по технике программирования... :)
     
  12. -=lebed=-

    -=lebed=- хэшкрякер

    Joined:
    21 Jun 2006
    Messages:
    3,804
    Likes Received:
    1,960
    Reputations:
    594
    empty возвращает false, если переменная существует и имеет не пустое или не нулевое значение; true в обратном случае.

    Т.е. если переменной нет или она есть и её значение пустое или нулевое. ->true

    Отсюда вывод: однозначно существование переменной она не проверяет!

    PS groundhog прав - проверяет на НЕПУСОТУ!
    Т.е. если переменная не существует или её значение пустое или 0, то результат одинаков.
     
    #12 -=lebed=-, 16 Sep 2007
    Last edited: 16 Sep 2007
    1 person likes this.
  13. Ponchik

    Ponchik Хлебо-булочное изделие

    Joined:
    30 Aug 2005
    Messages:
    687
    Likes Received:
    807
    Reputations:
    311
    По поводу баги №6
    Code:
    Защита:  Использование слеширования опасных символов функцией addslashes().
    А если в строку прописать stripslashes($_GET
    PHP:
    )&php=(тот_что_ты_показывал)[/B]  :D
     
  14. scrat

    scrat кодер

    Joined:
    8 Apr 2007
    Messages:
    625
    Likes Received:
    541
    Reputations:
    3
    можно ещё и так:
    PHP:
    <?
     if(
    is_file("modules/".$_GET['module'])) include("modules/".$_GET['module'];
    ?>
     
  15. n1†R0x

    n1†R0x Elder - Старейшина

    Joined:
    20 Jan 2007
    Messages:
    728
    Likes Received:
    376
    Reputations:
    235
    Ага, а если я в кач-ве $module передам
    ../../../../../etc/passwd ?
    или ты хочешь сказать, что при проверке is_file файла passwd функция возвратит false?
     
  16. Ponchik

    Ponchik Хлебо-булочное изделие

    Joined:
    30 Aug 2005
    Messages:
    687
    Likes Received:
    807
    Reputations:
    311
    n1†R0x, ИМХО апач тебя пошлёт, он кажись не обрабатывает такие пути ну в смысле ты указываешь папку а потом из неё возвращаешся, нипаймёт он тебя modules/../../../../../etc/passwd
     
  17. Mobile

    Mobile Elder - Старейшина

    Joined:
    18 Feb 2006
    Messages:
    1,089
    Likes Received:
    820
    Reputations:
    324
    I-I()/Ib и groundhog
    Красавцы, оба шарите, молодцы. Как говорится в споре рождается истина... обоим по +5 поставил
     
  18. n1†R0x

    n1†R0x Elder - Старейшина

    Joined:
    20 Jan 2007
    Messages:
    728
    Likes Received:
    376
    Reputations:
    235
    Вообще, как мне кажется, данную функцию будет обрабатывать PHP, а не апач. И мне не кажется в любом случае, что меня пошлют) разве что прав не хватит, но это уже другой разговор...
    Если бы апач (точнее, пхп) посылал, не было бы php-инклудов. А относительные пути с движением вверх поддерживаются еще со времен ms-dos :)
     
  19. groundhog

    groundhog Elder - Старейшина

    Joined:
    12 May 2007
    Messages:
    1,159
    Likes Received:
    425
    Reputations:
    180
    n1†R0x, ты прав, при такой реализации:

    Можно проинклудить незапланированное файло, ну, конечно, если не накладывается никаких дополнительных ограничений.
     
    1 person likes this.
  20. Ponchik

    Ponchik Хлебо-булочное изделие

    Joined:
    30 Aug 2005
    Messages:
    687
    Likes Received:
    807
    Reputations:
    311
    Да? Х_Х Ну а как от этого защититься-то?!
    if(is_file("./modules/".$_GET['module'])) include("./modules/".$_GET['module'];
    Так чтоли?