Накратко: При сигурността на качването във PHP решава не една отделна проверка, а комбинацията: проверка на типа файл на сървъра, преименуване на файловете, съхранение извън webroot и никакво изпълнение на скриптове в папката за качване. Непроверен формуляр за качване е един от класическите пътища, по които атакуващите внасят задна вратичка на уеб сървър.
Защо формулярите за качване са предпочитан вход
Всеки формуляр, който приема файлове, записва чужди данни на Вашия сървър. Към тях спадат формуляри за кандидатстване, клиентски портали, галерии с изображения, зони за поддръжка и много разширения на CMS и магазини. Ако атакуващ успее да постави там файл, който уеб сървърът след това изпълнява като PHP, той има трайна задна вратичка.
За ръководството това означава: привидно безобиден формуляр за качване на автобиографии може в най-лошия случай да доведе до превземане на целия уеб сървър. Освен това в папките за качване често има лични данни, които при грешна конфигурация са публично достъпни.
За администраторите и разработчиците това означава: кодът на приложението и конфигурацията на сървъра трябва да си пасват. Слабост на едно ниво често се поема от другото, ако и двете са реализирани чисто. Как да намерите вече внедрени файлове, описва наръчникът Разпознаване и премахване на уеб шелове.
Основните правила за сигурност на качването във PHP
Следните правила отговарят на препоръките на OWASP Cheat Sheets и са се доказали в експлоатация:
- Списък с разрешени вместо списък със забранени: Приемайте само изрично разрешени типове файлове, например PDF, JPG и PNG. Списъците със забранени отделни разширения могат да се заобиколят чрез варианти и двойни разширения.
- Проверявайте съдържанието, не само разширението: Установявайте действителния тип на сървъра с
finfo. MIME типът в$_FILESе данна на подателя и не е достоверен. - Ново име на файла: Никога не използвайте оригиналното име като име на файла. Генерирайте случайно име и добавяйте разширение от списъка с разрешени.
- Съхранение извън webroot: Съхранявайте файловете така, че да не са достъпни директно по URL. Доставя се чрез скрипт с проверка на правата.
- Ограничете размера: В приложението, в PHP и в уеб сървъра.
- Никакво изпълнение: Ако качените файлове все пак трябва да са в webroot, изключете изпълнението на скриптове в тази директория.
- Проверка само на сървъра: Проверките с JavaScript в браузъра подобряват удобството, но не защитават.
Сигурен пример с finfo и случайно име
Следният пример показва ядрото на сигурната обработка. Той е нарочно кратък и трябва да се вгради във Вашето приложение, т.е. с вход, проверка на правата, CSRF защита и протоколиране.
<?php
declare(strict_types=1);
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
const ERLAUBT = [
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
];
const ZIELORDNER = '/var/www/daten/uploads'; // извън webroot
$datei = $_FILES['dokument'] ?? null;
if (!is_array($datei) || $datei['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload fehlgeschlagen.');
}
if ($datei['size'] > MAX_BYTES || !is_uploaded_file($datei['tmp_name'])) {
http_response_code(400);
exit('Datei ungültig.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$typ = $finfo->file($datei['tmp_name']);
if (!isset(ERLAUBT[$typ])) {
http_response_code(415);
exit('Dateityp nicht erlaubt.');
}
$name = bin2hex(random_bytes(16)) . '.' . ERLAUBT[$typ];
$ziel = ZIELORDNER . '/' . $name;
if (!move_uploaded_file($datei['tmp_name'], $ziel)) {
http_response_code(500);
exit('Speichern fehlgeschlagen.');
}
chmod($ziel, 0640);
// Оригиналното име да се записва в базата данни само като текст
// и при всяко извеждане да се escape-ва с htmlspecialchars().
При изображенията си струва още една стъпка: да кодирате изображението наново с GD или Imagick и да отхвърлите метаданните. Това не замества другите проверки, но намалява остатъчния риск от вградени чужди данни.
За документи от неизвестни източници, например кандидатури или снимки по рекламации, допълнително е разумно антивирусно сканиране преди предаването. То защитава по-малко уеб сървъра, а повече служителите, които по-късно отварят файловете на работните си компютри. Протоколирайте освен това всяко качване с време, акаунт, размер на файла и разпознат тип, за да могат необичайни модели да се проследят по-късно.
При CMS и магазини вградените медийни библиотеки обикновено носят собствени проверки. По-рискови са разширенията за формуляри, функциите за импорт и самостоятелно програмираните допълнителни страници, които записват файлове в обход на библиотеката. Именно тези места трябва да разгледате първо при проверка.
Доставяйте файловете от защитената папка с Content-Disposition: attachment и X-Content-Type-Options: nosniff. Тогава браузърът не показва проблемно съдържание директно, а го предлага за изтегляне.
Изключване на изпълнението на PHP в папката за качване
Дори ако файл с грешно съдържание се промъкне, уеб сървърът не бива да го изпълнява като скрипт. Този втори защитен слой принадлежи на всеки сървър, който съхранява качвания в webroot.
За Nginx с PHP-FPM:
location ^~ /uploads/ {
location ~* \.(php|phtml|phar)$ {
deny all;
}
}
За Apache, най-добре директно във VirtualHost вместо в .htaccess, за да не може правилото да бъде презаписано от приложението:
<Directory "/var/www/html/uploads">
AllowOverride None
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
</Directory>
Ако PHP работи като модул на Apache (mod_php), в същия блок допълнително можете да зададете php_admin_flag engine off. След промяната проверете с безобиден тестов файл, който само извежда текст, че сървърът отговаря на заявка към него с 403.
Граници на размера и връзката с PHP2Shell
Качванията са тясно свързани с настройките на PHP post_max_size и upload_max_filesize. В рамките на PHP2Shell php.net препоръчва като преходна мярка post_max_size да се зададе на 8M, когато не са нужни качвания, или на 32M с качвания, а upload_max_filesize да се държи под нея. Това не е пълна защита и не замества актуализацията до поправена версия на PHP.
; php.ini или FPM пул (php_admin_value), след това рестартирайте всички worker процеси
file_uploads = On
post_max_size = 32M
upload_max_filesize = 20M
max_file_uploads = 5
Ако уебсайтът изобщо не се нуждае от качвания, file_uploads = Off е най-чистото решение. Кои настройки са разумни до пача, е описано на страницата Мерки при невъзможност за пачване. Подходящи права за директориите за качване описва наръчникът Права за файлове на уеб сървъра.
Типични грешки във функциите за качване
При преглед на съществуващи приложения винаги се появяват едни и същи модели. Проверете формулярите си целенасочено за тези точки:
- Разширението се проверява само частично, така че имена на файлове с няколко разширения се промъкват.
- Оригиналното име от формуляра попада в пътя за съхранение. Това отваря пътища за манипулиране на пътя и презаписване на съществуващи файлове.
- Качените файлове са публично достъпни с предвидими имена. При кандидатури или договори това е и проблем със защитата на данните.
- Папката за качване е достъпна за запис от уеб сървъра и същевременно изпълнима.
- Стари разширения за качване или добавки за формуляри не се актуализират, макар производителят да е публикувал актуализации за сигурност.
- Грешките не се протоколират. Отхвърлените качвания обаче са ранен сигнал за целенасочени опити.
Как конкретно помага PHP SOS?
- Проверяваме Вашите функции за качване в кода и в конфигурацията на сървъра и назоваваме конкретни слабости с приоритет.
- Въвеждаме забраната за изпълнение в папката за качване, подходящи права и граници на размера в рамките на заздравяването на сървъра.
- При съмнение претърсваме директориите за качване за внедрени скриптове и ги почистваме чрез нашето премахване на злонамерен софтуер.
- При желание организираме независима насрещна проверка на реализацията чрез тест за проникване.
Често задавани въпроси
Достатъчно ли е да се проверява разширението на файла при качване?
Не. Разширението и съобщеният от браузъра MIME тип идват от подателя и могат да се избират свободно. Проверявайте съдържанието на сървъра, давайте ново име на файла и заключете изпълнението на скриптове в директорията за качване.
Къде да се съхраняват качените файлове?
Най-добре извън публичната уеб директория. Доставянето тогава поема скрипт, който предварително проверява дали потребителят има право да види файла. Така файловете не могат нито да се извикват директно, нито да се изпълняват.
Безопасни ли са SVG файловете като изображения?
Не автоматично. SVG е XML и може да съдържа активно съдържание. Разрешавайте SVG само ако почиствате файловете или ги доставяте единствено като изтегляне с подходящ хедър Content-Disposition.
Има ли размерът на качването нещо общо с PHP2Shell?
Да, косвено. php.net препоръчва като преходна мярка по-ниски стойности за post_max_size и upload_max_filesize. Това намалява риска, но не замества актуализацията до поправена версия на PHP и рестарта на всички worker процеси.