Накратко: Потребителят на уеб сървъра, под който работи PHP, трябва по принципа на минималните права (Least Privilege) да чете само онова, което е нужно на уебсайта, и да записва само там, където сайтът съхранява данни. Той няма shell за вход, няма sudo права, няма собствени cron задачи и няма SSH ключове, а акаунтът му в базата данни може само да чете и променя данни. Опции на systemd като ProtectSystem и PrivateTmp ограничават допълнително PHP-FPM.
Въпросът след пробива: какво може този процес?
При PHP2Shell става дума за отдалечено изпълнение на злонамерен код без вход в системата. Ако успее, кодът не работи във вакуум, а като съвсем определен потребител: потребителят на PHP процеса. Всичко, което този потребител може, от този момент може и атакуващият.
На много сървъри това е учудващо много. Потребителят притежава целия програмен код, има shell, може да създава собствени cron задачи, а акаунтът на приложението в базата данни има всички права върху всички бази. За атакуващия това е идеалната отправна точка, за да се закрепи за постоянно.
Как да зададете правилно правата на файлове и папки, описва наръчникът Права на файловете на уеб сървъра. Тази статия разглежда самия акаунт: вход, специални права, планирани задачи, база данни и границите, които налага systemd.
Опис: кой е потребителят на уеб сървъра?
Първо изяснете кои потребители изобщо участват. При PHP-FPM всеки пул определя с user и group под кого работят неговите работни процеси. При mod_php PHP работи като потребителя на Apache. Как главният процес и работните процеси взаимодействат при PHP-FPM, обяснява PHP-FPM обяснено: мениджър на процеси, пулове и работни процеси. Следващите команди само четат:
# Под кои потребители работят уеб сървърът и PHP?
ps -eo user,group,cmd | grep -E "[p]hp-fpm|[n]ginx|[a]pache2|[h]ttpd" | sort | uniq -c
# Данни за акаунта: домашна директория и shell (съобразете потребителя)
getent passwd www-data
# Има ли потребителят sudo права?
sudo -l -U www-data
# Собствени cron задачи?
sudo crontab -l -u www-data
# SSH ключове в домашната директория (при Debian/Ubuntu това е /var/www)
sudo ls -la /var/www/.ssh 2>/dev/null
Запишете резултатите за всеки потребител на пул. Ако всички уебсайтове работят под един и същ потребител, това е първата точка в списъка; как да ги разделите, описва Изолирайте PHP-FPM пуловете за всеки уебсайт.
Права на потребителя на уеб сървъра: от какво акаунтът няма нужда
Следващият преглед показва от какво един потребител на уеб сървъра обикновено няма нужда и как да му го отнемете.
| Право | Защо е опасно | Препоръка |
|---|---|---|
| Shell за вход | Възможни са вход и интерактивна работа | Shell /usr/sbin/nologin |
| Записи за sudo | Път към права на root | Без записи, дори за отделни команди |
| Собствена crontab | Постоянна задна врата, която оцелява при всеки рестарт | Без такава, ограничете с /etc/cron.allow |
| SSH ключове | Вход отвън | Без authorized_keys |
| Собственост върху програмния код | Кодът може да бъде променян | Собственик е потребител за внедряване (Deploy) |
| Права за запис извън папките с данни | Поставяне на злонамерен код | Само качени файлове, кеш, Temp |
| Пълни права в базата данни | Бази на други приложения, достъп до файлове през базата | Само собствената база, само права върху данните |
Shell се задава с usermod -s /usr/sbin/nologin www-data. За cron важи: ако съществува /etc/cron.allow, само изброените там потребители могат да създават собствени cron задачи. Новосъздадени cron задачи или непознати SSH ключове са типичен признак, че някой се е закрепил. Особено ясен предупредителен сигнал описва статията Предупредителен сигнал: уеб сървърът стартира shell.
Базата данни: ограничете правата до необходимото
Акаунтът на един уебсайт в базата данни е фактически втори потребител на уеб сървъра. Данните му за достъп се намират в конфигурационния файл на приложението и са четими за внедрен код. Затова той трябва да може само онова, което приложението изисква в нормална работа.
-- Пример MySQL/MariaDB: отделен акаунт за всеки уебсайт, само права върху данните
CREATE USER 'seite1'@'localhost' IDENTIFIED BY 'hier-ein-langes-Zufallspasswort';
GRANT SELECT, INSERT, UPDATE, DELETE ON seite1.* TO 'seite1'@'localhost';
-- Проверка на правата
SHOW GRANTS FOR 'seite1'@'localhost';
Права като FILE, SUPER или GRANT OPTION и достъп до *.* не са нужни на акаунта на приложението. Много CMS за актуализации изискват допълнително CREATE, ALTER, INDEX и DROP. По-чисто решение е отделен акаунт за актуализации и миграции, който се използва само по време на поддръжка. Проверете изискванията в документацията на производителя.
Допълнително ограничаване на PHP-FPM със systemd
systemd може да ограничи изгледа на една услуга към системата. За PHP-FPM особено подходящи са няколко опции от документацията на systemd. Те действат и върху главния процес, който стартира като root.
# sudo systemctl edit php8.3-fpm
[Service]
# /usr, /boot и /etc само за четене
ProtectSystem=full
# /home, /root и /run/user невидими
ProtectHome=true
# собствени /tmp и /var/tmp само за тази услуга
PrivateTmp=true
# без разширяване на правата чрез setuid или capabilities на файлове
NoNewPrivileges=true
Тествайте тези опции, преди да ги въведете в продукция. Ако уебсайтовете се намират в /home, ProtectHome=true пречи на достъпа. NoNewPrivileges=true може да попречи на програми, които PHP извиква и които при стартиране сменят груповите си права, например някои локални пощенски програми за функцията mail() на PHP. Затова действайте на стъпки:
- Добавете една опция, рестартирайте услугата, проверете
systemctl status php8.3-fpm. - Тествайте основните функции на уебсайта: вход, формуляри, изпращане на поща, качване на файлове, cron задачите на приложението.
- Прегледайте журнала с грешки на PHP-FPM и на приложението.
- Едва тогава добавете следващата опция.
Накрая сокетът: според наръчника на PHP по подразбиране той получава права 0660 за потребителя и групата на пула. Задайте listen.owner и listen.group така, че да може да се свързва само уеб сървърът, а не всеки локален потребител.
Минималните права са основна нагласа, а не еднократен проект. Повторете описа от тази статия след всяка реорганизация и при всеки нов уебсайт, защото новите услуги често идват с щедри права по подразбиране.
Заключение: атакуващият наследява онова, което позволите
Least Privilege звучи като теория, но е съвсем практичен въпрос: какво може да направи някой, който изпълнява код като потребител на уеб сървъра? На сървър с shell, запис за sudo, собствена crontab и акаунт в базата данни с всички права отговорът е: почти всичко. На ограничен сървър нещата по-скоро остават в рамките на инцидент в един-единствен уебсайт, който може да бъде разпознат и почистен.
Преминаването не изисква нов софтуер, а само внимателен опис и постепенни тестове. Започнете с точките, които едва ли могат да счупят нещо, тоест shell, sudo и SSH ключове, и после продължете към правата в базата данни и опциите на systemd.
Как помага PHP SOS конкретно?
- Проверка на акаунтите: За всеки пул и уеб сървър отчитаме потребител, shell, записи за sudo, cron задачи, SSH ключове и права в базата данни.
- Намаляване на правата: Отнемаме онова, което не е нужно, и разделяме акаунтите за внедряване, за изпълнение и за поддръжка.
- Ограничаване на услугите: Въвеждаме защитните опции на systemd стъпка по стъпка и тестваме уебсайта след всяка стъпка.
- Поддържане във времето: В рамките на заздравяването на сървъра документираме концепцията за правата за бъдещи промени.
Често задавани въпроси
Под кой потребител работи PHP на моя сървър?
Това зависи от режима на работа. При mod_php PHP работи като потребителя на Apache, например www-data или apache. При PHP-FPM всеки пул определя своя потребител чрез user и group; главният процес обикновено работи като root, а работните процеси (worker) като потребителя на пула. Командата ps го показва бързо.
Нужен ли е на потребителя на уеб сървъра shell за вход?
Не. Нито Nginx, нито Apache, нито PHP-FPM изискват интерактивен вход за този потребител. Shell като /usr/sbin/nologin пречи на всеки да влезе с този акаунт. Внедряванията (deployment) се извършват с отделен потребител.
Защо www-data да няма sudo права, дори за една-единствена команда?
Защото внедреният код наследява точно тези права. Дори на пръв поглед безобидна команда, ако е разрешена неправилно, може да доведе до права на root. Задачите, които изискват повишени права, трябва да се поемат от отделни услуги или процеси извън уеб сървъра.
Помага ли това срещу PHP2Shell?
То не затваря уязвимостта; за това са необходими актуализацията на PHP и рестартът на всички работни процеси. Минималните права обаче ограничават какво може да направи атакуващият след успешна експлоатация: да променя файлове, да се закрепи в системата, да достигне други уебсайтове.