Operační systémy
Obsah Soubory
markdown

Prava-a-uzivatele.md

12 kB 216 řádků Změněno Zobrazit na GitHubu Stáhnout
markdown
# 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](Souborovy-system) ani izolaci jmenných prostorů ([Virtualizace-a-kontejnery](Virtualizace-a-kontejnery)). Předpokládá, že víš, co proces dědí po rodiči ([Procesy](Procesy)) a že kontrolu dělá jádro, ne program ([Jadro-a-uzivatelsky-rezim](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. ```bashid                    # 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ů](Virtualizace-a-kontejnery). ### 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í ```bashsudo 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](Souborovy-system). 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: ```bashchmod 077 tajne.txt   # vlastník nic, skupina rwx, ostatní rwxcat 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`. ```bashumask         # aktuální maska, typicky 0022umask 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`. ```bashls -l /usr/bin/passwd                     # -rwsr-xr-x 1 root rootfind / -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. ```bashsudo chgrp projekt /srv/projektsudo 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. ```bashls -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ů. ```bashgetcap -r /usr/bin 2>/dev/null                    # co má na systému capabilitysudo 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. ```bashsetfacl -m u:martin:rw soubor   # martin smí číst a psát, ostatní beze změnygetfacl 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. ```bashnamei -l /home/martin/data/soubor.txt   # práva a vlastník každé složky cestyid martin                               # skupiny, které uživatel skutečně másudo -l                                 # co smí spustit přes sudols -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](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 - **[Souborovy-system](Souborovy-system)** - proč mazání odstraňuje jméno, ne data- **[Virtualizace-a-kontejnery](Virtualizace-a-kontejnery)** - jak se stejné UID chová uvnitř kontejneru- **[Procesy](Procesy)** - co proces dědí po rodiči, včetně skupin a masky- **[Systemova-volani](Systemova-volani)** - kde přesně se kontrola práv provádí- **[Nastroje-a-diagnostika](Nastroje-a-diagnostika)** - `lsof`, `strace` a zbytek výbavy