Накратко: За да конфигурирате Nginx за PHP сигурно, предавайте към PHP-FPM само скриптове, които наистина съществуват, а при приложения с front controller дори само index.php. Към това се добавят блокирани скрити файлове като .git и .env, сървър по подразбиране за непознати имена на хостове, подходящи лимити за размер и security.limit_extensions = .php в пула. Това не защитава от уязвимости в самия PHP, но ограничава какво може да се изпълни след грешка.
Най-опасните редове са в PHP location
След PHP2Shell много екипи проверяват версиите и пуловете на PHP-FPM, както е описано на страницата Защита на Nginx с PHP-FPM, и след актуализацията рестартират всички worker процеси. Втори поглед заслужава самата конфигурация на Nginx. Именно там се решава кои файлове изобщо се изпълняват като PHP.
В много конфигурации има блок, който предава всяка заявка към .php на PHP-FPM, независимо къде е файлът. Ако внедрен PHP файл попадне в папка за качване, сървърът го изпълнява. Така от грешка във файловете става задна врата.
Тази статия се фокусира върху страната на Nginx: кои директиви имат значение, как действа редът на блоковете location и как да тествате резултата.
Как Nginx решава кой блок се прилага
Който иска да конфигурира Nginx сигурно за PHP, трябва да познава избора на блокове location. Според документацията на Nginx той протича така:
- Точно съвпадение с
location =прекратява търсенето веднага. - Сред префиксните location се запомня най-дългото подходящо. Ако носи
^~, регулярните изрази повече не се проверяват. - Иначе регулярните изрази (
~,~*) се проверяват в реда на конфигурационния файл; първото съвпадение печели. - Ако никой регулярен израз не съвпадне, важи запомненото префиксно location.
От това следват две практически правила. Блокиране на PHP в папки за качване трябва да се направи или като префикс ^~, или като регулярен израз пред общия PHP блок. А обикновено префиксно location като location /uploads/ не защитава, защото регулярният израз за .php го надделява.
Сигурна основна конфигурация
Следният пример показва блок server за приложение с front controller, както е обичайно при Laravel, Symfony, Shopware 6 или много собствени разработки. Пътищата, сокета и сертификатите адаптирайте.
server {
listen 443 ssl;
server_name www.example.de;
root /var/www/seite1/public;
index index.php;
ssl_certificate /etc/ssl/seite1/fullchain.pem;
ssl_certificate_key /etc/ssl/seite1/privkey.pem;
server_tokens off;
client_max_body_size 32m;
# Разрешаване на Let's Encrypt и други известни пътища
location ^~ /.well-known/ {
try_files $uri =404;
}
# Блокиране на скрити файлове и папки (.git, .env, .htaccess)
location ~ /\. {
deny all;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Само front controller се предава на PHP-FPM
location = /index.php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_hide_header X-Powered-By;
fastcgi_pass unix:/run/php/seite1.sock;
}
# Всеки друг PHP файл не се изпълнява
location ~ \.php$ {
return 404;
}
}
$realpath_root разрешава символните връзки. Това е важно при внедрявания със сменяща се символна връзка към текущия release, за да виждат PHP-FPM и OPcache правилния път. Имайте предвид освен това: според документацията на Nginx директивите fastcgi_param се наследяват от по-високи нива само ако на текущото ниво няма собствени. Затова include fastcgi_params; принадлежи в същия блок.
Класически CMS като WordPress, TYPO3 или Contao имат няколко входни точки. Там заменяте блока на front controller с общо PHP location, което предава само съществуващи файлове (try_files $uri =404;), и блокирате изпълнението във всички папки, в които може да се пише. Как да го постигнете, е описано в Забрана за изпълнение на PHP в папки за качване.
Второто осигуряване в PHP-FPM
Nginx решава какво се предава, PHP-FPM решава какво изпълнява. Според ръководството на PHP PHP-FPM по подразбиране изпълнява файлове с .php и .phar и само препоръчва това да се ограничи до .php. Така се прихващат грешки в конфигурацията на уеб сървъра.
; в пула, напр. /etc/php/8.3/fpm/pool.d/seite1.conf
security.limit_extensions = .php
listen = /run/php/seite1.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Сокетът трябва да е достъпен само за Nginx. Портовете на FPM в мрежата, например 9000, не трябва да се отварят навън. Как сървърът като цяло показва само необходимите портове, описва Защитна стена за уеб сървъри: отваряйте само необходимите портове.
Непознати имена на хостове и други регулатори
Без собствен сървър по подразбиране Nginx отговаря на заявки към непознати имена на хостове с първия подходящ блок server. Автоматизирани сканирания, които адресират само IP адреса, попадат тогава директно във Вашето приложение. Собственият сървър по подразбиране прекратява такива заявки без отговор.
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on;
return 444;
}
Други доказали се точки:
| Регулатор | Препоръка | Цел |
|---|---|---|
server_tokens |
off |
без номер на версия в заглавията и страниците с грешки |
client_max_body_size |
съобразено с post_max_size |
ранно отхвърляне на големи заявки |
autoindex |
изключено (по подразбиране) | без списъци на директории |
fastcgi_hide_header |
X-Powered-By |
без версия на PHP навън |
| Статус страници | pm.status_path само вътрешно |
без публични оперативни данни |
| Резервни файлове | блокирайте или премахнете .sql, .zip, .bak в уеб корена |
без изтегляне на бази данни |
Повече за данните за версиите има в статията expose_php и Server заглавия.
Друг регулатор засяга позволените HTTP методи. Повечето уебсайтове се нуждаят само от GET, HEAD и POST. С limit_except други методи могат да се отхвърлят в един location; според документацията на Nginx разрешаването на GET автоматично разрешава и HEAD. Проверете предварително дали интерфейсите на Вашето приложение използват например PUT или DELETE, защото REST API често го правят.
Тествайте, преди да презаредите
Всяка промяна проверявайте първо с nginx -t и презареждайте Nginx едва след това. После тествайте поведението отвън с нормални заявки към Вашия собствен домейн:
sudo nginx -t && sudo systemctl reload nginx
# Очаква се: 403 или 404
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.git/HEAD
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env
# Очаква се: 404, защото се изпълнява само front controller
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/info.php
# Кои блокове предават към PHP-FPM?
sudo nginx -T 2>/dev/null | grep -nE "location|fastcgi_pass"
Преди всяка по-голяма промяна запазвайте пълния изход на nginx -T с дата. Той съдържа цялата действаща конфигурация, включително всички включени файлове. Ако след промяна нещо се обърка, сравнението показва веднага какво се различава. При следващата критична уязвимост имате освен това надеждна основа за отчитане на наличното, без да събирате файловете.
Променяйте конфигурацията първо в тестова среда или върху копие на блока server. Забравена входна точка, например callback за плащане, иначе се забелязва едва когато поръчките спрат.
Как помага PHP SOS конкретно?
- Четене на конфигурацията: анализираме цялата конфигурация на Nginx с
nginx -Tи свързваме всяко предаване към PHP със съответния сайт и пул. - Определяне на входните точки: заедно с Вашите разработчици определяме кои скриптове трябва да се изпълняват и блокираме останалото.
- Прилагане и тестване: адаптираме блоковете server и пуловете, проверяваме с
nginx -tи тестваме формуляри, платежни пътища и качвания. - Трайно осигуряване: конфигурацията става част от нашето заздравяване на сървъра и от документацията на Вашия сървър.
Често задавани въпроси
Трябва ли Nginx да се рестартира след актуализация на PHP?
За пача на PHP не, защото Nginx не съдържа PHP. Трябва да се рестартират всички услуги PHP-FPM. Ако при това променяте и конфигурацията на Nginx, проверете я предварително с nginx -t и след това презаредете Nginx.
Защо да предавам само index.php към PHP?
Много съвременни приложения имат една-единствена входна точка, front controller. Ако към PHP-FPM се предава само този файл, внедрен PHP файл на друго място не може да се изпълни през браузъра. При системи с много входни точки този модел не е възможен.
Какво носи security.limit_extensions в PHP-FPM?
Настройката определя кои разширения на файлове PHP-FPM изпълнява като скрипт. Според ръководството на PHP по подразбиране са .php .phar; препоръчва се ограничаване до .php. Това прихваща грешки в конфигурацията на уеб сървъра, но не я замества.
Защита срещу PHP2Shell ли са правилата на Nginx?
Не. PHP2Shell е в интерпретатора на PHP и се затваря само чрез актуализация до коригирана версия и рестартиране на всички worker процеси. Сигурната конфигурация на Nginx намалява повърхността за атака и ограничава щетите след пробив.