Operační systémy
Obsah Soubory
Soubory a zařízení

Uživatelé a práva

Aktualizováno 8 min čtení 1 511 slov

Uživatelé, práva a setuid

„Root může všechno, ostatní skoro nic." Zní to jako poctivé shrnutí unixové bezpečnosti a vyvrátíš ho za deset vteřin.

Jako obyčejný uživatel martin spustíš passwd a změníš si heslo. Povede se to. Otisk se zapsal do /etc/shadow - do souboru, který nesmíš ani přečíst, natož do něj psát. Nezadával jsi heslo roota a nic jsi neobcházel.

Ten mechanismus se jmenuje setuid, je starý přes padesát let a stojí za velkou částí lokálních eskalací práv, jaké kdy v unixových systémech byly.

Stránka vysvětluje, podle čeho se přístup kontroluje a kde se to láme. Neřeší uložení práv v i-uzlu ani izolaci jmenných prostorů (Virtualizace-a-kontejnery). Předpokládá, že víš, co proces dědí po rodiči (Procesy) a že kontrolu dělá jádro, ne program (Jadro-a-uzivatelsky-rezim).

Jádro nezná uživatele. Zná čísla.

Tohle je nejdůležitější věta na celé stránce: práva se nekontrolují podle toho, kdo jsi, ale podle toho, jaké UID a GID má proces v okamžiku, kdy sáhne na soubor - a to číslo se dá za běhu změnit.

Jméno „martin" existuje kvůli lidem. Jádro pracuje s číslem 1000 a tabulku jmen uvnitř nemá - překlad dělá až knihovna v uživatelském prostoru, aby ls -l vypsal něco čitelného.

id                    # uid=1000(martin) gid=1000(martin) groups=1000(martin),27(sudo)
ls -ln /home/martin   # -n vypíše čísla místo jmen, tedy to, co vidí jádro

Root není účet se seznamem výsad. Root je UID 0. Jádro na řadě míst testuje doslova „je efektivní UID nula", takže roota nejde omezit změnou hesla ani skupiny.

Odtud plyne to, co překvapuje u kontejnerů. Proces v kontejneru pod UID 1000 je pro jádro hostitele tentýž uživatel jako martin a na připojeném svazku dosáhne na jeho soubory. Pomůže až mapování jmenných prostorů.

Proč jsou hesla a účty ve dvou souborech

/etc/passwd musí být čitelný pro všechny, protože každý ls -l potřebuje přeložit 1000 na „martin". Dřív v něm byl i otisk hesla, takže si ho kdokoliv zkopíroval a lámal offline.

Otisky se proto přestěhovaly do /etc/shadow s právy 640 a vlastníkem root:shadow. Tím vznikl problém, kterým stránka začíná.

Doplňkové skupiny se načtou při přihlášení

sudo usermod -aG docker martin   # -a je povinné, bez něj přepíšeš celý seznam skupin

Zápis do /etc/group proběhne, ale běžící shell se o něm nedozví. Seznam skupin je součástí přihlašovacích údajů procesu a dědí se od rodiče, takže platí až od dalšího přihlášení.

Devět bitů

Práva jsou tři trojice bitů: pro vlastníka, pro skupinu a pro ostatní. Osmičkově má r hodnotu 4, w hodnotu 2 a x hodnotu 1, takže se každá trojice sečte na jednu číslici. rw-r--r-- je 644, rwxr-xr-x je 755.

Co znamená každý bit u souboru a co u adresáře

U adresáře znamenají stejná písmena něco jiného a tady se to plete nejčastěji.

Bit U souboru U adresáře
r (4) číst obsah vypsat jména záznamů
w (2) měnit obsah vytvářet a mazat záznamy
x (1) spustit vstoupit a projít skrz

Odtud plyne první nepříjemnost: právo w k adresáři je právo mazat cizí soubory. Maže se adresářový záznam, tedy jméno, ne data - jak rozebírá souborový systém. Práva mazaného souboru se nekontrolují vůbec.

Druhá je x. Bez něj se do adresáře nedostaneš, i kdyby soubor v něm měl práva 777, protože jádro musí projít každou složkou cesty.

Rozhoduje první shoda, ne nejlepší

Jádro se ptá v pevném pořadí: jsem vlastník, jsem ve skupině souboru, jsem ostatní. Rozhodne první shoda a další trojice se už nečtou.

