Текуща ситуация: PHP2Shell. Две критични уязвимости в PHP (CVSS 9,2) позволяват злонамерен код без вход. Корекции от 21.10.2026 г.Какво да направите сега →

Предотвратяване на Local File Inclusion във PHP: LFI и RFI

Ако include или require приемат пътища от заявката, атакуващите могат да зареждат чужди файлове. Как да изградите сигурно избора на страници и шаблоните и да защитите допълнително сървъра.

Актуалност 5 мин. четенеНаръчници

Накратко: Local File Inclusion във PHP възниква, когато include, require или файлови функции използват път, на който посетителят може да влияе. Сигурното решение е фиксиран списък с разрешени: параметърът избира само от известни стойности, самият път никога не идва от заявката. От страна на сървъра защитата допълват allow_url_include=Off, open_basedir и ограничителни права за файлове.

Какво е Local File Inclusion във PHP?

Много по-стари PHP приложения зареждат страници по прост модел: параметър в адреса определя кой файл да се включи. Ако този параметър се предаде непроверен на include, атакуващ може да поиска други файлове на сървъра. В по-безобидния случай той чете конфигурационни файлове с данни за достъп. В по-лошия случай накарва PHP да изпълни съдържание, което преди това е поставил другаде, например в качен файл или в лог файл.

За ръководството е важно: тази уязвимост рядко е в ядрото на CMS, а най-често в собствени разработки, стари разширения или набързо построени допълнителни страници. В ежедневието тя не се забелязва, защото уебсайтът работи нормално.

При Remote File Inclusion PHP зарежда код от чужд адрес. Това предполага allow_url_include да е активирана. В актуалните версии на PHP тази настройка е изключена по подразбиране, но на стари или неправилно конфигурирани сървъри все още се среща.

Типични уязвими модели в кода

Застрашени са места, където стойност от заявката, от бисквитки или от базата данни става част от път до файл. Чести примери са превключватели на езика, които зареждат езиков файл по параметър, избор на страница чрез параметър в адреса, избор на шаблон или тема и скриптове за изтегляне на документи. Добавено .php накрая на пътя не защитава надеждно, защото атакуващите могат да се насочат към налични PHP файлове.

Първи преглед получавате с търсене на включвания, съдържащи променливи. То дава и безобидни съвпадения, но показва къде трябва да се погледне по-внимателно:

grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
  | grep -v '/vendor/'

Проверете всяко съвпадение откъде идва променливата и може ли посетителят да влияе на нея.

Сигурният модел: списък с разрешени вместо път от заявката

Най-надеждната защита е посетителят да избира само ключ, а приложението само да решава кой файл му съответства. Пътят никога не се съставя от въведени данни.

<?php
declare(strict_types=1);

$seiten = [
    'start'      => __DIR__ . '/../vorlagen/start.php',
    'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
    'kontakt'    => __DIR__ . '/../vorlagen/kontakt.php',
];

$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';

if (!array_key_exists($wahl, $seiten)) {
    http_response_code(404);
    $wahl = 'start';
}

require $seiten[$wahl];

Този модел е кратък, лесен за проверка и не оставя пространство за действие. В съвременните фреймуърци тази задача поема рутерът; там контролерите и шаблоните също никога не бива да се определят от свободни параметри.

Когато имената на файловете трябва да са динамични: realpath и базова директория

Понякога фиксиран списък не е практичен, например при изтегляния от папка с документи. Тогава проверявате разрешения път спрямо фиксирана базова директория и допускате само прости имена на файлове.

<?php
declare(strict_types=1);

const BASIS = '/var/www/daten/dokumente';

function sichererPfad(string $name): string
{
    if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
        throw new InvalidArgumentException('Ungültiger Dateiname.');
    }

    $basis = realpath(BASIS);
    $pfad  = realpath($basis . DIRECTORY_SEPARATOR . $name);

    if ($basis === false || $pfad === false
        || !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
        throw new RuntimeException('Datei nicht gefunden.');
    }

    return $pfad;
}

// Доставяне вместо включване: readfile() не изпълнява код.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);

Две точки са решаващи. Регулярният израз не допуска разделители на директории и специални знаци. А файловете, които само трябва да се доставят, се четат с readfile(), не с include, за да не се изпълнява никога съдържащият се код. str_starts_with() е налична от PHP 8.0.

Защита от страна на сървъра: php.ini, open_basedir и права

