nginx: Hardening
Ciljevi lekcije
Nakon ove lekcije trebalo bi da:
- ukloniš nepotrebne informacije koje server odaje o sebi
- postaviš pun skup sigurnosnih zaglavlja, uključujući CSP
- razumeš AppArmor profil za nginx i kako ga proveriti
- pooštriš systemd unit bez da oboriš servis
- prođeš kroz zatečeni server po listi za proveru
Ova lekcija delom povezuje stvari iz ranijih — dozvole iz lekcije 4, skrivene fajlove iz lekcije 6, TLS iz lekcije 9, ograničavanje pristupa iz lekcije 14 — i dodaje ono što nigde nije stalo.
1. Šta server odaje o sebi
Podrazumevano nginx šalje svoju verziju u svakom odgovoru i na svakoj stranici greške:
Server: nginx/1.24.0 (Ubuntu)
To je poklon svakome ko traži servere sa poznatim ranjivostima.
http {
server_tokens off;
}
Sada piše samo Server: nginx. Verzija je sakrivena, ali se i dalje zna koji server koristiš.
Za potpuno uklanjanje zaglavlja treba headers-more modul:
sudo apt install nginx-extras
http {
server_tokens off;
more_clear_headers Server;
}
Iskrena procena: ovo je security through obscurity i vredi malo. Skeniranje po otiscima ponašanja lako prepoznaje nginx bez ijednog zaglavlja. Uradi jer je jeftino, ali nemoj misliti da si time nešto ozbiljno postigao.
Isto važi i za zaglavlja aplikacije:
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;
2. Sigurnosna zaglavlja
Napravi snippet i uključuj ga svuda — podsetnik iz lekcije 3 da se add_header ne nasleđuje kada u užem bloku postoji makar jedno.
/etc/nginx/snippets/security-headers.conf:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
Šta koje radi:
X-Content-Type-Options: nosniff sprečava browser da pogađa tip sadržaja. Bez toga, otpremljena „slika" koja zapravo sadrži HTML može biti izvršena kao stranica.
X-Frame-Options: SAMEORIGIN brani od clickjacking-a — ugrađivanja tvog sajta u tuđi okvir. Moderna zamena je frame-ancestors direktiva u CSP-u, ali ovo zadržavaš zbog starijih browsera.
Referrer-Policy ograničava šta se šalje u Referer zaglavlju pri odlasku na drugi sajt. Podrazumevano se šalje pun URL, uključujući putanju koja može sadržati osetljive podatke.
Permissions-Policy isključuje pristup uređajima koje sajt ne koristi.
HSTS je obrađen u lekciji 9 — podsetnik da se uvodi postepeno i da se teško poništava.
Content Security Policy
CSP je najmoćnije od ovih zaglavlja i jedino koje traži stvaran trud. Ograničava odakle se sme učitavati sadržaj i time gasi većinu XSS napada.
Problem je što loše podešen CSP obara sajt — skripte prestaju da rade, stilovi nestaju.
Postupak koji radi:
Prvo režim izveštavanja, koji ništa ne blokira nego samo prijavljuje šta bi blokirao:
add_header Content-Security-Policy-Report-Only
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;"
always;
Otvori sajt, prođi kroz sve funkcije i gledaj konzolu browsera. Svako prijavljeno kršenje je nešto što moraš ili dozvoliti ili ispraviti u kodu.
Kada prijava više nema, prebaci na pravu direktivu:
add_header Content-Security-Policy
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';"
always;
Izbegavaj 'unsafe-inline' i 'unsafe-eval' — sa njima CSP gubi najveći deo smisla. Ako aplikacija koristi inline skripte, pravo rešenje je premeštanje u zasebne fajlove ili korišćenje nonce vrednosti.
Za sajt koji koristi spoljne servise, dodaj njihove domene izričito:
script-src 'self' https://cdn.primer.com;
Provera
curl -sI https://example.com | grep -iE "x-frame|x-content|referrer|permissions|strict-transport|content-security"
Za pregledan izveštaj postoje securityheaders.com i Mozilla Observatory. Oba traže javno dostupan sajt.
3. Ograničenja veličine i tajmauti
Iz lekcije 14, sažeto na jednom mestu:
http {
client_max_body_size 10m;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
keepalive_timeout 30s;
}
Ovo su i mere protiv preplavljivanja i zaštita od grešaka u aplikaciji. Postavi restriktivno globalno, pa podigni tamo gde konkretno treba:
location /otpremanje {
client_max_body_size 200m;
client_body_timeout 120s;
}
4. Dozvole i vlasništvo
Pravilo iz lekcije 4, u obliku liste za proveru:
# Sadržaj sajta — nginx čita, ne piše
sudo chown -R deploy:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 750 {} \;
sudo find /var/www/example.com -type f -exec chmod 640 {} \;
# Konfiguracija — nginx ne treba da je menja
sudo chown -R root:root /etc/nginx
sudo chmod 644 /etc/nginx/nginx.conf
# Privatni ključevi — samo root
sudo chmod 600 /etc/letsencrypt/live/*/privkey.pem
sudo chmod 700 /etc/letsencrypt/{live,archive}
# Fajl sa lozinkama
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
nginx ne sme imati pravo upisa u direktorijum sa sadržajem sajta. Ako aplikacija mora nešto da piše (uploadi, keš), daj joj upis samo u taj poddirektorijum, i tamo zabrani izvršavanje skripti kao u lekciji 11.
Provera da workeri ne rade kao root:
ps -eo user,cmd | grep "nginx: worker"
Ako tu piše root, user direktiva nedostaje ili je pogrešna, i to je ozbiljan problem.
5. AppArmor
Ubuntu isporučuje AppArmor profil za nginx uz distro paket. Profil ograničava kojim fajlovima proces uopšte može da pristupi, nezavisno od dozvola — dodatni sloj koji ograničava štetu ako neko probije nginx.
sudo aa-status | grep -i nginx
Izlaz ti kaže da li je profil u režimu enforce (primenjuje se) ili complain (samo beleži). Na Ubuntu-u profil često dolazi u complain režimu.
Prebacivanje u primenu:
sudo apt install apparmor-utils
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo systemctl reload nginx
Testiraj temeljno nakon ovoga. Ako serviraš fajlove sa putanja koje profil ne pokriva, dobićeš Permission denied iako su dozvole ispravne. To je i objašnjenje za onaj korak iz lekcije 6 kada sve izgleda ispravno a i dalje ne radi:
sudo dmesg | grep -i apparmor | tail -20
Dopunjavanje profila za sopstvene putanje — dodaj u /etc/apparmor.d/local/usr.sbin.nginx:
/srv/podaci/** r,
/var/cache/nginx/** rw,
Zatim:
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
Ako profil pravi previše problema, vrati ga u complain (sudo aa-complain ...) umesto da ga isključuješ potpuno — tako i dalje dobijaš zapise o tome šta bi bilo blokirano.
Napomena: nginx.org paket ne donosi AppArmor profil.
6. Pooštravanje systemd unita
Ubuntu-ov nginx.service je umeren. Možeš ga dopuniti kroz drop-in, bez menjanja originala:
sudo systemctl edit nginx
[Service]
PrivateTmp=true
ProtectHome=true
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictRealtime=true
Šta ovo daje: nginx dobija sopstveni /tmp, ne vidi korisničke direktorijume, ne može da stekne nove privilegije, ne dira kernel parametre.
Šta namerno nisam uključio: ProtectSystem=strict i PrivateDevices=true. Prvo čini ceo sistem fajlova nepisivim, pa nginx ne može da piše logove ni keš bez pažljivo nabrojanih ReadWritePaths. Drugo ume da napravi problem sa nekim modulima. Ako ih dodaješ, dodaj i:
ProtectSystem=strict
ReadWritePaths=/var/log/nginx /var/cache/nginx /run
Nakon svake izmene:
sudo systemctl daemon-reload
sudo systemctl restart nginx
sudo systemctl status nginx
curl -I http://localhost
Restart je ovde potreban, ne reload — menja se sam unit.
Proveri primenjena ograničenja:
systemd-analyze security nginx
Komanda daje ocenu i listu onoga što nije zaključano. Ne juri savršen rezultat — neka ograničenja obaraju funkcionalnost koja ti treba.
7. Održavanje ažurnosti
Najveći deo stvarnog rizika nije u konfiguraciji nego u zastarelim paketima.
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Podrazumevano se primenjuju samo sigurnosne nadogradnje, što je razuman izbor za produkciju.
Proveri da radi:
sudo unattended-upgrade --dry-run --debug 2>&1 | tail -20
cat /var/log/unattended-upgrades/unattended-upgrades.log
Ako koristiš nginx.org repozitorijum, sigurnosne objave prati sam — nema Ubuntu tima koji backportuje zakrpe umesto tebe.
8. Lista za proveru zatečenog servera
Kada preuzmeš server koji nisi ti postavio, prođi ovim redom.
Pregled konfiguracije:
nginx -v
sudo nginx -T > /tmp/pun-config.txt
grep -c "server {" /tmp/pun-config.txt
grep -E "server_name|listen|root" /tmp/pun-config.txt
Provera osnovnih stvari:
# Da li workeri rade kao neprivilegovan korisnik
ps -eo user,cmd | grep "nginx: worker"
# Da li se verzija odaje
curl -sI http://localhost | grep -i server
# Da li su zastareli TLS protokoli dozvoljeni
grep -n "ssl_protocols" /tmp/pun-config.txt
# Da li ima sigurnosnih zaglavlja
curl -sI https://localhost -k | grep -iE "x-frame|x-content|strict-transport"
# Da li je autoindex negde uključen
grep -n "autoindex on" /tmp/pun-config.txt
# Da li su skriveni fajlovi zaštićeni
curl -sI http://localhost/.git/config
curl -sI http://localhost/.env
Provera zaostavština u sites-enabled:
ls -la /etc/nginx/sites-enabled/
Traži rezervne kopije (.bak, .old, ~), koje se učitavaju jer sites-enabled/* uzima sve fajlove (lekcija 3).
Provera dozvola:
sudo find /var/www -type f -perm /o+w -ls | head # fajlovi pisivi za sve
sudo find /var/www -type d -perm /o+w -ls | head
ls -l /etc/letsencrypt/live/*/privkey.pem 2>/dev/null
Provera sertifikata:
for d in /etc/letsencrypt/live/*/; do
echo -n "$(basename $d): "
openssl x509 -in "$d/cert.pem" -noout -enddate 2>/dev/null
done
systemctl list-timers | grep certbot
Provera otvorenih portova:
sudo ss -tlnp
sudo ufw status verbose
Traži aplikacione servise koji slušaju na 0.0.0.0 umesto na 127.0.0.1 (lekcija 11).
Provera logova:
ls -lh /var/log/nginx/
sudo tail -50 /var/log/nginx/error.log
Veliki, nerotirani fajlovi su znak da rotacija ne radi (lekcija 13).
9. Šta ovo ne pokriva
Da bi slika bila poštena:
- Bezbednost aplikacije je i dalje na aplikaciji. nginx ne vidi SQL injekciju ni grešku u autorizaciji.
- Bezbednost operativnog sistema je zaseban posao — SSH ključevi umesto lozinki, firewall, ograničenje sudo pristupa, praćenje neuspelih prijava.
- WAF (ModSecurity sa OWASP CRS) je sledeći nivo ako ti treba pregled sadržaja zahteva, uz cenu u održavanju i lažnim pozitivima.
- Rezervne kopije. Server koji je savršeno podešen a nema rezervnu kopiju je jedan pogrešan
rmod katastrofe.
Praktična vežba
1. Ukloni informacije o verziji i proveri pre i posle:
curl -sI http://localhost | grep -i server
# dodaj server_tokens off; pa reload
curl -sI http://localhost | grep -i server
curl -s http://localhost/nepostoji | grep -i nginx
Druga komanda proverava i stranicu greške, gde se verzija takođe pojavljuje.
2. Napravi snippet sa sigurnosnim zaglavljima, uključi ga u server blok i proveri:
curl -sI -H "Host: test.local" http://localhost/ | grep -iE "x-frame|x-content|referrer|permissions"
3. Demonstriraj zamku sa nasleđivanjem. Dodaj add_header X-Nesto "1"; u jedan location blok bez ponovnog uključivanja snippeta, pa proveri da su sva ostala zaglavlja nestala. Popravi sa include.
4. Postavi CSP u režimu izveštavanja, otvori sajt u browseru i pogledaj konzolu. Ako imaš inline skripte, videćeš prijave.
5. Proveri stanje AppArmor profila:
sudo aa-status | grep -A3 -i nginx
Ako je u complain, pogledaj šta bi bilo blokirano:
sudo dmesg | grep -i "apparmor.*nginx" | tail
6. Pooštri systemd unit i izmeri razliku:
systemd-analyze security nginx | tail -3
sudo systemctl edit nginx # dodaj direktive iz teksta
sudo systemctl daemon-reload && sudo systemctl restart nginx
systemd-analyze security nginx | tail -3
curl -I http://localhost # mora i dalje da radi
7. Prođi celu listu za proveru na svom testnom serveru i zabeleži svaku stavku koja nije u redu. Zatim popravi svaku.
8. Namerno ostavi rezervnu kopiju u sites-enabled i vidi da je nginx učitava:
sudo cp /etc/nginx/sites-available/test /etc/nginx/sites-enabled/test.bak
sudo nginx -t
Verovatno ćeš dobiti grešku o dupliranom server_name — što je najbolji ishod, jer te upozorava. Obriši kopiju.
Rezime
server_tokens offje jeftino i vredi uraditi, ali ne gradi na tome osećaj sigurnosti.- Sigurnosna zaglavlja drži u snippetu i uključuj ga u svaki blok koji ima sopstveni
add_header. - CSP uvodi kroz
Content-Security-Policy-Report-Onlyi tek nakon što prijave prestanu prebacuj na pravu direktivu. 'unsafe-inline'u CSP-u poništava najveći deo njegove koristi.- nginx ne sme imati pravo upisa u direktorijum sa sadržajem sajta.
- Privatni ključevi:
chmod 600, direktorijum700. - AppArmor na Ubuntu-u često dolazi u
complainrežimu;aa-enforcega aktivira, ali testiraj temeljno. dmesg | grep apparmorje odgovor kada dozvole izgledaju ispravno a pristup i dalje ne prolazi.- systemd drop-in sa
PrivateTmp,ProtectHomeiNoNewPrivilegesje bezbedan dodatak;ProtectSystem=stricttražiReadWritePaths. unattended-upgradespokriva veći deo stvarnog rizika nego bilo koje podešavanje u ovoj lekciji.- Za zatečeni server prođi listu iz odeljka 8 pre nego što bilo šta menjaš.
Pitanja za proveru
- Zašto je
server_tokens offkorisno, i zašto ne treba precenjivati tu korist? - Postavio si sigurnosna zaglavlja u
serverblok, ali ih nema na/api/putanji. Zašto? - Zašto se CSP uvodi kroz
Report-Onlyrežim? - Šta gubiš dodavanjem
'unsafe-inline'uscript-src? - Dozvole nad fajlom su ispravne,
sudo -u www-data catprolazi, ali nginx i dalje vraća 403. Gde gledaš? - Zašto
ProtectSystem=strictu systemd unitu može da obori nginx? - Preuzeo si nepoznat server. Koje su prve tri komande koje pokrećeš?
- Šta od stvarnog rizika ova lekcija ne pokriva?
Kraj obrađenog dela kursa. Preostale teme iz prvobitnog plana — tuning performansi, stream modul, sistematska dijagnostika, automatizacija i završni projekat — ostaju kao mogući nastavak.
Comments
Post a Comment