Проблемы CGI на Perl

Автор: Topol Вторник, Апрель 17th, 2012 Нет комментариев

Рубрика: Программирование

Введение

Мне кажется, что я должен немного пояснить о чем будет идти речь. По большей части я сам писал и просматривал различные CGI и пытался разобраться с тем, как убрать те несколько проблем, которые, по-моему, являются дырками в системе. На этом я закруглюсь перейду к дыркам.

Полуфабрикат

Ядовитый NULL байт

Обратите внимание: название `Poison NULL byte` изначально был использован Olaf Kirch в письме в Bugtraq. Мне оно понравилось и оно подходит. Поэтому я его использую. Благодаря Olaf.

Когда «root» != «root», но в тоже время «root» == «root» (уже смущены?)? Когда вы смешиваете языки программирования.

Однажды вечером меня заинтересовало, что же именно может Perl и могу ли я заставить что-то идти не так, как это ожидается. Так я начал передавать очень критичные данные различным системным вызовам и функциям. Ничего захватывающего не произошло кроме одной примечательной вещи…

Как вы видите, я пытался открыть конкретный файл, «rfp.db». Я использовал фальшивый web-сценарий чтобы получить входное значение «rfp» к которому добавляется «.db» и затем открывается файл. В Perl основная часть скрипта выглядит примерно так:

 # parse $user_input $database="$user_input.db"; open(FILE "<$database");

Замечательно. Я передаю ‘user_input=rfp’ и скрипт пытается открыть «rfp.db». Все достаточно просто (давайте пока не будем рассматривать явного упущения /../).

Но интересное началось когда я передал ‘user_input=rfp%00′. Perl выполняет $database=»rfp.db» и затем пытается открыть $database. Последствия? Он открыл «rfp» (или мог бы открыть если бы он существовал). Что же случилось с «.db»? Это интересно.

Видите ли, Perl позволяет нулевые символы в качестве данных содержащихся в переменной. В отличии от C NUL не является конечным символом строки. Но лежащие ниже вызовы системы/ядра написаны на «С». Так что «root» != «root». Но вызовы системы/ядра написаны на С, который РАСПОЗНАЮТ NUL как разделитель строки. Что получается в результате? Что Perl передает «rfp.db», но лежащие ниже библиотеки останавливаются когда встречают первый NUL.

Что, если у нас есть скрипт, который позволяет отдельным младшим администраторам менять пароли любых пользователей кроме root? Код может выглядеть примерно так:

 $user=$ARGV[1] # user the jr admin wants to change if ($user ne "root"){ # делать все что угодно с этим пользователем} (**ЗАМЕЧАНИЕ: здесь показана упрощенная форма и теория чтобы только проиллюстрировать проблему)

Так что если младший администратор попробует ‘root’ в качестве имени, он не сможет что-либо сделать. Но если он передаст ‘root’, то скрипт Perl завершит проверку успешно и выполнит блок. Теперь, когда системный вызов передан (если только не все написано на Perl… что возможно, но невероятно), этот NUL будет успешно потерян и все действия будут происзодить над записью root.

Пока сама по себе эта проблема не является проблемой безопасности, но является достаточно интересной особенностью, за которой можно следить. Я видел множество CGI, которые добавляют «.html» к каким-либо вводимым данным для получения результирующей страницы. Т.е: page.cgi?page=1

Показывает мне 1.html. Это не совсем безопасно, поскольку добавляет «.html», как вы могли бы подумать, в крайнем случае я могу получить только «.html» страницу. Но если мы пошлем page.cgi?page=page.cgi%00 (%00 == » escaped)

То скрипт выдаст нам копию собственных текстов. Даже проверка с помощью опции Perl ‘-e’ не пройдет: $file=»/etc/passwd.txt.whatever.we.want»; die(«hahaha! Caught you!) if($file eq «/etc/passwd»); if (-e $file){ open (FILE, «>$file»);}

Это проскочит и (если на самом деле существует /etc/passwd) откроет его на запись.

Решение? Очень простое! Удалите нули. В Perl это всего лишь

 $insecure_data=~s///g;

ЗАМЕЧЕНИЕ: не заменяйте их набором метасимволов! Полностью удалите их!

Обратный Слэш

 

