Administratorski panel na serveru gotovo uvek udari u isti zid: veb procesu treba da uradi nešto za šta nema prava. Da blokira adresu, otvori port, pročita žurnal. Odgovor koji se sam nameće jeste dati jednu usku liniju u sudoers-u i ništa više:
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client set * banip *
Linija se čita kao „samo blokiranje". U stvarnosti znači „bilo šta, kao root". Ispod sledi zašto tako ispada, kako se to na sopstvenom serveru proverava za pet minuta i čime se takva pravila zamenjuju.
Šta * zaista radi
Ključni detalj: sudo ne upoređuje argumente jedan po jedan, nego celu komandnu liniju, a * u šablonu mirno prelazi preko razmaka — to jest preko granica argumenata. Literal banip koji stoji u sredini pravila zato ne ograničava ništa: dovoljno je da se reč banip negde u komandi pojavi, a pre nje i posle nje može se predati bilo šta.
Dalje se traži program kojem se može predati komanda za izvršavanje. U našem slučaju stajao je pravo u samom pravilu. U fail2ban-u se akcija blokiranja zadaje kao tekst, a taj tekst se može redefinisati u toku rada:
fail2ban-client get portscan actions
sudo fail2ban-client set portscan action nftables-multiport actionban "<komanda>" banip 192.0.2.10
sudo fail2ban-client set portscan banip 192.0.2.11
Prvi poziv podmenjuje tekst akcije kod stvarne akcije, drugi tera jail da se aktivira — i fail2ban, koji radi kao root, izvršava podmetnuto. Uradili smo to 4. jula na sopstvenom serveru: fajl u /root nastao je sa root pravima, to jest veb proces je dobio potpuna prava na mašini iz pravila koje je izgledalo usko.
Usput su izašla tri detalja koja u tuđim razradama obično nedostaju:
- opciju
-cpresetsudo odseca: u šablonu jesetprilepljen pravo uz ime izvršnog fajla i pre njega se ništa ne može ubaciti; addactioniactionbez rečibaniptakođe ne prolaze — ali sve potrebno staje u jednu komanduset … banip …, i time je šablon ispunjen;- ime akcije mora biti stvarno, inače nema šta da se podmeni: pokazuje ga
fail2ban-client get <jail> actions.
Ko još stoji na istom spisku
fail2ban tu nije kriv i nije izuzetak. Opasna je sama kombinacija NOPASSWD i zvezdice. Šta smo pored njega našli na sopstvenim serverima:
journalctl *— žurnal se otvara kroz pejdžer, a iz pejdžera se pokreće školjka. Pravilo naizgled govori o čitanju logova; u praksi je to root školjka. Lek nije uži šablon nego grupasystemd-journal: tadajournalctlradi u potpunosti bez sudo-a;grep * /var/log/fail2ban.log— prvi argumentgrep-a je šablon, ali zvezdica dozvoljava da se preda i druga putanja, a grep se izvršava kao root. Pročitati/etc/shadowtim pravilom je jedna komanda. Zamena je ista: grupaadmza čitanje logova;ufw --force *i goliufw allow/deny/delete *— veb proces može ugasiti zaštitni zid u celini. Posebno neprijatno: u oba slučaja panel te pravila uopšte nije koristio, bile su to mrtve dozvole preostale iz ranih verzija instalacionog programa;lynis-scan.sh *— omotač koji prosleđuje"$@"root procesu jednak je pravilu bez ikakvih ograničenja.
Kako videti šta je kod vas
Gleda se ne fajl, nego stvarna prava konkretnog korisnika — onog pod kojim zaista radi PHP-FPM skup procesa (ne uvek www-data; na jednoj našoj mašini panel je radio kao admin):
ps -o user= -C php-fpm | sort -u
sudo -l -U www-data
sudo -l -U admin
Dalje sam spisak fajlova, a tu su dve zamke na kojima smo gubili vreme:
sudo grep -rn 'NOPASSWD' /etc/sudoers /etc/sudoers.d/
ls -la /etc/sudoers.d/
Prva: pravila žive na dva mesta istovremeno. Kod nas je opasna linija bila i u /etc/sudoers.d/monitor i u glavnom /etc/sudoers — u glavnom u oko osam kopija, nakupljenih iz različitih verzija instalacionog programa. Očistiti jedno mesto i umiriti se uobičajen je način da rupa ostane otvorena.
Druga: sudo ignoriše fajlove čije ime sadrži tačku. Fajl www-data.bak3 u /etc/sudoers.d/ izgleda kao važeće pravilo i čita se kao važeće pravilo, ali ne deluje. To radi u oba smera: „pravilo postoji, prava ne" i lažni mir zbog toga što kopija konfiguracije „leži tu pored".
Čime zameniti
Sužavanje šablona je uzaludno — zvezdica bilo gde u liniji vraća zadatak na početak. Drži se jedan pristup: root omotač koji prima fiksne pozicione argumente, sam ih proverava i poziva program bez najmanje mogućnosti da se nešto dopiše.
#!/bin/sh
# /usr/local/sbin/arciveo-f2b — ban|unban <jail> <ip>, i ništa više
set -eu
action="$1"; jail="$2"; ip="$3"
case "$action" in ban|unban) ;; *) echo "invalid action"; exit 1 ;; esac
case "$jail" in *[!a-zA-Z0-9_-]*|'') echo "invalid jail"; exit 1 ;; esac
printf '%s' "$ip" | grep -Eq '^[0-9a-fA-F:.]+$' || { echo "invalid ip"; exit 1; }
case "$action" in
ban) exec /usr/bin/fail2ban-client set "$jail" banip "$ip" ;;
unban) exec /usr/bin/fail2ban-client set "$jail" unbanip "$ip" ;;
esac
Omotač pripada root-u, prava 755, i — to nije opciono — ne sme biti upisiv sa veba, inače cela konstrukcija gubi smisao. U sudoers-u ostaje samo on:
www-data ALL=(root) NOPASSWD: /usr/local/sbin/arciveo-f2b
Argumenti se u pravilu ne navode: proverava ih sama skripta, a pozicije u njoj su fiksne, tako da nema gde da se podvuče tekst akcije. Pravila samo za čitanje — statusi, ss, ipset list — idu u odvojene linije, takođe bez zvezdice gde je to moguće.
Posebno o zameni sudo-a grupama. Pristup je ispravan: systemd-journal za žurnal i adm za logove izlaze jeftinije i bezbednije od bilo kog sudo pravila. Ali izvlačiti veb korisnika iz grupa naslepo ne ide. Izbacili smo www-data iz jedne grupe na hostu sa panelom i odmah dobili 403 na svim sajtovima: Apache tamo radi kao www-data, fajlovi sajtova pripadaju drugom korisniku, a pristup za čitanje davalo je upravo to članstvo u grupi. Vraćanje je zahtevalo potpuni restart — ne reload — Apache-a i PHP-FPM-a, jer stari radni procesi zadržavaju prethodni skup grupa i daju stanje „radi, a onda 403".
Provera posle izmene
Pre zamene sudoers fajla treba proveriti njegovu sintaksu — inače se može ostati potpuno bez sudo-a:
sudo visudo -cf /etc/sudoers.d/monitor
Posle toga potvrditi da je stari vektor mrtav, i to u ime upravo tog korisnika:
sudo -k
sudo -u www-data sudo -n fail2ban-client set portscan banip 192.0.2.10
Odgovor treba da bude a password is required, dok poziv omotača sa smećem umesto adrese treba da da invalid ip. Tu čuči sopstvena zamka: sudo -v pamti potvrdu petnaest minuta, a posle nje svaka provera pomoću sudo -n prolazi „uspešno". Tačno tako smo jednom potvrdili pravilo koje na serveru uopšte nije postojalo. Zato je sudo -k pre provere obavezan.
Čemu služi posmatranje
Sudo pravila se menjaju retko, ali se menjaju nečujno: instalacioni program dopisuje, panel hostinga dodaje svoje, uklonjen paket ostavlja za sobom svoje linije. Jednokratna revizija zatvara ono što postoji danas i ne govori ništa o tome šta će doneti sledeće ažuriranje. Praktičan zaključak je jednostavan: spisak onih koji mogu postati root stoji bolje na vidiku, uz ostalo stanje servera, nego u sećanju posle incidenta. Kako to izgleda sastavljeno, pokazuju demo stranice ispod.