Накратко: Един .env файл защитавате, като изобщо не го поставяте в публично достъпната директория: уеб коренът сочи към подпапка като public/, а конфигурацията стои едно ниво по-горе. Освен това уеб сървърът блокира всички файлове с точка в началото, файлът е четим само за собственика и PHP процеса и никога не принадлежи в Git хранилището. След пробив всички данни за достъп в него се смятат за известни и се сменят.
Файлът с ключовете за къщата
Съвременните PHP проекти с готовност съхраняват тайните си във файл с име .env: парола за базата данни, достъп до пощенския сървър, API ключове за платежни и куриерски услуги, ключа, с който приложението подписва бисквитките. Фреймуърци като Laravel и Symfony ползват този образец, много собствени разработки са го възприели.
Идеята е добра: конфигурацията и кодът са разделени и всяка среда получава собствени стойности. Опасно става, когато файлът попадне в грешната директория. Ако стои в уеб корена и уеб сървърът не го блокира, достатъчно е едно-единствено извикване в браузъра, за да бъдат прочетени наведнъж всички достъпи.
Към това се добавят варианти с други имена: .env.local, .env.production, config.local.php или файлове с данни за достъп до услуги за съхранение. Което тук е описано за .env, важи за всички файлове, в които има тайни.
Такива файлове се търсят непрекъснато от автоматизирани скенери. Това не е специалност на отделни атакуващи, а фонов шум в интернет, подобно на масовите сканирания за непачнати версии на PHP.
Къде трябва да стои .env файлът
Най-ефективната мярка е структурна: уеб сървърът може да доставя само папката, която наистина трябва да е публична. В Laravel и Symfony това е папката public/. Всичко останало, тоест код, конфигурация, журнали и .env, стои над нея и изобщо не е адресируемо чрез HTTP.
/srv/www/shop/
├── .env ← недостъпен
├── config/
├── src/
├── vendor/
└── public/ ← единственият уеб корен
├── index.php
└── assets/
На практика уеб коренът все пак често сочи към самата директория на проекта, например защото хостинг панелът го задава така или защото при преместване един път не е бил коригиран. Проверете затова в конфигурацията на уеб сървъра или в панела накъде сочат root, съответно DocumentRoot.
Защита на .env файл: блокиране в уеб сървъра като втора линия
Ако уеб коренът не може да се промени или искате допълнителна защита, блокирайте скритите файлове в уеб сървъра. Папката .well-known трябва да остане достъпна, защото се използва, наред с другото, за издаване на сертификати.
# Блокиране на всички файлове и папки с точка в началото, освен .well-known
location ~ /\.(?!well-known/) {
deny all;
return 404;
}
# Блокиране на файлове с точка в началото
<FilesMatch "^\.">
Require all denied
</FilesMatch>
Отговор 404 вместо 403 не издава, че файлът съществува. Дали блокирането действа, проверете отвън:
# Очаквано: 403 или 404, никога 200
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.example
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.bak
Последните два реда не са случайни: копия като .env.bak или .env.old възникват при дейности по поддръжка и често биват забравяни. Повече за такива останки има в Резервни файлове в уеб корена.
Права, Git и боравене с копия
И извън уеб корена важи принципът на най-малките права. Доказано разпределение:
- Собственик е потребителят за внедряване (Deploy), а не PHP процесът.
- Група е групата на пула на PHP-FPM, която може само да чете; права
640. - Без достъп за запис за PHP процеса. Едно приложение не бива да може да променя собствените си данни за достъп.
- Без истински стойности в хранилището: вписва се само
.env.exampleсъс заместители, истинският файл е в.gitignore. - Отделни стойности за всяка среда: разработката, staging и продукцията ползват различни достъпи, за да не отвори изтичане в тестова среда продукцията.
chown deploy:shop-php /srv/www/shop/.env
chmod 640 /srv/www/shop/.env
# Проверка дали файлът някога е бил добавян в хранилището
git log --all --oneline -- .env
Обърнете внимание и на страниците с грешки. Фреймуърците в режим за отстраняване на грешки (debug) понякога показват променливи на средата и конфигурационни стойности; на продуктивна система този режим трябва да е изключен. Проверявайте това след всяко внедряване, защото погрешна стойност в самия .env може да го включи отново.
Ако последната команда даде резултати, данните за достъп стоят в историята на Git и се смятат за разкрити, дори файлът днес да липсва. Защо това е особено деликатно, когато папката .git е на сървъра, описва Публична папка .git: недооценено изтичане на данни. Основите за правата обяснява наръчникът Права на файловете на уеб сървъра.
Тестови файлове с phpinfo() показват променливите на средата. Който предава тайни като променливи на средата, ги издава чрез забравен тестов файл. Подробности има в Файлове phpinfo(): защо трябва да изчезнат.
След пробив: смяна на данните за достъп
Никое блокиране не защитава от атакуващ, който вече изпълнява код на сървъра. PHP процесът трябва да може да чете конфигурацията си, значи и внедреният код в същия процес може. След PHP2Shell затова важи: ако пробивът не може да се изключи, всички данни за достъп на сървъра трябва да се третират като известни.
Подредената смяна обхваща пароли за бази данни, достъпи до пощата и SMTP, API ключове на външни услуги, ключове за подпис на приложението и достъпи до услуги за съхранение. Последователността и моментът трябва да са планирани: който първо сменя и после почиства сървъра, може да предаде на атакуващия новите стойности веднага.
Вредата от едно изтичане ограничавате още предварително, като всяка тайна може толкова, колкото е нужно. Потребителят на базата данни на уебсайта не се нуждае от права върху други бази данни. API ключовете на външни услуги при много доставчици могат да се ограничат до отделни функции или адреси на подателя, например създаване на пратки, но без нареждане на изплащания. И всяко приложение получава собствени достъпи, вместо няколко уебсайта да споделят един и същ пощенски акаунт или ключ. Тогава изтичането засяга ясно ограничена област и смяната остава обозрима.
В контейнерни среди файлът .env не принадлежи нито в образа, нито в публично достъпен регистър. Изключете го чрез .dockerignore и предавайте тайните едва по време на изпълнение чрез механизмите на платформата.
В дългосрочен план си струва да погледнете централизирани хранилища за тайни, каквито препоръчва OWASP: тайни на едно място, с журнал на достъпа, редовна смяна и по възможност възможно най-кратка валидност.
Как помага PHP SOS конкретно?
- Проверяваме отвън дали конфигурационни файлове, копия и скрити файлове на Вашите уебсайтове са достъпни.
- Преустройваме уеб корена, блокирането в уеб сървъра и правата на файловете така, че тайните да не са достъпни чрез HTTP.
- Проверяваме хранилища и внедрявания за добавени данни за достъп.
- След инцидент планираме и съпровождаме смяната на всички засегнати достъпи в правилния ред.
Тези дейности са част от нашето заздравяване на сървъри за PHP уеб сървъри.
Често задавани въпроси
Публичен ли е автоматично един .env файл в уеб корена?
Не автоматично, но често. Дали е достъпен, зависи от конфигурацията на уеб сървъра. Много стандартни конфигурации защитават само .htaccess файловете, а не други файлове с точка в началото. Тест с curl внася яснота.
По-сигурни ли са променливите на средата от един .env файл?
Не по принцип. OWASP посочва, че променливите на средата са четими за всички процеси на потребителя и могат да се появят в журнали или дъмпове на паметта. Те обаче избягват файл в уеб корена. По-добри са централизирани хранилища за тайни, където средата ги поддържа.
Какво да направя, ако .env файлът е бил достъпен?
Третирайте всички съдържащи се в него данни за достъп като известни и ги сменете: база данни, поща, API, платежни услуги, ключове на приложението. След това проверете в журналите за достъп дали файлът е бил извикван и при лични данни включете длъжностното лице по защита на данните.
Принадлежи ли .env файлът в Git хранилището?
Не. В хранилището най-много принадлежи шаблон без истински стойности, например .env.example. Истинският файл се изключва чрез .gitignore и се предоставя отделно на сървъра.
Може ли phpinfo() да издаде данни за достъп?
Да. phpinfo() показва, наред с другото, променливите на средата и сървърните променливи. Ако там има тайни, те се извеждат заедно с тях. Затова тестови файлове с phpinfo() никога не принадлежат на достъпна система.