Если вы загляните в FAQ по вопросам безопасности на WWW сервере W3C, то найдете, что рекомендуется следующий список метасимволов:

 &;`'\"|*?~<>^()[]{}$\n\r

Я же нашел весьма интересным, что все, кажется, забыли про обратный слэш (‘\’), может быть это из-за того, как записывается управляющий код на Perl:

 s/([\&;\`'\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g;

Со всеми этими обратными слешами, которые комментируют [](){} и т.д. забываешь о необходимости убедиться, что и обратный слеш здесь так же перечислен (здесь он ‘\\’), из-за того, что некоторые люди просто не разбираются в регулярных выражениях и, видя присутствие обратного слеша, думают что он так же перечислен.

Так, в конце концов, почему же это важно? Представьте, что у вас есть следующая строка в CGI:

 user data `rm -rf /`

И вы прогоняете ее через вашу командную последовательность, которая превращает ее в

 user data \`rm -rf /\`

которая теперь может быть безопасно выполнена в командах шелл и т.д. Теперь, давайте представим, что вы забыли закомментировать обратный слеш. Пользователь вводит следующую строку:

 user data \`rm -rf / \`

ваш код превращает ее в:

 user data \\`rm -rf / \\`

Двойной обратный слеш будет обращен о одиночный обратный слеш в данных, оставляя кавычку не закомментированной. Что приведет к успешному выполнению `rm -rf / \`. Конечно, при таком подходе вам всегда приходится иметь дело с подложным обратным слешем. То, что вы оставите обратный слеш как последний символ в строке вызовет ошибку при обращении Perl к системному вызову или ошибку с кавычкой (по крайней мере в предыдущем примере). Вы должны ускользнуть от этого ;) (это вполне возможно)…

Другой интересный эффект связанный с обратным слешем получается из следующего кода, который запрещает обратный путь в директориях:

 s/\.\.//g;

Все, что он делает — удаляет двойные точки, эффективно устраняя обращение к файлам более высокого уровня, так что

 /usr/tmp/../../etc/passwd

превратится в

 /usr/tmp///etc/passwd

что не сработает (имейте ввиду — повторные слеши разрешены. Попробуйте ‘ls -l /etc////passwd’)

А теперь введем нашего друга — обратный слеш. Давайте дадим следующую строчку:

 /usr/tmp/.\./.\./etc/passwd

регулярное выражение не совпадет из-за обратного слеша. А теперь смотрите, как использует такое имя Perl:

 $file="/usr/tmp/.\\./.\\./etc/passwd"; $file=s/\.\.//g; system("ls -l $file");

Обратите внимание: в примере использован двойной обратный слеш, чтобы Perl вставил одиночный — иначе Perl будет считать, что вы просто комментируете точку.

С точки зрения данных строка является /usr/tmp/.\./.\./etc/passwd

В тоже время, это работает только на вызове system и вызове с обратной кавычкой. -e в Perl и open (не-конвейерный) не будут работать:

 $file="/usr/tmp/.\\./.\\./etc/passwd"; open(FILE, "<$file") or die("No such file");

«умрет» с диагностикой «No such file». По-моему это потому, что требуется шел для преобразования ‘\.’ в ‘.’.

Решение? Убедитесь, что вы комментируете обратный слеш. Очень просто.

Эта вредная труба

 

В Perl добавление ‘|’ (конвейер) к концу имени файла в операторе open заставляет Perl выполнить указанный файл а не открыть его. Так

 open(FILE, "/bin/ls")

выдаст вам кучу двоичного кода, но

 open(FILE, "/bin/ls|")

на самом деле запустит /bin/ls. Заметьте, что регулярное выражение

 s/(\|)/\\$1/g

Предотвратит это (Perl «умрет» с диагностикой ‘unexpected end of file’ поскольку sh требуется следующая строка, которую обозначает ‘\’. Если вы найдете способ обойти это — пожалуйста дайте мне знать).

Теперь мы можем обобщить ситуацию с другими методами, показанными выше. Давайте предположим, что $FORM это строка, посланная пользователем на вход CGI. Вначале у нас есть:

 open(FILE, "$FORM")

Так мы можем установить $FORM в «ls|» чтобы получить список директории. Теперь, предположим мы имеем:

 $filename="/safe/dir/to/read/$FORM" open(FILE, $filename)

тогда нам надо определенным образом указать где находится «ls», так что мы установим $FORM в «../../../../bin/ls|», что даст нам каталог директории. Поскольку это конвейерный open наша техника с обратным слешем может быть использована, если она применима, чтобы обойти анти-обратный-ход в директориях.

До сих пор мы могли использовать опции командной строки в команде. Например, используя приведенный выше кусок кода, мы могли установить $FORM в «touch /myself|» чтобы создать файл /myself

Сейчас у нас более сложная ситуация:

 $filename="/safe/dir/to/read/$FORM" if(!(-e $filename)) die("I don't think so!") open(FILE, $filename)

Теперь нам придется оставить в дураках ‘-e’. Проблема в том, что ‘-e’ отвалится, если попробует найти ‘ls|’ поскольку такого не существует — она просматривает имя файла вместе с настоящим конвейером в конце. Таким образом нам надо убрать конвейер для -e но оставить его для того, чтобы его видел Perl. Что-нибудь приходит на ум? Ядовитый NULL — вот наше спасение! Все что надо сделать — установить $FORM в «ls|» (или в форме управляющих символов web «ls%00|». После этого ‘-e’ проверяет наличие ‘ls’ (он останавливается на NULL игнорируя конвейер). В то же время Perl видит конвейере и отлавливает его и… выполняет нашу команду, он останавливается на NULL — это означает, что мы не можем задать опции командной строки. Может быть этот пример покажет лучше:

 $filename="/bin/ls /etc|" open(FILE, $filename)

Это дает нам каталог директории /etc

 $filename="/bin/ls /etc|" if(!(-e $filename)) exit; open(FILE, $filename)

Придется так, потому что ‘-e’ увидит, что «/bin/ls /etc» не существует

 $filename="/bin/ls /etc|" if(!(-e $filename)) exit; open(FILE, $filename)

Это будет работать, но мы получим листинг текущего каталога (как в случае с просто ‘ls’), а не листинг директории /etc.

Я хочу также заметить для ущербных программистов: если бы ленивые программисты на Perl (не все, а только ленивые) тратили бы чуть-чуть времени и указывали бы режим доступа к файлу, до речи бы не шло о подобной ошибке.

 $bug="ls|" open(FILE, $bug) open(FILE, "$bug")

работает, но

 open(FILE, "<$bug") open(FILE, ">$bug") open(FILE, ">>$bug") и т.д. и т.п.

не работает. Если вы хотите читать файл, то откройте «<$file» а не просто $file. Вставив всего один символ «меньше» вы защитите и себя и свой сервер от кучи неприятностей.

OK, теперь, когда у нас есть оружие, давайте займемся противником.

(беззащитные) скрипты Perl в этой жизни

 

Наш первый CGU я взял с freecode.com. Это администратор рекламных баннеров. Из файла CGI:

 # First version 1.1 # Dan Bloomquist dan@lakeweb.net

Теперь первый пример… Дан разбирает все переменные входной формы в %DATA. Он не исключает ни ‘..’ ни NUL. Взглянем на кусочек кода:

 #This sets the real paths to the html and lock files. #It is done here, after the POST data is read. #of the classified page. $pageurl= $realpath . $DATA{ 'adPath' } . ".html"; $lockfile= $realpath . $DATA{ 'adPath' } . ".lock";

Используя ‘adPath=/../../../../../etc/passwd%00′ мы можем указать на /etc/passwd. То же самое с $lockfile. Мы не можем использовать конвейер, т.к. он добавляет «.html»/».lock» в конце (вы, конечно, можете попробовать — но работать не будет ;)

 #Read in the classified page open( FILE,"$pageurl" ) || die "can't open to read $pageurl: $!\n"; @lines= <FILE>; close( FILE );

Здесь Дан считывает $pageurl, которая является указанным нами файлом. К счастью для Дана, затем он немедленно открывает $pageurl на запись. Так что чтобы мы не указали для чтения — нам нужны так же права на запись. Это ограничивает возможности взлома. Но это служит великолепным примером подобной проблемы.

Достаточно любопытно, что Дан затем продолжает:

 #Send your mail out. # open( MAIL, "|$mailprog $DATA{ 'adEmail' }" ) || die "can't open sendmail: $adEmail: $!\n";

Хмммм… здесь ваши обязательные нет-нет…. Дан не разбирает метасимволы shell, так что ‘adEmail’ становится ужасающим.

Исследуя далее freecode.com, я нашел простую программку записи данных из форм:

 # flexform.cgi # Written by Leif M. Wright # leif@conservatives.net

Лейф заносит данные в %contents и не комментирует метасимволы shell. Затем он делает следующее:

 $output = $basedir . $contents{'file'}; open(RESULTS, ">>$output");

Используя обычный обратный путь в директориях мы можем даже не добавлять NUL. Но опять де нам необходимо везение с разрешениями на файл, который мы хотим открыть. И опять же дырка с конвейером не будет работать, поскольку используется режим добавления к файлу (‘>>’).

Теперь LWGate, который является WWW-интерфейсом ко многим популярным спискам рассылки.

 # lwgate by David W. Baker, dwb@netspace.org # # Version 1.16 #

Дэйв помещает разобранные переменные форм в %CGI, затем:

 # The mail program we pipe data to $temp = $CGI{'email'}; $temp =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g; $MAILER = "/usr/sbin/sendmail -t -f$temp" open(MAIL,"| $MAILER") || &ERROR('Error Mailing Data')

Хм… кажется Дэйв забыл обратный слэш в регулярном выражении. Ай-яй-яй.

Так. Теперь давайте посмотрим на одно из многих приложений — «тележек для покупок». Опять же с freecode.com, Perlshop.

 $PerlShop_version = 3.1; # A product of ARPAnet Corp. - perlshop@arpanet.com, www.arpanet.com/perlshop

Интересным является следующее:

 open (MAIL, "|$blat_loc - -t $to -s $subject") || &err_trap("Can't open $blat_loc!\n")

$to это явно определяемый пользователем email. Blad — почтовая программа NT. Метасимволы в NT это <>&|% (что-то еще?).

Помните про вредную трубу? (надеюсь, что помните… это всего парой параграфов выше!). Признаюсь, это не самая удачная ошибка, но это я ее нашел. Давайте пройдемся далее по архиву скриптов Матта.

 # File Download Version 1.0 # Copyright 1996 Matthew M. Wright mattw@worldwidemart.com

Сначала он выбирает данные в $Form (ничего не комментируя). Затем выполняет следующее:

 $Request_File = $BASE_DIR . $Form{'s'} . '/' . $Form{'f'}; if (!(-e $filename)) { &error('File Does Not Exist'); } elsif (!(-r $filename)) { &error('File Permissions Deny Access'); } open(FILE,"$Request_File"); while (<FILE>) { print; }

Это вполне удовлетворяет критериям ‘проблемы вредной трубы’ ™. Имеется проверка ‘-e’, так что мы не можем использовать аргументы командной строки. Поскольку вначале он приклеивает $BASE_DIR нам придется использовать обратный путь в директории.

Уверен, что читая выше вы встретились с более простой проблемой. Как насчет f=../../../../../../etc/passwd? Да, он существует и может быть прочитан. таким образом вам его должны показать. И покажут. Но с другой стороны: весь доступ к download.cgi записывается в журнал следующим кодом:

 open(LOG,">>$LOG_FILE"); print LOG "$Date|$Form{'s'}|$Form{'c'}|$Form{'f'}\n"; close(LOG);

Так что, скрытая камера наблюдает за всем, что вы делаете. Но в любом случае — не хорошо причинять зло серверам посторонних людей. ;)

Теперь полетаем вместе с BigNoseBird.com. Я имею ввиду:

 bnbform.cgi #(c)1997 BigNoseBird.Com # Version 2.2 Dec. 26, 1998

Самое интересное происходит после того, как скрипт открывает конвейер к сендмейлу как MAIL:

 if ($fields{'automessage'} ne "") { open (AM,"< $fields{'automessage'}"); while (<AM>) { chop $_; print MAIL "$_\n"; }

Еще одна простая вещь. BNB не делает какого-либо разбора входных переменных пользователей ($fields), так что мы можем указать любой файл как ‘automessage’. Если он доступен на чтение контексту веб-сервера он будет послан на любой адрес, который мы укажем (по крайней мере теоретически).

А вот и конец

 

Да, так. К этому времени я слегка устал от разбора программ на Perl. Я оставлю немножко и вам, в качестве домашнего задания. И если вы что-то найдете — черкните мне пару строчек — особенно если вы найдете скрипты, где можно использовать ‘проблему вредной трубы’. На этом все. До новых встреч.

Источник: woweb.ru

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты