S-bit -- životu nebezpečno
-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Unix is a lot more complicated (than CP/M) of course -- the typical
Unix hacker can never remember what the PRINT command is called this
week -- but when it gets right down to it, Unix is a glorified video
game. People don't do serious work on Unix systems; they send jokes
around the world on USENET or write adventure games and research
papers.
-- E. Post
"Real Programmers Don't Use Pascal", Datamation, 7/83
Pavel Kaňkovský, 3. března 1998
Mnoho systémových programů v UNIXovém operačním systému --
Linux nevyjímaje -- potřebuje pro svůj běh zvláštní privilegia.
Příkladem budiž program passwd, který musí být schopen zapisovat do
souboru /etc/passwd resp. /etc/shadow, aniž by k tomu byl oprávněn
sám uživatel (z pochopitelných důvodů). Nejčastěji používaným
mechanismem, který toto umožňuje, je tzv. propůjčení identifikace.
U programu, jehož spustitelný soubor má nastavený aspoň jeden
z s-bitů, například takto:
-r-sr-xr-x 1 root bin 15796 /usr/bin/passwd
dojde po jeho spuštění systémovým voláním execve() k tomu,
že efektivní vlastník, efektivní skupina, případně oboje -- podle
toho, zda je v přístupových právech nastaven setuid bit ( 04000),
setgid bit (02000), nebo oba dva -- jsou nastaveny na hodnoty určené
uživatelem resp. skupinou vlastnící soubor. (Množina doplňkových
skupin (supplemental groups) je vždy ponechána beze změny.) Program
pak pracuje se stejnými oprávněními, jako kdyby byl spuštěn svým
vlastníkem resp. uživatelem v dané skupině.
Proč je to nebezpečné?
Výše popsaný mechanismus není prost rizik. Musí být zajištěno,
aby zvláštní privilegia přidělená programu nemohla být zneužita.
Část odpovědnosti leží na samotném jádře systému, které musí mj.
zabránit jeho trasování nebo čtení jeho paměťového prostoru (kde
se mohou nacházet důvěrná data) ze strany skutečného vlastníka,
ovšem převážná část spočívá na bedrech samotného programu (a také
jím používaných knihovnách).
Privilegovaný proces musí pečlivě kontrolovat prostředí,
ve kterém je spuštěn, a vstupy, které jsou mu uživatelem
předkládány. Naneštěstí mnoho programů (či spíše jejich autorů) toto
podceňuje a výsledkem jsou závažné bezpečnostní problémy, které jsou
ještě umocněny tím, že velká část takových programů pracuje rovnou
s nejvyššími možnými privilegii -- s právy superuživatele.
K nejčastějším chybám, které mají za následek bezpečnostní
problémy (a které budou detailněji diskutovány níže) patří:
- přetečení mezí pole
- neopatrná manipulace se soubory
- neopatrné spouštění dalších programů
Odstrašující příklad -- Xserver
Jak si asi mnozí, kteří mají na svém Linuxu nainstalovaný balík
{\tt XFree86}, všimli, Xserver běží s právy superuživatele. Je tomu
tak, aby mu bylo dovoleno přistupovat přímo ke grafickému hardware,
k jehož ovládání neposkytuje standardní linuxové jádro prakticky
žádnou podporu. Ovšem Xserver je velmi komplikovaný program
a vyskytují se v něm (nyní bude řeč o aktuální verzi 3.3.1 založené
na X11R6.3) různé problémy, dokonce ze všech tří výše vyjmenovaných
kategorií. Jedná se o následek toho, že kód Xserveru (a do jisté
míry to platí o celém X11) byl většinou navrhován a implementován,
aniž byl kladen dostatečný důraz na jeho bezpečnost. Svou roli zde
nepochybně hraje velký rozsah a složitost kódu, stejně jako jeho
značná různorodost.
Přetečení mezí pole
Toto je snad nejfrekventovanější chyba vyskytující se
v céčkových programech vůbec. Poněkud překvapivě se však chyba
vyskytuje mnohem častěji nepřímo -- při použití některých funkcí
standardní knihovny. Zvláště se jedná o funkce sprintf(), strcpy()
a strcat(), které neumožňují explicitně stanovit velikost pole, do
kterého je ukládán výsledek, a bezstarostně zapisují za jeho konec.
(Což vede k otázce, zda je celková koncepce standardní knihovny
vhodná.) Podrobněji k této problematice v dalším článku.
Pomocí vhodně sestrojených vstupních dat pak může zlý uživatel
přepsat návratovou adresu na zásobníku nebo jiné důležité údaje
a donutit program k vykonání prakticky libovolných akcí -- např.
ke spuštění privilegovaného shellu. Detailní popis přináší například
http://www-miaif.ibp.fr/willy/security/.
Příklad (xc/programs/Xserver/os/access.c):
void
ResetHosts (display)
char *display;
{
register HOST *host;
char lhostname[120], ohostname[120];
char *hostname = ohostname;
char fname[100];
...
strcpy (fname, "/etc/X");
strcat (fname, display);
strcat (fname, ".hosts");
if (fd = fopen (fname, "r"))
...
}
Uživatel může při spuštění Xserveru specifikovat libovolný
(i nesmyslný a hlavně libovolně dlouhý) název displaye pomocí
parametru uvozeného dvojtečkou. Tato hodnota je předána do funkce
ResetHosts(), která ji používá pro konstrukci jména souboru. Avšak
příliš dlouhá hodnota způsobí přetečení mezí pole fname.
Neopatrná manipulace se soubory
Většina programů pracuje nějakým způsobem se souborovým
systémem. Pokud program neprovádí dostatečné kontroly jmen souborů
nebo jiných uživatelem poskytnutých dat, z nichž jsou jména souborů
konstruována, lze snadno získat neoprávněný přístup k souborům,
systémovým zvláště.
Příklad (xc/programs/Xserver/os/utils.c)
static void
InsertFileIntoCommandLine(resargc, resargv, prefix_argc, prefix_argv, filename
suffix_argc, suffix_argv)
int *resargc;
...
{
struct stat st;
FILE *f;
char *p;
char *q;
int insert_argc;
char *buf;
int len;
int i;
f = fopen(filename, "r");
...
} /* end InsertFileIntoCommandLine */
void
ExpandCommandLine(pargc, pargv)
int *pargc;
char ***pargv;
{
int i;
for (i = 1; i << *pargc; i++)
{
if ( (0 == strcmp((*pargv)[i], "-config")) && (i << (*pargc -- 1)) )
{
InsertFileIntoCommandLine(pargc, pargv,
i, *pargv,
(*pargv)[i+1], /* filename */
*pargc -- i - 2, *pargv + i + 2);
i--;
}
}
} /* end ExpandCommandLine */
Funkce ExpandCommandLine() interpretuje parametr -config tak,
že načte obsah specifikovaného souboru a přidá ho k parametrům
zadaným na příkazové řádce. Nicméně vůbec nekontroluje, zda je
daný soubor uživateli, který Xserver spustil, přístupný pro čtení.
Například, pokud zlý uživatel zadá soubor /etc/shadow, dostane se mu
chybového hlášení typu:
Unrecognized option: root:BFLMpsvzABCD:12345:::::
a může začít luštit superuživatelovo heslo.
Zvláštní kapitolu představují dočasné soubory vytvářené
v oblastech přístupných uživateli -- zvláště v adresáři /tmp,
ale i v domovském adresáři uživatele, v aktuálním adresáři apod.
Zlý uživatel může vytvořením (sym)linků nebo přejmenováváním souborů
lehce přesvědčit naivně implementovaný program, aby pracoval s úplně
jiným souborem, než zamýšlel. Bohužel tomu opět napomáhá standardní
knihovna -- konkrétně funkce mktemp() a tmpnam(). V případě
Xserveru lze problém tohoto typu demonstrovat na práci s adresářem
/tmp/.X11-unix, i když konkrétní povaha těchto rizik poněkud
vybočuje ze zaměření tohoto článku.
Neopatrné spouštění dalších programů
Příležitostně potřebuje jeden program využít služeb jiného
programu. Pokud první program běží se zvláštními privilegii, pak
jsou často tato privilegia přenášena i na programy z něj spuštěné.
To pak znamená, že se na jedné straně nabízí možnost využít (či
spíše zneužít) nedostatků jiného programu, na druhé straně pak
možnost přímo donutit privilegovaný proces k spuštění libovolného
programu dodaného zlým uživatelem (sem lze zařadit použití dynamicky
linkované knihovny podle přání uživatele). Xserver vykazuje vzácnou
kombinaci obou problémů, jak uvidíme v příkladu.
Příklad (xc/programs/Xserver/xkb/ddxLoad.c)
Bool
XkbDDXCompileKeymapByNames(XkbDescPtr xkb,
XkbComponentNamesPtr names,
unsigned want,
unsigned need,
char * nameRtrn,
int nameRtrnLen)
{
...
if (XkbBaseDirectory!=NULL) {
...
sprintf(buf,
"%s/xkbcomp -w %d -R%s -xkm -- -em1 %s -emp %s -eml %s \"%s%s.xkm\"",
XkbBaseDirectory,
((xkbDebugFlags<<2)?1:((xkbDebugFlags>>10)?10:xkbDebugFlags)),
XkbBaseDirectory,
PRE_ERROR_MSG,ERROR_PREFIX,POST_ERROR_MSG1,
xkm_output_dir,keymap);
...
}
else {
...
}
...
out= popen(buf,"w");
...
}
Proměnná XkbBaseDirectory obsahuje buď implicitní hodnotu
( /usr/lib/X11/xkb), nebo hodnotu specifikovanou pomocí parametru
-xkbdir. (Z tohoto důvodu mi není zcela jasný význam testu na
nulovou hodnotu, ale to ponechme stranou.) Uživatel má nyní dvě
možnosti jak tento parametr zneužít: jednak může vytvořit libovolný
program pod jménem xkbcomp a předat jméno adresáře, ve kterém se
nachází, nebo může využít faktu, že funkce popen() způsobí spuštění
shellu, a zadat hodnotu, která obsahuje znaky mající pro shell
zvláštní význam (např. mezery, obrácené apostrofy aj.).
Východiska z nouze
Rozsáhlé a těžko prověřitelné programy (jako zmíněný Xserver)
neměly být nikdy spouštěny se zvláštními privilegii. Principiálně
vzato by těžko prověřitelné programy neměly vůbec vznikat. Často
používané bezpečnostní obálky (wrappers) a podobná opatření jsou
pouze nápravou následků, nikoli příčin, které spočívají v špatné
konstrukci samotných programů, i když za určitých okolností mohou
mít své výhody. Návod ke správné konstrukci bezpečných programů lze
hledat ve dvou "základních bezpečnostních pravidlech", která zní:
- Nikdy nikomu nevěř.
- Co není dovoleno, je zakázáno.
První pravidlo říká, že v programu nesmí být chyby, ale navržen
musí být s ohledem na to, že tam chyby budou. To například znamená,
že ty části programů, které zvláštní privilegia vyžadují, je vhodné
separovat do malých samostatných programů s přesně definovaným
rozhraním, jejichž správnou funkci bude možno ověřit -- ideálně
formálním důkazem.
Druhé pravidlo pak doporučuje, aby každý program měl
přidělena pouze ta privilegia, která ke své práci potřebuje
a pokud možno žádná jiná. To bohužel často naráží na omezení
operačního systému -- zvláště v UNIXovém světě (Linux opět
nevyjímaje), kde vládne silná dichotomie: vše (superuživatel)
nebo nic (ostatní uživatelé). Není-li možno dostatečně jemně
rozlišovat privilegia, dochází k tomu, že jsou přidělována
širší oprávnění, než je zapotřebí, a otevírá se prostor pro
jejich zneužití. Jediným skutečným řešením tohoto problému je
upravit operační systém tak, aby programy nemusely mít větší
oprávnění než je nutno. Komplexnější projekty jako LinuxPrivs
http://www.kernel.org/pub/linux/libs/security/linux-privs/
(implementace privilegií podle POSIX.1e) jsou vykročení
správným směrem, ale existují i částečná řešení, jako například
patch http://www.phrack.com/52/P52-06, který mj. eliminuje dosti
nesmyslnou (v dnešní době) potřebu superuživatelských práv pro
některé síťové služby.
Poněkud paradoxní je, že popisovaná opatření (pomineme-li obecné
doporučení, že v programech nemají být chyby) nejsou příliš vhodná
pro náš Xserver. Ten totiž ve skutečnosti žádná speciální privilegia
nepotřebuje, pouze musí být schopen ovládat grafický hardware.
Stačí, aby pro to byla v jádře dostatečná podpora -- jako jí
poskytuje například GGI http://www.ggi-project.org/.
Závěr
Tento článek popisuje pouze malou část z rozsáhlého repertoáru
možných rizik: zmiňované problémy (nebo problémy jim podobné)
mohou vznikat (a bohužel také vznikají) všude, kde dochází
k interakci mezi procesy pracujícími s různými oprávněními,
ať už jsou to diskutované programy využívající mechanismus
propůjčení identifikace, programy komunikující po síti (na serverové
i na klientské straně), nebo dokonce uživatelské aplikace, jsou-li
jim předloženy vstupy pocházející z potenciálně nedůvěryhodného
zdroje. Proto nelze než doporučit, aby tvůrce libovolného programu
zvážil při jeho návrhu a implementaci možná bezpečnostní rizika
a snažil se je úplně eliminovat, nebo alespoň maximálně omezit.
výheň