Уязвимость связки PHP+nginx с кривым конфигом

    Summary


    Announced: 2010-05-20
    Credits: 80sec
    Affects: сайты на ngnix+php с возможностью загрузки файлов в директории с fastcgi_pass




    Background


    Зачастую How-To по настройке связки nginx с php-fpm / php-cgi есть подобные строчки:

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
        include fastcgi_params;
    }
    


    Problem Description


    Однако если попросить у сервера отдать example.com/1px.gif/test.php, то URI примет вид 1px.gif/test.php, что подойдёт под location \.php$, а SCRIPT_FILENAME станет равным /scripts/1px.gif/test.php.

    Далее, если cgi.fix_pathinfo == 1 (по дефолту), то SCRIPT_FILENAME станет равным /scripts/1px.gif, а PATH_INFO будет равен test.php

    NB! В некоторых конфигурациях уязвимость триггерит URL вида 1px.gif%00test.php

    В итоге php интерпретатор обработает /scripts/1px.gif. То есть,

    Impact


    Любой пользователь будет иметь возможность заливать файлы на сервер (например, аватары) то создав особенное изображение, которое будет одновременно проходить валидацию размеров GD и исполняться php интерпретатором, будет иметь права на исполнение произвольного кода на сервере с правами php процесса.

    Workaround


    Через в php.ini

    cgi.fix_pathinfo=0


    или же через конфиг nginx.conf
    location ~ \.php$ {
            try_files $fastcgi_script_name =404;
    	fastcgi_index			index.php;
    	fastcgi_param			script_FILENAME /scripts$fastcgi_script_name;
    	include				fastcgi_params;
    }
    

    это фактически закроет доступ ко всем не существующим файлам .php
    Метки:
    Поделиться публикацией
    Комментарии 109
    • +4
      полезно, в избранное. Пошел править конфиги.
      • 0
        ТС спасибо
        • 0
          особенное изображение, которое будет одновременно проходить валидацию размеров GD и исполняться php интерпретатором, будет иметь права на исполнение произвольного кода на сервере с правами php процесса.

          А реально такое изображение создать? Ведь в начале каждого img идет бинарный header
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              зачем? Можно просто после конца gif вставить сколько надо и какой хочешь информации, вполне будет валидная картинка
              • +2
                А еще валиднее — EXIF! Очень семантично, валидаторастам на радость!
                • +1
                  А что? После тела идет нужный мусор. Я так, в сове время, делал гигабайтные прозрачные гифки 1x1 пиксель ;)
                  • 0
                    А зачем если не секрет? Просто чтобы подшутить над кем-то?
                    • 0
                      Гиговый — для сових личных тестов.

                      Вообще так можно удобно симулировать новый траффик на вполне реальных клиентах
                      • +2
                        бедные клиенты )
                        • 0
                          еще никто не жаловался, между прочим. Пока я это делал, я сильно мониторил вебпланету и хабр
                  • +1
                    если картинка обрабатывается с помощью функций PHP работы с изображениями, то вся EXIF-информация будет удалена
              • 0
                google://rarjpeg
              • НЛО прилетело и опубликовало эту надпись здесь
                • –1
                  перфоманс у вас тоже не возникает, однако
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • –9
                      MVC хорошая, красивая архитектура. Но, например, тот же Шетухин (вы знаете кто это?) ругался на нее.

                      Далее, я не понимаю, зачем использовать index.php; php!; для отдачи просто статической картинки?
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • –10
                          тогда ваш комментарий это просто позерство. Ибо тут обсуждается раздача статики.

                          Но критика к MVC все так же остается.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • –10
                              Я чуть ниже уже высказал мысль, что статику надо отдавать тем, кто это умеет делать хорошо: akamai, amazon cloudfront или любой другой CDN
                              • –2
                                товарищи с минусами, а расскажите что вам не нравится?
                                • +2
                                  invidia :>
                      • 0
                        Да хотя бы так: RewriteRule !\.(js|gzip|gz|css|ico|gif|jpe?g|png|zip|txt|xml|htm?l|rar|exe|php|tar|bin)$ index.php [L]
                        А в папках с графикой запрет на выполнение чего-либо интерпретатором.
                        • 0
                          htm?l — ошибочка, надо html?
                  • +10
                    Это не проблема nginx; это проблема тех, кто читает howto и делает по ним, вместо того, что бы прочитать документацию, которая, в основном, на русском!
                    • 0
                      проблема именно в cgi.fix_pathinfo — fpm тоже не при чём
                      • 0
                        по-мойму это проблема в недопонимание инструментов, что используют
                        • +6
                          Проблема как всегда в php. cgi.fix_pathinfo реально сложный инструмент. PATH_INFO изначально появился для SEO-шников, чтобы были красивые урлы (как бы статические, раньше поисковики так любили), если php запускается как CGI и веб сервер не поддерживает rewrite.
                          Понимаете для какого это года актуально? ;-)
                          Фича прижилась в php, как и множество других костылей из cgi_main.c.
                          Также, зачем-то cgi.fix_pathinfo по умолчанию выставили в «1» — спорное решение.
                          Получается, в случае её использования rewrite происходит непосредственно в php, и это происходит по-умолчанию.
                          Понятно, что если rewrite помимо этого происходит в веб сервере, то ничего хорошего не получится.
                          Потом в cgi sapi появилась поддержка fastcgi. это тоже было странно — надо было уже разносить на разные sapi — в cgi и fastcgi не сильно много общего с точки зрения кода. Для эры fastcgi опция уже давно потеряла свой старый смысл и актуальность, но уже не осталось людей, которые имели полную картину происходящего.
                          Потом fastcgi sapi начали повсеместно использовать с nginx (изначально fastcgi использовался с IIS/zeus и он был сильно специфичнее в использовании).
                          И вот, наконец, костылики с хрустом начали ломаться обнажая все проблемы что были скрыты все эти годы.
                          • +4
                            да, забыл добавить.
                            сама реализация cgi.fix_pathinfo достаточно тормозная — на каждый запрос проверяется каждая компонента пути — не скрипт ли это.
                            так что её стоит выключать также и по соображениям производительности.
                            • 0
                              The WWW Common Gateway Interface Version 1.1 8 января 1996 года. PATH_INFO уже есть. Более древних упомининай не нашёл, но уже видно что дела давних дней. Очень давних.
                      • –9
                        Спасибо! Заэксплуатируем, пока не закрыли.
                        • 0
                          Что использовать вы хотите? Что закрывать?
                          • 0
                            Не будут закрывать.
                            Конструкции вроде /wiki/index.php/Заглавная никто не отменял, да и переход на интерпретатор по *.php тоже.
                            • 0
                              вы мне скажите, в чем ошибка-то?
                              • 0
                                Ошибки нет, это не баг, а фича (фичи), поэтому в самом коде nginx по этому поводу, скорее всего, ничего менять не будут. Ошибка если и есть, то в конфигах отдельных конфигураций. А фичи эти вполне часто используемые (те же самые /wiki/index.php/Заглавная).
                                «Не будут закрывать» == «Пальцем не пошевелят по этому поводу»
                          • 0
                            Я правильно понимаю, что если за nginx стоит apache, он обработает либо запрос /scripts/1px.gif/test.php либо /scripts/1px.gif и в любом случае не станет выполнять картинку как php-скрипт?
                            • +2
                              Да. Поведение nginx ровно такое, как его просил автор конфига. Все что кончается на .php выполнять там. Я не пониманию, почему это ошибка.
                              • –1
                                Как минимум по тому, что просят выполнить .../img/test.gif/.php, а он по факту выполняет .../img/test.gif как PHP.
                                • +1
                                  Покажите мне debug log
                                  • 0
                                    Да, это действительн php меняет pathinfo, а не nginx.
                                    • +7
                                      тогда топикстартер просто провокатор! Который громко крикнул и убежал в кусты
                                      • 0
                                        =)) Ну во-первых, не в кусты, а на обед.
                                        Во-вторых я не писал, что это уязвимость nginx. Читайте внимательно.
                                        Ну и в третьих проблема мне кажеться в том, что оказывается в переменной $fastcgi_script_name. У лайти такого например нету.
                                        • +1
                                          А что в ней оказывается в nginx и лайте?
                                          • +1
                                            nginx:
                                            /scripts/1px.gif/test.php

                                            lighttpd:
                                            /scripts/1px.gif
                                          • +1
                                            проблема в том, что некоторые, используют софт, не думая, что они делают
                                  • –1
                                    Всетаки ошибка в том, что nginx по умолчанию делает то, что ему делать не следовало бы, правит pathinfo. Как он вообще догадывается, что нужно проверить существование файла? Там четко написано — если запрос заканчивается на php. Про файлы там вообще ничего нет.
                                    • +3
                                      nginx передает этот запрос на fastcgi обработчик. Как обрабатывает его php, nginx как-то не особо волнует
                                      • 0
                                        Прошу прощения, проморгал, что cgi.fix_pathinfo пэхэпэшнаю опция. Тогда к nginx никаких претензий.
                                        • –4
                                          Топик стартер просто провокатор, это нормально, они почти все такие…
                                • +12
                                  Статику вообще надо держать на отдельном server{}, где нет никаких средств исполнения. И будет счастье.
                                  • +2
                                    а лучше на отдельном домене, за который отвечает внешний cdn…

                                    но это, дивный, новый, мир. А мы про суровые, российские будни
                                  • +9
                                    Катап решил эпично всех завоевать в этом топике!
                                    • +13
                                      Причём не лично, а через, простите, CDN.
                                    • –1
                                      Писал когда то по просьбе знакомого описание наиболее распространенных уязвимостей: recoilme.ru/blog/comments/237
                                      • 0
                                        Как ни пытался не получается ошибку воспроизвести. Всё время пишет «No input file specified.»
                                        • 0
                                          Ах да, у меня используются unix socket'ы
                                        • 0
                                          У вас точно cgi.fix_pathinfo установлена в 1?
                                          • 0
                                            Она у меня вообще нигде не прописана, даже в php.ini
                                            phpinfo() эту опцию вообще не отображает. Вот конфиг:

                                            location ~ \.php$ {
                                            fastcgi_pass unix:/home/vz/root/201/tmp/php.sock;
                                            fastcgi_index index.php;
                                            fastcgi_param script_FILENAME /home/username/htdocs$fastcgi_script_name;

                                            fastcgi_param QUERY_STRING $query_string;
                                            fastcgi_param REMOTE_ADDR $remote_addr;
                                            fastcgi_param REQUEST_METHOD $request_method;

                                            fastcgi_param CONTENT_TYPE $content_type;
                                            fastcgi_param CONTENT_LENGTH $content_length;

                                            fastcgi_param script_NAME $fastcgi_script_name;
                                            fastcgi_param REQUEST_URI $request_uri;
                                            fastcgi_param DOCUMENT_URI $document_uri;
                                            fastcgi_param DOCUMENT_ROOT $document_root;
                                            fastcgi_param SERVER_PROTOCOL $server_protocol;
                                            fastcgi_param GATEWAY_INTERFACE CGI/1.1;
                                            fastcgi_param REMOTE_PORT $remote_port;
                                            fastcgi_param SERVER_ADDR $server_addr;
                                            fastcgi_param SERVER_PORT $server_port;
                                            fastcgi_param SERVER_NAME $server_name;
                                            }

                                            Unix socket поднят в виртуальной среде
                                            • 0
                                              Т.е. unix:/home/vz/root/201/tmp/php.sock; относится к серверу, а
                                              fastcgi_param script_FILENAME /home/username/htdocs$fastcgi_script_name; уже к виртуальной машине.
                                              • 0
                                                А какая версия пхп и с какими ключами собрана?

                                                Судя по php.net/manual/en/ini.core.php:

                                                Available since PHP 4.3.0. PHP_INI_ALL prior to PHP 5.2.1.
                                                • 0
                                                  5.2.11
                                                  • 0
                                                    Вот ключи:

                                                    Configure Command => './configure' '--with-config-file-path=/etc/php5' '--mandir=/usr/share/man' '--enable-memory-limit' '--disable-debug' '--with-regex=php' '--disable-rpath' '--disable-static' '--with-pic' '--with-layout=GNU' '--with-pear=/usr/share/php' '--enable-calendar' '--enable-sysvsem' '--enable-sysvshm' '--enable-sysvmsg' '--enable-trans-sid' '--enable-bcmath' '--with-bz2=/usr/lib' '--enable-ctype' '--without-gdbm' '--with-iconv' '--enable-exif' '--with-gettext' '--enable-mbstring' '--with-pcre-regex=/usr' '--enable-shmop' '--enable-sockets' '--with-libxml-dir=/usr/lib' '--with-zlib' '--with-zlib-dir=/usr/lib' '--with-openssl=/usr' '--enable-soap' '--enable-zip' '--with-mime-magic=' '--with-exec-dir=/usr/lib/php5/libexec' '--with-system-tzdata' '--prefix=/usr' '--without-mm' '--without-sybase-ct' '--without-mssql' '--without-sqlite' '--sysconfdir=/etc/php5' '--with-gd' '--enable-gd-native-ttf' '--with-jpeg-dir=/usr/lib' '--with-png-dir=/usr/lib' '--with-freetype-dir=/usr/lib' '--with-mhash=shared,/usr/lib' '--with-mysql=/usr/lib/mysql/lib' '--enable-shared' '--enable-fastcgi' '--with-fpm' '--with-fpm-conf=/etc/php5/php-fpm.conf' '--with-fpm-log=/var/log/php-fpm.log' '--with-fpm-pid=/var/run/php-fpm.pid' '--with-curl=/usr/lib' '--with-mcrypt=/usr/lib' '--with-libevent=shared'
                                              • 0
                                                Ах да, у меня еще PHP 5.2.x
                                            • 0
                                              Сработал адрес вида site/file.gif%00.php

                                              Ушел патчить
                                              • +1
                                                Помогло вот это:

                                                location ~ \00 {
                                                deny all;
                                                }
                                                • 0
                                                  А теперь
                                                  location ~ \01 {
                                                  deny all;
                                                  }
                                                  И так далее.
                                                  • +2
                                                    %01 не обозначает конец строки и никак не влияет на уязвимость
                                            • –9
                                              Бугога =) Капитан очевидность. Баге этой сто лет в обед.
                                              • 0
                                                Да, знаю, я специально указал Announcement date. Просто сёдня в очередной раз нашёл её на нескольких сайтах, решил написать на хабре, может админы заметят.
                                                • 0
                                                  Еще раз спасибо, кэп. Но багу далеко не первый год. Это «стандартная» уязвимость у админского нубья. С Apache mod_cgi та же фигня.
                                              • 0
                                                location ~* ^/users_upload_dir/.* {
                                                break;
                                                }
                                                и все.
                                                • 0
                                                  нафиг пользователям загружать картинки если их нельзя посмотреть?
                                                  • 0
                                                    ну там же не написано return 403;
                                                    картинки отдадуться, покажутся.
                                                    но не попадут под location ~ \.php {}
                                                • +4
                                                  уязвимость связки? м… э…
                                                  • +2
                                                    ещё есть аналогичный «нюанс» с путями вида httр://........./image.gif%00.php
                                                    • 0
                                                      у меня выдаёт 400 Bad request, что в принципе логично
                                                      • 0
                                                        А вот это у меня сработало
                                                        • 0
                                                          А вот у меня нет — почему?
                                                      • 0
                                                        Можно просто проверить существует ли файл и вернуть, например, 404 Not Found:

                                                        location ~ \.php$ {
                                                        try_files $uri =404;
                                                        include «fastcgi_params»;
                                                        fastcgi_pass ...;
                                                        fastcgi_param ...;
                                                        }
                                                        • 0
                                                          вообще тут нужно с умом подойти. в nginx есть куча способов от этой вещи отмазаться, но если у вас генерятся картинки (sic!) или что-то другое на лету, тут надо думать
                                                          • 0
                                                            Я везде использую такую конструкцию, проверяющую наличие файла:

                                                            location ~ \.php$ {
                                                            	if ( -f $request_filename ) {
                                                            		fastcgi_pass		unix:/tmp/php-fpm.sock;
                                                            	}
                                                            	fastcgi_index			index.php;
                                                            	fastcgi_param			script_FILENAME /nginx/html$fastcgi_script_name;
                                                            	include				fastcgi_params;
                                                            }
                                                            • 0
                                                              где-то в рассылке nginx пробегало, что конструкции с if лучше избегать.
                                                              • 0
                                                                • 0
                                                                  Да не, можно использовать. Говорите всем что катаб вам позволяет.

                                                                  Только стоит понимать что делает if и почему у вас будут сегфолы и никто править не будет ;)
                                                                • +1
                                                                  Ого, я вижу, что кусок моего конфига ушел в пост. Мелочь, а приятно :))
                                                              • +1
                                                                Что для меня всегда было загадкой, так это возможность upload'ить картинки в каталог "/scripts/".
                                                                • 0
                                                                  угу. более чем странно.

                                                                  это скорее не для
                                                                  fastcgi_param script_FILENAME /scripts$fastcgi_script_name;
                                                                  а для варианта
                                                                  fastcgi_param script_FILENAME $document_root$fastcgi_script_name;
                                                                  который в большем количестве how-to встречается…
                                                                • 0
                                                                  Спасибо! Закрыл. Правда говоря трюк с php.ini почему-то не прошел. Сделал через конфиги nginx'а.
                                                                  • 0
                                                                    хм… топик обновлен.
                                                                    if ( $fastcgi_script_name ~ \..*\/.*php ) {
                                                                    return 403;
                                                                    }
                                                                    а чем этого мало?
                                                                  • +4
                                                                    А мой патч, который исправляет все эти проблемы сразу, так и не приняли… :/
                                                                    • 0
                                                                      Желто.
                                                                      • 0
                                                                        Сорри, отправилось раньше.

                                                                        Желто.
                                                                        На нормальном конфиге php НИКОГДА не должен обрабатываться в пути, куда может заливать юзер.
                                                                        Это реализуется, как было описано выше, редиректом всей динамики на index.php, как вариант — наличие php-скриптов в отдельном от статики месте или location ^~ для статики.

                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                        • 0
                                                                          А ЧПУ будет работать?
                                                                          • 0
                                                                            БОльшая часть известных ЧПУ реализована через htaccess, тут уже не nginx
                                                                            • 0
                                                                              Имею в виду битриксы, джумлы, велоЦМС etc.
                                                                              • 0
                                                                                Я просто забыл о существовании apache, поэтому и спросил.
                                                                                У меня в конфиге насколько я помню эта строчка ведет не на 404, а на /bitrix/urlrewrite.php
                                                                                • 0
                                                                                  Тем более тема о связке nginx + php-cgi
                                                                                  Во всех HowTo по настройке связки nginx с php-fpm / php-cgi есть похожие строчки:
                                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                                          • 0
                                                                            С ужасом сейчас обнаружил, что в дефолтном php-правиле у cherokee таки есть эта уязвимость.
                                                                            «Починил», добавив к выполнению *.php файлов условие File Exists :) Теперь нормально.
                                                                            • 0
                                                                              Немного апну тему :)
                                                                              Не работает на свежей связке nginx + php-fpm, так как в конфиге php-fpm есть строчка
                                                                              ;security.limit_extensions = .php .php3 .php4 .php5
                                                                              Которая не даст выполнить другие файлы.
                                                                              • +1
                                                                                На всякий случай уточню, что не работает не из-за наличия вышеупомянутой строчки (";" в начале означает, что строка закомментирована), а из-за применения самого подхода с белым списком в конфиге — по умолчанию разрешено только расширение .php, и чтобы снова стать уязвимым к описанному в топике, нужно ручками добавить новое расширение и перезапустить php5-fpm :)

                                                                                Как это сейчас выглядит для конфига в php 5.4.23
                                                                                ; Limits the extensions of the main script FPM will allow to parse. This can
                                                                                ; prevent configuration mistakes on the web server side. You should only limit
                                                                                ; FPM to .php extensions to prevent malicious users to use other extensions to
                                                                                ; exectute php code.
                                                                                ; Note: set an empty value to allow all extensions.
                                                                                ; Default Value: .php
                                                                                ;security.limit_extensions = .php .php3 .php4 .php5

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