Egy kiszolgálón futó adminisztrációs felület szinte mindig ugyanabba a falba fut: a webes folyamatnak olyat kell tennie, amihez nincs joga. Címet tiltani, portot nyitni, naplót olvasni. A kézenfekvő válasz egyetlen szűk sort megadni a sudoers fájlban, és semmi többet:
www-data ALL=(root) NOPASSWD: /usr/bin/fail2ban-client set * banip *
A sor úgy olvasható, hogy „csak tiltás”. Valójában azt jelenti: „bármi, rootként”. Az alábbiakban arról lesz szó, miért alakul ez így, hogyan ellenőrizheti a saját kiszolgálóján öt perc alatt, és mivel váltják ki az ilyen szabályokat.
Mit tesz valójában a *
A döntő részlet: a sudo nem argumentumonként hasonlít össze, hanem a teljes parancssort, és a mintában szereplő * gond nélkül átlép a szóközökön — vagyis az argumentumhatárokon. A szabály közepén álló szó szerinti banip ezért nem korlátoz semmit: elég, ha a banip szó a parancsban valahol előfordul, előtte és utána pedig bármi átadható.
A következő lépés olyan programot találni, amelynek végrehajtandó parancs adható át. A mi esetünkben ez ott állt magában a szabályban. A fail2banban a tiltási művelet szövegként van megadva, és ez a szöveg menet közben újradefiniálható:
fail2ban-client get portscan actions
sudo fail2ban-client set portscan action nftables-multiport actionban "<parancs>" banip 192.0.2.10
sudo fail2ban-client set portscan banip 192.0.2.11
Az első hívás egy valódi művelet szövegét cseréli le, a második pedig elsüti a jailt — és a fail2ban, amely rootként fut, végrehajtja a becsempészettet. Ezt július 4-én tettük meg a saját kiszolgálónkon: a /root könyvtárban rootként jött létre egy fájl, vagyis a webes folyamat teljes jogot kapott a gépen egy szabályból, amely szűknek látszott.
Közben három olyan részlet derült ki, amely mások leírásaiból általában kimarad:
- a
-ckapcsolót asetelőtt a sudo levágja: a mintában asetközvetlenül a programnévhez van tapasztva, és elé semmi nem szúrható be; - az
addactionés azactionabanipszó nélkül szintén nem megy át — de minden szükséges belefér egyetlenset … banip …parancsba, és ezzel a minta teljesül; - a művelet nevének valódinak kell lennie, különben nincs mit lecserélni: a
fail2ban-client get <jail> actionsmutatja meg.
Ki áll még ugyanezen a listán
A fail2ban itt nem hibás, és nem is egyedi eset. A veszélyes maga a NOPASSWD és a csillag párosítása. Amit mellette találtunk a saját kiszolgálóinkon:
journalctl *— a napló egy oldalazón keresztül nyílik meg, az oldalazóból pedig parancsértelmező indítható. A szabály látszatra a naplók olvasásáról szól; a gyakorlatban root-parancsértelmező. A megoldás nem szűkebb minta, hanem asystemd-journalcsoport: akkor ajournalctlegyáltalán sudo nélkül működik;grep * /var/log/fail2ban.log— agrepelső argumentuma a minta, de a csillag megengedi egy második útvonal megadását is, és a grep rootként fut. Az/etc/shadowelolvasása ezzel a szabállyal egyetlen parancs. A csere ugyanaz: azadmcsoport a naplók olvasásához;ufw --force *és a csupaszufw allow/deny/delete *— a webes folyamat teljesen kikapcsolhatja a tűzfalat. Külön kellemetlen, hogy a felület egyik esetben sem használta ezeket a szabályokat: halott engedélyek maradtak a telepítő korai változataiból;lynis-scan.sh *— egy burkoló, amely a"$@"tartalmát továbbadja egy root folyamatnak, felér egy mindenféle korlátozás nélküli szabállyal.
Hogyan nézhető meg, mi van önnél
Nem a fájlt kell megnézni, hanem egy konkrét felhasználó tényleges jogait — azét, amelyikkel a PHP-FPM készlet valóban fut (nem mindig www-data; az egyik gépünkön a felület admin néven futott):
ps -o user= -C php-fpm | sort -u
sudo -l -U www-data
sudo -l -U admin
Utána magát a fájllistát, és itt két csapda van, amely időnkbe került:
sudo grep -rn 'NOPASSWD' /etc/sudoers /etc/sudoers.d/
ls -la /etc/sudoers.d/
Az első: a szabályok egyszerre két helyen élnek. Nálunk a veszélyes sor a /etc/sudoers.d/monitor fájlban és a fő /etc/sudoers fájlban is ott volt — az utóbbiban körülbelül nyolc példányban, amelyeket a telepítő különböző változatai halmoztak fel. Egy helyet kitakarítani és megnyugodni a szokásos módja annak, hogy a rés nyitva maradjon.
A második: a sudo figyelmen kívül hagyja azokat a fájlokat, amelyek nevében pont van. A www-data.bak3 fájl a /etc/sudoers.d/ könyvtárban érvényes szabálynak látszik és érvényes szabályként olvasható, de nincs hatása. Ez két irányban működik: „a szabály megvan, a jog nincs”, illetve az a hamis megnyugvás, hogy a konfiguráció egy példánya „ott van mellette”.
Mivel váltható ki
A minta szűkítése hasztalan — egy csillag a sor bármely pontján visszateszi a feladatot a kezdetre. Egyetlen megközelítés állja meg a helyét: egy root burkoló, amely kötött helyzetű argumentumokat vesz át, maga ellenőrzi őket, és úgy hívja meg a programot, hogy semmit ne lehessen hozzáfűzni.
#!/bin/sh
# /usr/local/sbin/arciveo-f2b — ban|unban <jail> <ip>, és semmi más
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
A burkoló root tulajdonában van, jogai 755, és — ez nem opcionális — a webről nem lehet írható, különben az egész szerkezet értelmét veszti. A sudoers fájlban csak ez marad:
www-data ALL=(root) NOPASSWD: /usr/local/sbin/arciveo-f2b
Az argumentumokat a szabály nem sorolja fel: azokat maga a szkript ellenőrzi, és benne a helyek kötöttek, így nincs hová becsúsztatni egy műveletszöveget. A csak olvasó szabályok — állapotok, ss, ipset list — külön sorokba kerülnek, szintén csillag nélkül, ahol ez lehetséges.
Külön a sudo csoportokkal való kiváltásáról. A megközelítés helyes: a systemd-journal a naplóhoz és az adm a naplófájlokhoz kevesebbe kerül és biztonságosabb bármely sudo-szabálynál. De a webes felhasználót vaktában kivenni a csoportokból nem lehet. Egy paneles gépen kivettük a www-data felhasználót az egyik csoportból, és egyszerre 403-at kaptunk minden oldalon: az Apache ott www-data néven fut, az oldalak fájljai más felhasználóhoz tartoznak, és az olvasási hozzáférést épp az a csoporttagság adta. A visszaállítás az Apache és a PHP-FPM teljes újraindítását igényelte — nem újratöltést —, mert a régi feldolgozók megtartják a korábbi csoportkészletüket, és azt az állapotot hozzák létre, hogy „működik, aztán 403”.
Ellenőrzés a változtatás után
Egy sudoers fájl kicserélése előtt ellenőrizni kell a szintaxisát — különben sudo nélkül maradhat az ember:
sudo visudo -cf /etc/sudoers.d/monitor
Utána meg kell erősíteni, hogy a régi út halott, és éppen annak a felhasználónak a nevében:
sudo -k
sudo -u www-data sudo -n fail2ban-client set portscan banip 192.0.2.10
A válasz a password is required legyen, míg a burkoló meghívása cím helyett szeméttel invalid ip választ adjon. Itt saját csapda leselkedik: a sudo -v tizenöt percre gyorsítótárba teszi a megerősítést, és utána bármely sudo -n ellenőrzés „sikeresen” lefut. Pontosan így erősítettünk meg egyszer egy szabályt, amely a kiszolgálón egyáltalán nem volt ott. Ezért a sudo -k az ellenőrzés előtt kötelező.
Mire jó a megfigyelés
A sudo-szabályok ritkán változnak, de hangtalanul: a telepítő hozzáír, a hoszting felülete a magáét fűzi hozzá, egy eltávolított csomag ott hagyja a sorait. Az egyszeri átvizsgálás bezárja azt, ami ma van, és semmit nem mond arról, mit hoz a következő frissítés. A gyakorlati következtetés egyszerű: annak a listája, ki válhat roottá, a kiszolgáló többi állapota mellett, szem előtt a helye — nem pedig az incidens utáni emlékezetben. Hogy mindez összeállítva hogyan néz ki, azt az alábbi bemutatóoldalak mutatják meg.