Сигурно използване на Bourne shell I. Що е то Bourne shell и за какво служи? 1. История - команден интерпретатор още в първите версии на AT&T Unix, създаден от Stephen Bourne на основа на предишни шелове на Ken Thompson и John Mashey - разширения впоследствие: ksh (Korn shell на David Korn), zsh (Z Shell на Paul Falstad), bash (Bourne-again shell на Chet Ramey като част от проекта GNU) и други - несъвместими други шелове: csh (C Shell на Bill Joy), tcsh (TENEX C shell на Bill Joy и Christos Zoulas), rc (част от Plan 9), ... - Bourne shell, особено във версията, стандартизирана в POSIX, е де факто стандарт за писане на shell scripts, които да могат да бъдат изпълнени върху различни платформи 2. Използване - интерактивно изпълнение на команди (login shell) - автоматизирано изпълнение на често повтаряни действия (scripts) - отдалечено изпълнение на команди: винаги се минава през login шела върху компютъра, който действително изпълнява командата - изпълнение на външни команди от програми на други езици (system(), exec, passthru(), ...) II. Поглед върху сигурността 1. Какво - от какво се боим? - пряко изпълнение на вмъкнати от атакуващия команди - промяна на действието на зададени от нас команди чрез вмъкнати от атакуващия опции и параметри - промяна на логиката на изпълнение на програмата/скрипта - странни странични ефекти - събиране на информация за системата, промяна на файловата система, промяна на работата на други програми и т.н. 2. Как - как можем да получим несигурни данни от потребителя/атакуващия? - параметри от командния ред - променливи от обкръжението - използване на read за въвеждане на данни от потребителя - използване на изхода от други програми (``, $(..)) - изпълнение на шел-команди със source (или '.') III. Често срещани проблеми 1. PATH-базирани атаки - външни програми се изпълняват или със задаване на пълен път към тях, или само със задаване на името; във втория случай шелът претърсва директориите, описани в променливата PATH от обкръжението, и изпълнява първата програма с такова име, което намери в някоя от тях в реда, в който са зададени - атакуващият може да зададе собствена стойност за PATH, която да съдържа директория, в която той е поставил програма със същото име, която обаче прави нещо малко по-различно, и може да бъде изпълнена с повишени привилегии или да й бъдат предадени поверителни данни - решение 1: твърдо задаване на PATH в шел-скрипта: може да създаде проблеми при преносимост върху други операционни системи или специални инсталации - решение 2: твърдо задаване на пълен път при всяко изпълнение на външна програма: *ще* създаде *много* проблеми при пренасяне върху друга ОС или специални инсталации - решение 3: задаване на път (и евентуално параметри) за използваните команди веднъж в началото на скрипта, запазването им в променливи и използване на тези променливи: SSH=/usr/bin/ssh RSYNC=/usr/local/bin/rsync XSLTPROC=/usr/local/bin/xsltproc BC=/usr/bin/bc -posix ... $RSYNC -avz "$IN" "$OUT" $SSH "$user"@"$host" rm /tmp/ssh.lock Това, че около $RSYNC и $SSH няма кавички, е съзнателно: за работата на някои програми върху някои системи може да се наложи да зададем опции, така че те да работят по очаквания начин. Ако използваме "$BC" ..., тогава интервалът и -posix ще бъдат приети като част от името на програмата, която да бъде изпълнена, и нищо няма да се получи. 2. Whitespace и разделяне на изпълняваната команда на 'думи', преди да бъде изпълнена като команда и аргументи - шелът обработва редове от текст, разпознава синтактични конструкции (if, while, for), сложни команди (&&, ||, |, ;, ...) и прости команди, прави заместване на променливи, макроси, ~username и разни други такива, разделя ги на думи и ги изпълнява (при простите команди първата дума е командата, останалото са аргументи) - разделяне - по whitespace: интервали и табулации - ако не внимаване, потребителят може да вкара интервал или табулация в променлива или по друг начин в командата, преди шелът да я е разделил на думи, и по този начин да промени броя/вида на параметрите - това е feature! Важно е за правилната работа на много скриптове! а) разделяне на командни параметрию cp in/$1 out/ -- ако $1 е 'file.txt /etc/passwd', това ще изпълни cp in/file.txt /etc/passwd out/ и ще изкопира файл, който всъщност не се намира в директорията in/ Последствия: - промяна на работата на програмата, странични ефекти - може директно да донесе полза за атакуващия - може да попречи на работата на програмата по-нататък, ако тя не е успяла да направи това, което е очаквала да се случи б) промяна на значещи опции cp in/$1 out/ - при GNU cp, ако $1 е 'file.txt -R /etc', това -R ще бъде интерпретирано като опция за cp и тя ще изкопира цялата директория /etc с всичките й файлове и поддиректории в out/etc/ ! adduser $username - ако $username е '-u 0 newroot', това -u 0 може да убеди adduser да даде на новия потребител user ID 0, т.е. администраторски привилегии if [ $answer = Y ]; then... - ако $answer е '-f /etc/shadow -o N', това ще се превърне в if [ -f /etc/shadow -o N = Y ]; then... т.е. в логически еквивалент на if [ -f /etc/shadow ]; then... и атакуващият ще може да събере информация за системата, до която по принцип не би трябвало да има достъп в) защита - QUOTING! QUOTING! QUOTING! - '...' - шелът го интерпретира като една дума, без да интерполира (замества) променливи; използвайте това за непроменящи се низове: echo 'Trying to do some stuff...' - "..." - шелът го интерпретира като една дума с интерполиране на променливи; използвайте кавички ВИНАГИ, когато искате да подадете точно една дума като команда или параметър! cp in/"$1" out/ adduser "$username" if [ "$answer" = 'Y' ]; then... - особености при опит за предаване на всички параметри от командния ред: $* ще ги даде като отделни думи, само че тези, съдържащи интервали, ще бъдат разделени на повече от една дума; "$*" ще ги даде като една-единствена дума, което в повечето случаи не е това, което искаме; "$@" ще ги даде като отделни думи, но всеки параметър като точно една дума, независимо от това дали съдържа интервали и табулации или не. 3. Динамично генериране на код а) `команда с парамери`, $(команда с параметри) - изпълняват командата, като преди това интерполират всички променливи и т.н., и връщат като стойност това, което тя е извела на стандартния си изход. userid=`id -n $username` groupid=`id -gn $username` - същите проблеми като при нормално изпълнение на команди - whitespace може да доведе до неправилно разделяне на командата на думи - затваряне на променливите в кавички помага б) eval "низ" изпълнява низа като ПОСЛЕДОВАТЕЛНОСТ от шел-команди! Тук може не само да бъде разделен неправилно на думи, а може и да се достигне до изпълнение на повече от една команда, ако някоя променлива съдържа ||, &&, |, ; или други метасимволи, неправилно оценяване на променливи, ако някоя променлива съдържа $, изпълнение на други команди, ако някоя променлива съдържа `..` или $(...), и всякакви други неприятни неща! в) динамично създаване на изпълними шел-скриптове върху диска, т.е. файлове, които след това някой друг шел-скрипт ще изпълни директно или чрез source (или '.'). г) изпълнение на отдалечени команди чрез ssh или rsh 4. Динамично изпълнение на код - source - САМО ако знаете ТОЧНО какво изпълнявате! - ако става дума само за конфигурационни файлове, може да бъде направена частична алтернатива с while read и expr, но и там може да има проблеми с особени символи като ', " и т.н. 5. Почистване на временни файлове и други временни данни - нормално завършване на програмата: за всеки exit ли сме сигурни, че ще почисти както трябва? Пропуснали ли сме някой exit? - прекъсване: ^C, logout/hangup (затваряне на login сесия), сигнали, ... - trap "команда" SIGNALNAME1 SIGNALNAME2... TDIR=`mktemp -d -t mytempdir` trap "rm -f \"$TDIR\"" TERM INT HUP (може и SEGV FPE ABORT ...) 6. Race conditions - проверка и после създаване на файл: може да бъде избегнато с режима (опцията) noclobber: 'set -o noclobber' 7. Създаване на временни файлове - НИКОГА не ги създавайте директно в world-writeable и world-readable директория като /tmp - или поне не и ако не искате целият свят да може да ги прочете или в някои случаи и замаже :) - не ги създавайте с предварително добре известно име или в директория с предварително известно име по същата причина; дори и тази директория да е собственост на потребителя, който изпълнява програмата, може да се окаже, че през същия акаунт се изпълняват и други неща (пример: уебсървър в хостинг система) - използвайте mktemp, особено ако тя може да създава временна директория: mktemp -d -t mytempdir - mktemp използва променливи от обкръжението като TMPDIR, за да реши къде да създаде временния файл или директория! Затова, ако позволяваме на потребителя да контролира тези променливи, ТРЯБВА да слагаме кавички около това, което получаваме, за да няма изненади с интервали :) TDIR=`mktemp -d -t mytempdir` INFILE="$TDIR"/in.txt OUTFILE="$TDIR"/out.txt trap "rm -rf \"$TDIR\"" INT HUP TERM 8. Проверка за валидност на данните - expr(1) - стандартна част от Unix (или по-точно POSIX) средата - може да се използва за проверка за съвпадения с регулярни изрази и за вадене на части от тях с expr "текст" : "израз" echo 'Enter a number:' read count badchars=`expr "$count" : '[0-9]*\([^0-9]*\)'` if [ $? -eq 0 ]; then echo "Invalid character(s) $badchars in the count!" 1>&2 exit 1 fi echo "$count seems to be a number indeed" IV. Препоръки - QUOTING! QUOTING! QUOTING! - апострофи, където е възможно, иначе кавички - expr за проверка на валиден формат на данните - при повишени привилегии или поверителна информация: НИКОГА source на файл, контролиран от потребителя! - mktemp и mktemp -d за временни файлове (и пак quoting!) - PATH - quoting! quoting! quoting!