Cislovani je stejne jako na cviceni. (1) Skript, ktery nedela nic, krome toho, ze po kazdem stisku Ctrl-C (tedy kdyz mu dojde signal SIGINT) vypise, kolikrat uz bylo Ctrl-C stisknuto, a po tretim Ctrl-C umre. (2) Naimplementujte seq START STEP STOP, ktery na zvlastnich radcich postupne vypisuje cisla START, START+STEP, START+2STEP, dokud nedosahne STOP. (3) Naimplementujte i seq START STOP a seq STOP. (4) Najdete bezpecnostni chybu v kodu inspirovaneho prednaskovym motivacnim prikladem na eval: if [ "$1" = "-v" ]; then verbose="" else verbose=">/dev/null" fi shift ps axu | while read uname pid x x x x x x x x cmd; do eval echo "Vidim proces $pid uzivatele $uname: $cmd" $verbose # Tady provedu neco "uzitecneho" done (Tento skript spousti root (superuzivatel, administrator) a dela si podle nej statistiku spustenych procesu, kontroluje, jestli nejaky nezere moc pameti nebo nebezi moc dlouho, nebo dela jine podobne veci. Pokud byl spusten s parametrem -v, vypisuje u kazdeho procesu nejakou hlasku, jinak ji pise do /dev/null.) Ukolem bylo, abyste jako obycejny ne-root uzivatel dokazali vyuzit chybu v tomto skriptu ke smazani celeho disku (prikazem rm -rf /). BUDTE OPATRNI - nezkousejte to naostro primo na prikazu rm -rf /. Pokud nejste root, nesmaze Vam to sice cely disk, docela dobre to ale muze smazat alespon soubory, ke kterym mate prava, napriklad vas domovsky adresar. ----------------------------------------------------------------------- ----------------------------------------------------------------------- Reseni: (1) Je rada variant tohoto skriptu. Nejprimocarejsi: #!/bin/sh signalu=0 sigint () { signalu=$(($signalu+1)) echo "Dostal jsem $signalu-ty SIGINT." if [ $signalu -le 3 ]; then exit fi } trap sigint SIGINT while true; do sleep 10; done Trapneme si signal SIGINT tak, aby pri kazdem prijeti tohoto signalu (SIGINT je interrupt from keyboard, tedy zmacknuti ctrl-c) nebyl nas skript ukoncen, ale zavolala se funkce sigint. V te inkrementujeme pocitadlo, vypiseme hlasku a pripadne skoncime cely skript. Nasledne si pustime pomoci konstrukce s while true nekonecnou smycku (prikaz true vzdy uspeje, tedy neustale cyklime). Pokud bychom ovsem uvnitr nedelali nic, bylo by to velmi narocne na procesor, kdezto onim sleepem to "zmirnime" (je pritom uplne jedno, jak dlouho spime). Kdyz zmackneme ctrl-C, SIGINT dostanou vsechny procesy bezici na nasem terminalu, tedy i shell, ze ktereho jsme skript spustili (ten SIGINT spokojene ignoruje) a prikaz sleep, ktery se naopak prerusi. Diky tomu muzeme pouzit jeste o neco jednodussi verzi: #!/bin/sh signalu=0 sigint () { signalu=$(($signalu+1)) echo "Dostal jsem $signalu-ty SIGINT." } trap sigint SIGINT while [ $signalu -le 3 ]; do sleep 10 done (2) Trivialni procviceni pocitani a cyklu: #!/bin/sh start=$1 step=$2 stop=$3 while [ $start -le $stop ]; do echo $start start=$(($start+$step)) done (3) Jednoduche rozsireni (2). Budeme duplikovat jenom inicializaci, cyklus samotny zustava stejny: #!/bin/sh start=1 # to preinicializujeme vzdy step=1 stop=-1 # to preinicializujeme vzdy case $# in 1) stop=$1;; 2) start=$1; stop=$2;; 3) start=$1; step=$2; stop=$3;; *) echo "Nespravny pocet parametru." >&2;; esac while [ $start -le $stop ]; do echo $start start=$(($start+$step)) done (Vsimnete si, ze chybovou hlasku pisu na stderr, nikoliv stdout.) (4) Opet opakuji, BUDTE OPATRNI - nezkousejte reseni primo s "rm -rf /", ale treba s "echo test". Vzdy, kdyz evalujete prikaz, do ktereho predtim vlozite promennou, kterou mohl nejak ovlivnit jiny uzivatel, mate ve Vasem skriptu bezpecnostni diru. Bud musite zkontrolovat, ze Vam uzivatel nepodstrcil do promenne neco nepekneho, nebo se musite vyhnout evalu (asi nejlepsi reseni), nebo, je-li to mozne, substituovat za promennou az behem provedeni evalu, ne pri generovani jeho parametru (tedy odescapovat uvodni $). Soucasny eval exploitneme treba tak, ze si jako obycejny uzivatel pustime prikaz s nejakymi zlovolnymi parametry: vi "; rm -rf /" To se (bez uvozovek) objevi v promenne $cmd. Jak bude vypadat volani prikazu eval? eval echo "Vidim proces 1234 uzivatele pasky: vi ; rm -rf /" \>/dev/null To vypada v poradku, strednik je preci v uvozovkach. ALE tyto uvozovky sezere jeste shell pred predanim parametru prikazu eval. eval samotny tedy bude vyhodnocovat toto: echo Vidim proces 1234 uzivatele pasky: vi ; rm -rf / >/dev/null Tedy dva prikazy oddelene strednikem. Ten druhy smaze cely disk. Oops. Co s tim? Muzeme opravit uvozovky: eval echo \"Vidim proces 1234 uzivatele pasky: vi ; rm -rf /\" \>/dev/null Na to ale zase muze zlovolny uzivatel reagovat jednoduse: vi "\"; rm -rf /\"" Z cehoz vznikne eval echo \"Vidim proces 1234 uzivatele pasky: vi \"; rm -rf /\"\" \>/dev/null a tedy bude vyhodnocen prikaz echo "Vidim proces 1234 uzivatele pasky: vi "; rm -rf /"" >/dev/null ("" se zanihiluje, proste prazdny retezec prilepeny za neprazdny retezec.) Co ted? Odescapovat uvozovky v promenne? vi '`rm -rf /`' Ani tim si nepomuzeme, pokud peclive neoescapujeme vsechny metaznaky. Lepsim resenim je cmd=$(echo "$cmd" | sed "s/'/'\\''/g") eval echo \'Vidim proces $pid uzivatele $uname: $cmd\' $verbose nebot uvnitr '' se nevyhodnocuji zadne metaznaky krome ', a apostrof uz je jednoduche sedem odescapovat. Jeste lepsim resenim je ovsem substituovat az v samotnem evalu: eval echo "Vidim proces \$pid uzivatele \$uname: \$cmd" $verbose (jen $verbose musime vyhodnotit uz driv, kvuli tomu cely eval delame). Nejlepsim resenim je vsak vubec eval nedelat, pokud to nezbytne neni nutne: if [ "$1" = "-v" ]; then echo="echo" else echo=":" fi shift ps axu | while read uname pid x x x x x x x x cmd; do $echo "Vidim proces $pid uzivatele $uname: $cmd" # Tady provedu neco "uzitecneho" done