Není to hledání nejvýhodnějšího práva, a proto se dá vyrobit tohle:

chmod 077 tajne.txt   # vlastník nic, skupina rwx, ostatní rwx
cat tajne.txt         # Permission denied - a to čte vlastník

Vlastník na svůj soubor nedosáhne, zatímco kdokoliv jiný ano. Zbývá mu jediná výhoda: chmod smí volat bez ohledu na bity.

umask: proč má nový soubor 644 a ne 666

Program při vytváření souboru žádá o práva 666, u adresáře o 777. Bit x u nového souboru nedostaneš nikdy a je to záměr.

Z toho jádro odečte masku procesu. Umask je seznam bitů, které se odebírají, a při výchozí hodnotě 022 vznikne ze 666 soubor 644 a ze 777 adresář 755.

umask         # aktuální maska, typicky 0022
umask 077     # nové soubory budou 600 a adresáře 700

Nastavuje se v /etc/login.defs, přes pam_umask, v ~/.profile a u služeb volbou UMask= v jednotce systemd.

Na sdíleném stroji si dej umask 077 do ~/.profile. Výchozí 022 znamená, že každý tvůj nový soubor přečte kdokoliv na systému.

setuid, setgid a sticky bit

Nad devíti bity je ještě čtvrtá osmičková číslice: 4000 je setuid, 2000 setgid a 1000 sticky.

Setuid znamená, že proces poběží s UID vlastníka souboru, ne toho, kdo ho spustil. Proto passwd vlastněný rootem běží jako root a dostane se do /etc/shadow. Poznáš ho podle s na místě x.

ls -l /usr/bin/passwd                     # -rwsr-xr-x 1 root root
find / -perm -4000 -type f 2>/dev/null    # všechny setuid programy v systému

Ten výpis si projdi. Každý řádek je program, jehož chyba znamená roota pro kohokoliv s přístupem k příkazové řádce. Velké S je setuid bez x, tedy překlep.

setgid na adresáři

Na adresáři má setgid jediný rozumný význam: nové soubory a podadresáře dědí skupinu adresáře místo primární skupiny svého autora. Podadresář zdědí i samotný setgid bit.

sudo chgrp projekt /srv/projekt
sudo chmod 2775 /srv/projekt   # dvojka na začátku je setgid

Tohle je správné řešení sdíleného adresáře pro tým. Bez něj skončí každý soubor ve skupině svého autora.

Sticky bit a proč ho má /tmp

/tmp musí mít w pro všechny, jinak by si tam nikdo nic nezaložil. Jenže to je zároveň právo mazat cizí záznamy, takže by ti kdokoliv smazal rozdělaný soubor.

Sticky bit to omezuje: záznam smí smazat nebo přejmenovat jen vlastník souboru, vlastník adresáře a root.

ls -ld /tmp   # drwxrwxrwt - to t na konci je sticky bit

Setuid skript nefunguje a je to schválně

Nastav setuid na shellový skript a nestane se nic. Linux ho u souborů se #! cíleně ignoruje.

Důvod je souběh: mezi ověřením práv a okamžikem, kdy interpret otevře cestu znovu podle jména, může útočník podstrčit jiný soubor. Privilegovanou akci ze skriptu řeš pravidlem v sudo.

Pwnkit: dvanáct let v jedné setuid binárce

V lednu 2022 zveřejnila firma Qualys zranitelnost CVE-2021-4034, přezdívanou pwnkit. Šlo o pkexec z balíku polkit - setuid binárku vlastněnou rootem, kterou měla ve výchozí instalaci prakticky každá distribuce.

Chyba byla ve zpracování argumentů. Při spuštění s prázdným seznamem argumentů četl pkexec mimo pole a přepsal si vlastní prostředí hodnotou, kterou řídil útočník. Odtud se dala vnutit vlastní knihovna a jakýkoliv místní uživatel se stal rootem. Ten kód tam byl od roku 2009, tedy dvanáct let.

Poučení: setuid program musí být napsaný tak, že nevěří ničemu, co dostane od uživatele - včetně proměnných prostředí. Útočník neřídí jen vstup, ale celý stav, ve kterém proces startuje.

