Yanlış kurulan root, .env dosyanızı internete nasıl açıyor
Web root proje dizinini gösteriyorsa ve sunucu diskte var olan her dosyayı sunuyorsa, .env ve .git tek bir düz istek uzaklıktadır.
Baktığımız bir platformda rutin bir kontrol, orada olmaması gereken bir şey döndürdü: bir environment dosyasının içeriği, tarayıcıda, kimlik doğrulama olmadan ve hiçbir hüner gerektirmeden. İkinci istek sürüm kontrolü konfigürasyonunu getirdi. Kimse bir yere girmemişti. Sunucu tam olarak kendisine söyleneni yapıyordu: gösterildiği dizinin altında var olan her dosyayı sunmak.
Gerçekte ne oluyor
İki sıradan ayar birleşip tek bir delik oluşturuyor. Birincisi, root'un içindeki public alt dizini yerine doğrudan proje dizinini göstermesi. İkincisi, isteği uygulamaya devretmeden önce istenen yolda var olan dosyayı sunan bir fallback kuralı.
server {
server_name ornek.com;
root /srv/app; # public klasor degil, projenin kendisi
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}Sunucu için .env sıradan bir dosyadır. Baştaki nokta, dosyayı varsayılan kabuk listelemesinden gizleyen bir gelenektir, fazlası değil. try_files $uri dosya sistemine /srv/app/.env var mı diye sorar, cevap evettir ve dosya sunucunun tahmin edebildiği content type ile döner. Bilinmeyen uzantıda bu tahmin genelde düz metindir, yani isteyen için olabilecek en okunaklı sonuç.
Aynısı sürüm kontrolü dizini için de geçerli. Dizin listeleme kapalıyken içini gezemezsiniz ama gezmeye gerek yok, çünkü içindeki yollar sabit ve herkesçe biliniyor. /.git/config remote adreslerini ve çoğu zaman bir kullanıcı adını verir. /.git/HEAD ve index dosyası pack dosyalarına götürür, pack dosyalarından da tüm geçmiş yeniden kurulabilir: bir zamanlar commit edilip sonraki bir commit'te silinmiş her sır dahil.
Bu hedefli bir saldırı değil. /.env istekleri, bir alan adı yeni bir adrese bakmaya başladıktan saatler sonra gelmeye başlar; birkaç yüz yolluk bir listeyi deneyen tarayıcılardan. Siz hedef değil, listedeki bir satırsınız. Bu yüzden önemli olan sizin ne kadar bilindik olduğunuz değil, açığın ne kadar açık kaldığı.
Kendi sitenizi nasıl test edersiniz
Bir yol listesi ve bir döngü. Bunu ağınızın dışından, localhost'a değil herkese açık adrese karşı çalıştırın:
for p in /.env /.env.local /.env.production /.env.bak \
/.git/HEAD /.git/config /.gitignore \
/docker-compose.yml /package.json /composer.lock \
/.DS_Store /.htpasswd /backup.sql /config.php~ ; do
res=$(curl -s -o /dev/null -w '%{http_code} %{size_download}' "https://ornek.com$p")
printf '%-26s %s\n' "$p" "$res"
doneÇıktı hızlı okunur, çünkü tek bir kalıba bakıyorsunuz:
/.env 200 412
/.git/HEAD 200 23
/.git/config 200 287
/package.json 200 1184
/backup.sql 404 0404 0 olmayan her satır incelenmeyi hak eder. 403, 200'den iyidir ama yine de tarayıcıya dosyanın var olduğunu söyler; gövdesi dolu bir 200 ise dosyanın teslim edildiği anlamına gelir.
Bu döngüyü bir kereye mahsus çalıştırmayın. Her deploy ağaca yeni dosya ekler ve kimse bunu tek tek düşünmez; testi deploy sonrası kontrollerin arasına koymak, listeyi hatırlamak zorunda kalmamanın en ucuz yoludur.
Döngüyü makinenin cevap verdiği her hostname için tekrarlayın: çıplak IP adresi, staging adı ve eski adlar dahil. Kimsenin kullanmadığı vhost genelde en eski konfigürasyonu taşıyandır; bu da kontrol paneli ile nginx dosyasının çelişmesiyle aynı kayma sorunudur.
Çözüm
Dört değişiklik, gerçekten ne kadar koruduklarına göre sıralı.
- Root'u yalnızca public dizine gösterin. Üstündeki her şey, başka hangi kural olursa olsun erişilemez hale gelir. Kötü yolların listesini hatırlamaya bağlı olmayan tek çözüm budur.
root /srv/app/public;- Yine de nokta dosyalarını ve sürüm kontrolü yollarını ikinci katman olarak reddedin:
location ~ /\.(?!well-known/) {
deny all;
access_log off;
log_not_found off;
return 404;
}
location ~ /\.(git|svn|hg)(/|$) {
return 404;
}(?!well-known/) kısmı önemli. Sertifika yenileme /.well-known/acme-challenge/ yolunu kullanır; nokta ile başlayan her şeyi toptan reddetmek, insanların sertifika yenilemeyi kırıp bunu altmış gün sonra öğrenmesinin bilinen yollarından biridir.
Sırları sunulan ağaçtan tamamen çıkarın. Environment dosyasının yeri public dizinin bir üstü, ya da dosyanın diskte hiç bulunmadığı süreç ortamıdır. Veritabanı dump'ları, deploy arşivleri ve editör yedekleri o ağacın yakınında bile durmamalı; geçici olarak bile, çünkü geçici süre yeterince uzun.
Reddedilen her şey için 403 değil 404 dönün. 403 bir onaydır, tarayıcının topladığı şey de zaten onaydır. Bulunmaması gereken şey için gerçek 404 dönmek, ana sayfaya yönlendirmek yerine gerçek 404 dönmekle aynı ilkedir.
Eğer iş zaten olduysa
Sıra, akla ilk gelen sıra değil; yanlış sırayla ilerlemek de tek önemli şeye, zamana mal oluyor.
- Önce rotasyon. O dosyadaki her kimlik bilgisi, sizin fark ettiğiniz andan değil indirilebilir olduğu andan beri açıkta. Veritabanı şifreleri, API anahtarları, mail bilgileri, imza ve webhook sırları, session anahtarı. Sunucu konfigürasyonuna dokunmadan önce hepsini değiştirin, çünkü konfigürasyonu düzeltmek yayınlanmış bir şeyi geri almaz.
- Konfigürasyonu ikinci sırada düzeltin ve yukarıdaki döngüyle dışarıdan doğrulayın.
- Access log'u üçüncü sırada okuyun; rotasyona karar vermek için değil, olayın boyutunu ölçmek için:
grep -E '/\.(env|git)' /var/log/nginx/access.log* \
| awk '{print $1, $7, $9, $10}' \
| sort | uniq -c | sort -rn | head -20Durum kodunu ve gönderilen bayt sayısını birlikte okuyun. Gövdesi olan bir 200, teslim edildi demektir. Farklı kaynak adreslerini sayın ve aynı adresten gelen, ilk isteğin bulduğu şeyi kullanan ikinci bir istek arayın; tarama ile içeri girme arasındaki fark oradadır.
- Rotasyonun yan etkilerini sürpriz olarak değil plan olarak ele alın. Session anahtarını değiştirmek herkesi çıkışa atar, webhook sırrını değiştirmek de karşı tarafın aynı pencerede güncellenmesini gerektirir.
- Sürüm kontrolü dizini açıldıysa, o geçmişte bir kez görünmüş her sırrı açıkta sayın; sonraki bir commit'te silinmiş olanlar dahil. Geçmişi tutmak zaten sürüm kontrolünün amacı.
Çalıştığını nasıl doğrularsınız
Reddedilen yollar gerçek 404 ve boş gövde döner, çalışmaya devam etmesi gereken tek nokta yolu ise hâlâ çalışır:
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' https://ornek.com/.env
# 404 0
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' https://ornek.com/.git/config
# 404 0
printf 'ok\n' > /srv/app/public/.well-known/acme-challenge/probe
curl -s https://ornek.com/.well-known/acme-challenge/probe
# okSonra tüm yol döngüsünü tekrar çalıştırın. Her satır 404 0 okumalı; staging adında ve çıplak adreste de.
Nelere dikkat etmeli
- Aynı makinede hâlâ eski root'u taşıyan ikinci bir server bloğu, bir default server veya eski bir staging vhost'u. Düzeltme server bloğu başınadır ve kimsenin kullanmadığı blok, kimsenin bakmadığı bloktur.
- Web sunucusunun değil uygulamanın sunduğu dosyalar. Bir framework route'u URL'den yol alıp okuyorsa hiçbir web sunucusu kuralı sizi kurtarmaz, yukarıdaki deny kuralları hiç çalışmaz.
- Zararsız uzantılı artıklar:
.env.save,config.php~, dosya adlarını listeleyen.DS_Storeve bağımlılık sürümlerini birebir veren, dolayısıyla hangi bilinen açıklara sahip olduğunuzu da veren lock dosyaları. - Yedekleme betikleri dump'ı sık sık önce proje dizinine yazıp sonra taşır. Taşınana kadar geçen süre, tarayıcının listesindeki bir yolun cevap vermesi için fazlasıyla yeterlidir.
- Önde duran bir cache veya CDN, siz origin'i düzelttikten sonra sızan dosyayı sunmaya devam edebilir. Önce purge edin, sonra testi origin'e değil herkese açık adrese yapın.
- Bu kuralları bir location bloğunun içine eklemek, başka yerde kurduğunuz başlıkları sessizce düşürebilir; bu da güvenlik başlıklarını sessizce düşüren nginx kuralıyla aynı kalıtım tuzağıdır. Konfigürasyonu değiştirdikten sonra yalnızca durum kodlarına değil başlıklara da bakın.
Asıl hata, web root'u dosyaların durduğu bir yer sanmak; oysa orası yayınlanmış bir yüzey. Altındaki her şey yayındadır, hem şimdi hem de bir sonraki deploy kimsenin aklına gelmeyen bir dosya eklediğinde. Güvenli sürümü, yalnızca herkese açık olması gereken şeyleri içeren bir dizin ve gerisi için bir deny kuralıdır. Testi de bir yabancının yapacağı gibi yapın: bir yol listesi, bir döngü ve makinenin cevap verdiği her ad. Bir dakika sürer ve bu yazıdaki en ucuz dakikadır.
Sorular ve cevaplar
- .env dosyamı gerçekten indirebilirler mi?
- Dosya, web sunucusuna gösterdiğiniz dizinin altındaysa ve istenen yolda var olan neyse onu sunan bir kural varsa evet. Sunucunun gizli dosya diye bir kavramı yoktur. Yolun diskte var olup olmadığına bakar, bulur ve döner; uzantıyı tanımadığı için de genellikle düz metin olarak döner.
- Dizin listeleme kapalıysa açıktaki .git yine de tehlikeli mi?
- Evet. Listelemeye gerek yok, çünkü içindeki yollar sabittir ve herkesçe bilinir. .git/config remote adreslerini ve çoğu zaman bir kullanıcı adını verir; index ve pack dosyalarından ise tüm kaynak geçmişi yeniden kurulabilir, bir zamanlar commit edilip sonra silinmiş her sır dahil.
- Bu yollarda 403 mü 404 mü dönmeli?
- 404. 403, dosyanın var olduğunu onaylar ve tarama yapan biri için bu değerli bilgidir. Reddedilen yollarda 404 dönmek size hiçbir şeye mal olmaz, taramayı da sıradan bir ıskadan ayırt edilemez kılar.
- Sızıntıdan sonra doğru müdahale sırası nedir?
- Önce rotasyon, sonra düzeltme, en son inceleme. Dosyadaki her kimlik bilgisi sizin fark ettiğiniz andan değil, indirilebilir olduğu andan beri açıktadır; bu yüzden rotasyon ne konfigürasyon değişikliğini ne de log analizini bekleyebilir. Ardından konfigürasyonu düzeltip dışarıdan doğrulayın. Log'u en sona bırakın: olayın boyutunu ve bir şeyin kullanılıp kullanılmadığını orada görürsünüz.