Сигурният код е първата линия на защита. Конфигурацията на сървъра гарантира, че пропусната грешка ще причини по-малко щети.

; php.ini или FPM пул (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen   = Off   ; само ако приложението не зарежда външни URL
open_basedir      = /var/www/kunde-a/:/tmp/kunde-a/
display_errors    = Off
log_errors        = On

allow_url_fopen е нужна на някои приложения за актуализации или интерфейси. Тествайте изключването първо в тестова среда. open_basedir ограничава PHP до собствените директории на уебсайта, което е особено важно на сървъри с няколко клиента. Подробности има в наръчниците open_basedir и изолация за всеки уебсайт и Сигурно конфигуриране на php.ini.

Допълнително директориите за качвания и кеш не бива да са изпълними, а лог файловете да не са в webroot. Така на атакуващия липсват местата, където би могъл да постави собствено съдържание за включване.

По какво разпознавате опити за експлоатация в логовете

Опитите за експлоатация на File Inclusion обикновено оставят следи в access лога. Подозрителни са параметри, съдържащи смяна на директории, абсолютни системни пътища, stream wrapper-и или пълни чужди адреси, особено при параметри като page, file, template или lang. Едно отделно съвпадение обикновено е автоматизирано сканиране. Важно е дали такава заявка е получила отговор със статус код 200 и необичаен размер на отговора.

Как да действате систематично при това, описва Анализ на логовете на уеб сървъра след атака. Ако намерите указания за успешен достъп, приемете, че конфигурационни файлове са прочетени, и подновете съдържащите се в тях данни за достъп.

Към тях спадат преди всичко паролите за базата данни, ключовете за услуги за плащане и доставка, SMTP достъпите и тайните ключове на CMS. Проверете освен това дали след първия подозрителен достъп са възникнали нови файлове. Който е могъл да чете файлове, вероятно е опитал и да постави собствени. В този случай вече не става дума само за уязвимост в кода, а за инцидент със сигурността, който трябва да се обработи структурирано.

Трайно обезопасяване на корекциите

След корекция си струва защитната функция да се обезопаси с автоматизирани тестове. Прост тест предава на избора на страница непознати стойности, смяна на директории и празно въвеждане и във всички случаи очаква стандартната страница или грешка 404. Такива тестове пречат по-късна промяна да отвори незабелязано уязвимостта отново.

За ръководството и отговорниците за проекта е полезен един въпрос към агенцията: има ли в приложението места, където файлове се зареждат по параметри, и как са защитени? Конкретен отговор с места и тестове е добър знак.

Как конкретно помага PHP SOS?

  • Търсим целенасочено във Вашия код include, require и файлови функции с данни от заявката и оценяваме всяка находка.
  • Задаваме allow_url_include, open_basedir и подходящи права за всеки уебсайт и проверяваме, че приложението продължава да работи след това.
  • Анализираме логовете за опити за експлоатация и изясняваме дали данните за достъп трябва да се подновят.
  • За независима проверка предлагаме одит на сигурността с тест за проникване за PHP приложения.

Често задавани въпроси

Каква е разликата между LFI и RFI?

При Local File Inclusion приложението зарежда файл, който вече е на сървъра, но не трябва да се зарежда. При Remote File Inclusion код се зарежда от чужд адрес. RFI предполага несигурна настройка на PHP, LFI не.

Достатъчно ли е да премахна ../ от параметъра?

Не. Такива филтри често могат да се заобиколят чрез кодиране или варианти и пропускат абсолютни пътища. Сигурно е само фиксирано съпоставяне на разрешени стойности към файлове или проверка на разрешения път спрямо базова директория.

Защитава ли open_basedir напълно от File Inclusion?

Не, тя ограничава само директориите, до които PHP има достъп. В тези граници уязвимостта остава експлоатируема. open_basedir е допълнителен защитен слой и не замества сигурния код.

Засегнати ли са от LFI CMS като WordPress?

Ядрото на масовите CMS се смята по този въпрос за добре проверено. Уязвимости се появяват по-скоро в разширения, теми и собствени разработки. Затова са важни актуализациите на разширенията и поглед към техните съобщения за сигурност.

Източници

Незабавен контакт

Всеки час има значение. Пишете ни сега.

Опишете накратко системата, версията на PHP и какво сте забелязали. Ние се свързваме с Вас, уточняваме ситуацията и работим отдалечено, докато инцидентът бъде приключен и документиран.

Незабавна помощ