Že to není unixová specialita, ukázal macOS High Sierra v listopadu 2017: chyba v ověřování umožnila přihlásit se jako root s prázdným heslem.

Co setuid nahradilo: capabilities

Setuid je „všechno, nebo nic". Program, který potřebuje jedinou drobnost, dostane veškerou moc roota a doufá, že ji sám neztratí.

Linux proto rozdělil práva roota na kousky - capabilities. CAP_NET_BIND_SERVICE dovolí naslouchat na portech pod 1024, CAP_NET_RAW otevřít surový soket, CAP_DAC_OVERRIDE obejít kontrolu devíti bitů.

getcap -r /usr/bin 2>/dev/null                    # co má na systému capability
sudo setcap 'cap_net_bind_service=+ep' ./program  # smí na port 80, jinak nic

ping je učebnicový případ: roky to byla setuid binárka, dnes má na Debianu jen cap_net_raw.

Nové programy nedělej setuid, dej jim capability. A ještě lépe: nech je běžet pod vlastním uživatelem tak, aby nepotřebovaly nic - port pod 1024 umí systemd předat jako hotový soket.

ACL, když devět bitů nestačí

Devět bitů popíše jednoho vlastníka, jednu skupinu a zbytek světa. Jakmile potřebuješ různá práva pro víc konkrétních uživatelů, nemáš je kam napsat.

setfacl -m u:martin:rw soubor   # martin smí číst a psát, ostatní beze změny
getfacl soubor                  # vypíše i to, co ls -l neukáže

ls -l u takového souboru přidá za práva + a to je jediná stopa.

Poctivě: ACL se používají zřídka a většinou je lepší přeskupit skupiny. Zálohovací nástroje je občas zahodí a za rok nikdo neví, proč tam ten přístup je.

Nad tím vším: sudo, SELinux a AppArmor

Sudo je lepší než přihlašování jako root ze dvou důvodů: nechává v logu, kdo co spustil, a umí povolit jen konkrétní příkazy místo celého shellu.

Nad systémem UID a bitů leží ještě povinné řízení přístupu - SELinux a AppArmor. Ta pravidla platí i pro procesy s UID 0, takže zkompromitovaná setuid binárka se nedostane dál, než jí profil dovolí.

Diagnostika: proč tam nemůžu

Nejlepší nástroj na „mám práva a stejně tam nesmím" je namei, protože ukáže práva na celé cestě, ne jen na cílovém souboru.

namei -l /home/martin/data/soubor.txt   # práva a vlastník každé složky cesty
id martin                               # skupiny, které uživatel skutečně má
sudo -l                                 # co smí spustit přes sudo
ls -ln soubor                           # UID a GID vlastníka číselně
getfacl soubor                          # ACL, které ls -l neukáže

Odmítnutí na systému se SELinuxem najdeš v auditu příkazem ausearch -m AVC -ts recent. Zbytek výbavy je na stránce Nastroje-a-diagnostika.

Příznak Kde je problém
„Permission denied" u souboru s právy 777 chybí x na adresáři cesty, ukáže namei -l
Přidal jsem uživatele do skupiny a nic se nezměnilo skupiny se načítají při přihlášení
Program nemůže otevřít port 80 port pod 1024 chce CAP_NET_BIND_SERVICE
Někdo mi smazal soubor ve sdíleném adresáři adresář má w pro všechny a chybí sticky bit
Skript se s bitem se chová jako obyčejný jádro setuid u skriptů ignoruje
ls -l ukazuje S místo s setuid bez x
Vlastník soubor nepřečte, ostatní ano rozhodla první shoda, trojice vlastníka

Co si odnést

Jádro nezná jména, jen čísla. Rozhoduje UID a GID procesu v okamžiku přístupu.

Root je UID 0. Není to seznam výsad, je to jedna hodnota, kterou jádro testuje.

U adresáře znamená x vstup, r výpis a w právo mazat cizí soubory. Přepiš si to na papír.

Rozhoduje první shoda. Vlastník s právy 077 na vlastní soubor nedosáhne.

Setuid dává programu veškerou moc vlastníka souboru. Pwnkit to dokazoval dvanáct let.

Capability místo setuid, vlastní uživatel místo capability. V tomhle pořadí.

Než začneš měnit práva, pusť namei -l. Většina odmítnutí je jinde, než hledáš.

Kam dál