Ваш сайт тоже позволяет заливать всё подряд?

    Один французский «исследователь безопасности» этим летом опубликовал невиданно много найденных им уязвимостей типа arbitrary file upload в разных «написанных на коленке», но популярных CMS и плагинах к ним. Удивительно, как беспечны бывают создатели и администраторы небольших форумов, блогов и интернет-магазинчиков. Как правило, в каталоге, куда загружаются аватары, резюме, смайлики и прочие ресурсы, которые пользователь может загружать на сайт — разрешено выполнение кода PHP; а значит, загрузка PHP-скрипта под видом картинки позволит злоумышленнику выполнять на сервере произвольный код.

    Выполнение кода с правами apache — это, конечно, не полный контроль над сервером, но не стоит недооценивать открывающиеся злоумышленнику возможности: он получает полный доступ ко всем скриптам и конфигурационным файлам сайта и через них — к используемым БД; он может рассылать от вашего имени спам, захостить у вас какой-нибудь незаконный контент, тем подставив вас под абузы; может, найдя параметры привязки к платёжной системе, отрефандить все заказы и оставить вас без дохода за весь последний месяц. Обидно, правда?

    Как ему это удастся?

    Вы не проверяете загружаемые файлы вообще?


    Клинический случай: вы скопировали из мануала по PHP строчку
    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
    и так её и оставили в продакшн-коде.

    Так я могу заливать вообще любые файлы, причём не только в каталог upload, а куда угодно — могу указать в качестве имени файла ../index.html и заменить вашу главную страницу своей. Обидно, правда?

    Добавьте хотя бы вызов basename() для $_FILES["file"]["name"]. В последней версии PHP, судя по моим экспериментам, $_FILES["file"]["name"] и так возвращает только basename от переданного в HTTP-запросе значения filename; но осторожность тут лишней не будет.

    Вы проверяете, является ли файл картинкой?


    Перед тем, как сохранить загруженный файл в upload/, вы вызываете getimagesize(), чтоб убедиться, что загружена именно картинка. К превеликому удовольствию «исследователей безопасности», PHP позволяет вставлять выполнимый код в любое место любого файла, игнорируя всё, что не заключено в теги <? ?>. Я могу взять фотографию своего любимого котика, дописать в её конец что-нибудь навроде <?php passthru($_GET['c']); ?> и залить под именем pwn.php. Тогда getimagesize() подтвердит вам, что в файле JPEG-картинка, потому что анализирует только заголовки; и моя «фотография с припиской» пройдёт проверку.

    Вы проверяете mime-type?


    Почему нельзя доверять полю «mime», возвращаемому getimagesize(), понятно: оно берётся на основании заголовка файла. Тем более нельзя доверять $_FILES["file"]["type"] — ничто не мешает мне передать в HTTP-запросе «Content-Type: image/jpeg» перед PHP-скриптом. Это кажется банальным, но люди действительно на это полагаются. Вот я только что видел проверку if(substr($_FILES["file"]["type"], 0, 6)=="image/") {/* сохранить файл */} в одном самоуверенном проекте. Они, поди, считают изящным и хитроумным то, что смогли одной проверкой покрыть все возможные типы картинок! Но не стоит у них заимствовать этот «передовой опыт».

    Вы проверяете расширение?


    В другом самоуверенном проекте я только что видел проверку
    if(substr($_FILES["file"]["name"], -3)=="jpg" || substr($_FILES["file"]["name"], -3)=="gif" || substr($_FILES["file"]["name"], -3)=="png") {/* сохранить файл */}
    Особенность Apache — что когда он встречает файлы с незнакомыми расширениями, он эти расширения «откусывает» и ищет знакомые расширения перед ними. Поэтому если я залью файл pwn.php.omgif, то он пройдёт проверку, но Apache увидит в нём PHP-скрипт, потому что для расширения omgif не зарегистрирован ни mime-type, ни модуль-обработчик.

    Вы проверяете «нехорошие» расширения?


    В третьем самоуверенном проекте я видел проверку if(strpos($_FILES["file"]["name"], ".php")===FALSE) {/* сохранить файл */} — мол, если в имени файла хоть где-нибудь встретилось расширение .php, значит файл не годится. Но не надо забывать, что по умолчанию интерпретатор PHP вызывается ещё и для расширений .phtml и .php3, а некоторые хостеры включают его и для других расширений — порой даже для .html, .js, .jpg и так далее, чтобы вставлять в статические ресурсы какой-нибудь SEO-код, или отслеживать статистику хитов. (Можете сами проверить, сколько гугл находит вопросов «как мне сделать, чтоб выполнялся PHP-код в JPEG-файлах?») Кроме того, если злоумышленнику удастся загрузить свой .htaccess, то он сможет исполнять PHP-код в файлах с любыми расширениями — даже в самом этом файле .htaccess. В общем, будьте бдительны.

    Вы проверяете расширение правильно?


    Предположим, вы убедились, что имя файла заканчивается на .jpg (вместе с точкой!) Можно ли быть уверенным, что PHP-код в нём не будет выполняться? Учитывая предыдущий пункт — скорее всего можно, но гарантии нет. Может случиться, что злоумышленник (или криворукий админ) как-то повредил /etc/mime.types, так что расширение .jpg окажется незарегистрированным, и Apache станет анализировать расширение перед ним. Может случиться, что /etc/mime.types стал недоступен из-за chroot. Может случиться, что злоумышленник (или криворукий админ) забросил в каталог upload/ такой .htaccess, который позволяет выполнять PHP-код во всех файлах подряд.

    Кроме того, до недавних версий PHP можно было загрузить файл с именем «pwn.php\0.jpg», который при сохранении на диск превращался бы в «pwn.php», потому что для системных вызовов строка с именем файла заканчивается на \0, а для функций PHP это обычный допустимый в строке символ. В таком случае проверка расширения тоже ничего не гарантировала бы.

    И что же делать?


    Исчерпывающий чеклист попытались составить на stackoverflow. Если коротко: а) запретите явно выполнение PHP в каталоге, куда сохраняются пользовательские файлы; б) переименовывайте пользовательские файлы в неподконтрольные пользователям имена; в) если возможно, пересохраняйте картинки при помощи GD, чтобы удалить ненужные метаданные и непрошенные приписки в конце файла.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 120
    • +4
      Самое смешное, что «знакомые расширения» апач тоже «откусывает».
      phpfaq.ru/apache/blah.php.jpg — дефолтные настройки.

      Поэтому рекомендуется заключать подключение PHP в контейнер
      • +9
        Упс, тег съелся.
        В контейнер FilesMatch

        <FilesMatch \.php$>
        AddType application/x-httpd-php .php
        </FilesMatch>

        Это отобьёт Апачу охоту умничать при определении MIME-типа:
        phpfaq.ru/good/blah.php.jpg
        phpfaq.ru/good/blah.php
        • +3
          Мне понравилась формулировка «отобьёт Апачу охоту умничать...»
          • 0
            Лучше всё же так
            <FilesMatch "\.php$">
            	SetHandler application/x-httpd-php
            </FilesMatch>

            Чтобы уж не бояться случайного пропуска FilesMatch, без которого AddType превращается в способ выполнения каких угодно файлов как PHP скриптов, содержащих ".php" в любом месте имени.
        • –8
          Я проверяю MIME вот так.

          /**
          * Метод определяет MIME-тип файла.
          * @todo move to class_mime
          * link www.iana.org/assignments/media-types/index.html
          * return
          * bool false в случае, если файл не существует.
          * str MIME-тип файла.
          */
          static public function get_mime($filename){
          if( is_file($filename) == true ){
          $finfo = finfo_open(FILEINFO_MIME_TYPE, '/usr/share/file/magic.mgc');
          $mimetype = finfo_file($finfo, $filename);
          if( $mimetype === false )
          return false;
          finfo_close($finfo);
          return $mimetype;
          }else{
          return false;
          }
          }
          • +3
            Есть тег source на Хабре…
            • +1
              И как это поможет против картинки с приписанным PHP-кодом в конце файла или в метаданных?
              • +10
                if( is_file($filename) == true ) — мощно сказано
                • +31
                  Вот так же надо!
                  if ( (is_file($filename) == true) == true)
                  
                  • –6
                    Вообще-то is_file — это проверка аналогичная file_exists.

                    $filename — это абсолютный путь к файлу. Мало ли как называется переменная $filename или $path_to_file.

                    is_file — Возвращает TRUE, если файл существует и является обычным файлом, иначе возвращает FALSE.

                    • 0
                      спасибо кэп, только зачем bool сравнивать с bool?)
                      • 0
                        еще маленькая поправка — функция is_file проверяет существование файла и возможность выполнения с ним операций чтения/записи. Так же результат кешируется. Функция file_exists — просто проверяет наличие файла и, по-моему, директории.

                        А что значит «и является обычным файлом»? какие файлы еще бывают?
                        • 0
                          какие файлы еще бывают?

                          директории, блочные и символьные устройства, возможно еще какие-то варианты есть.
                          • –2
                            «is_file — Возвращает TRUE, если файл существует и является обычным файлом, иначе возвращает FALSE.» — это из официальной русской документации с php.net.

                            Есть ещё is_link и is_dir — ссылка и директория, это ведь тоже файлы, исходя из этого контекста используется словосочетание «обычным файлом».

                            SILENTNUKE — причём тут моя стилистика написания кода, хочу и указываю явно сравнение с bool. Или ты типичный ХАБРАТРОЛЛЬ? Здесь идёт обсуждение конкретной темы, а мой рецепт дополнение к этой конкретной теме, к определению MIME. Или у тебя туалета дома нет и ты тут решил сходить.
                            • 0
                              Не стоит так ругаться. Стилистика тут не при чём. Оператор if работает с булевыми выражениями, а is_file возвращает именно его, и поэтому дополнительное сравнение является избыточным.

                              Если тебе указали на ошибку, то лучше попросить объяснить, чем отругиваться.
                              • –3
                                Это не ошибка! Ну насмешил «Если тебе указали на ошибку». У меня стилистика явно указывать сравнение с boolean!
                                Когда ты столкнёшься с большим проектом и с «неуловимой» проблемой в коде, что я опишу ниже, ты начнёшь делать также, как и я, то есть явно сравнивать.
                                И если вы, никогда не работали с проектом, в котором 1 000 000 строк исполняемого PHP кода, то вам не понять мой подход явного указания сравнения.

                                В моём рецепте функция finfo_file($finfo, $filename) может вернуть mixed значение: строку или boolean. И в этом случае понятно, что всё что не false будет восприниматься как true,
                                поэтому можно записать if( !$mimetype ).

                                Но бывают такие функции, которые возвращают значения: true (строка, число, массив), false или null.

                                True, строка, число, массив — может означать, что функция отработала успешно и вернула какой-то результат.

                                False — будет означать то, что произошла ошибка.

                                Null — будет означать, что функция отработала верно, но вернула пустоту.

                                И с таким раскладом True/False/Null в PHP есть ряд функций, да и в собственных тоже такое бывает. И чтобы мне не заморачиваться, я выбрал именно правильный для меня подход, писать всегда явно определение true, false и null.

                                if( !my_function() ){

                                // Код, который нужно выполнить только в случае ошибки.
                                // Но, если функция отработала корректно и
                                // вернула пустой результат Null, то мы также окажемся здесь. А мне этого не нужно!

                                }

                                // Поэтому местным троллям в случае описываемой ситуации, также придётся писать явное сравнение.

                                if( !my_function() ){

                                // Код, который нужно выполнить только в случае ошибки.

                                }else if( my_function() == null ){

                                // Код, который нужно выполнить только при пустом значении.

                                }

                                // Но так как, у меня своя стилистика, богатый опыт и профессиональный подход, я пишу всегда явно.
                                // Теперь вы понимаете тролли? И тут тролли закричали в один голос: «Прости нас идиотов...»

                                if( my_function() == false ){

                                // Код, который нужно выполнить только в случае ошибки.

                                }else if( my_function() == null ){

                                // Код, который нужно выполнить только при пустом значении.

                                }

                                Я очень рад за местных многоуважаемых троллей и за то, что любимый PHP позволяет опускать boolean сравнение в if.

                                Но мне нравится, так как я пишу, потому что застрахован от таких нелепых ошибок! И не нужно отнимать моё время на пустой трёп, чтобы доказывать троллям, что они тролли.

                                Вместо того, чтобы обсирать мой код, лучше бы дополнили статью полезными материалами, давая какие-то решения.
                                • 0
                                  Я очень рад за местных многоуважаемых троллей и за то, что любимый PHP позволяет опускать boolean сравнение в if.


                                  Дело не в вашем любимом PHP. Это программирование и концепция типов данных (см. Никлаус Вирт «Алгоритмы и структуры данных»)

                                  Но мне нравится, так как я пишу, потому что застрахован от таких нелепых ошибок! И не нужно отнимать моё время на пустой трёп, чтобы доказывать троллям, что они тролли.
                                  Не застрахованы. Сравните смысл равенства == и эквиваленции === в вашем любимом языке. И заодно про приведение типов.

                                  // Но так как, у меня своя стилистика, богатый опыт и профессиональный подход, я пишу всегда явно.
                                  // Теперь вы понимаете тролли? И тут тролли закричали в один голос: «Прости нас идиотов...»

                                  Мне вас жаль. И вашу учительницу информатики.

                                  Не шучу. И не троллю.
                                  • –4
                                    Поправлюсь, пока тролли спят.

                                    if( my_function() === false ){

                                    // Код, который нужно выполнить только в случае ошибки.

                                    }else if( my_function() === null ){

                                    // Код, который нужно выполнить только при пустом значении.

                                    }

                                    Буду краток и откровенен.

                                    Мне нелегко об этом писать, но правда заключается в том, что таких гениальных и талантливых людей, как Я — единицы. Нас всегда ущемляют в правах, пытаются высмеять и вытеснить, серая масса люмпенов программистов, да и просто бездарные люди. Вот и на Хабре, тот самый случай, я могу постить комментарии 1 раз в час. Ах, какое не справедливое сообщество. Ладно, пошёл себе арбуз рубану.
                                    • +1
                                      Сообщите, когда будет готова принципиально новая ОС.
                                    • 0
                                      И на каждом сравнении будет вызываться my_function()?
                                      Страшно представить, что у вас там за проекты на 1кк строк… Талантливый вы наш и гениальный…
                      • +21
                        британские ученые отдыхают. неделя юного кодоиспытателя на хабре.
                        • +2
                          И как раз на том же Stackoverflow дали нормальный ответ — прогонять все фотографии через GD или Imagemagick.
                          • 0
                            Вас, возможно, шокирует, что PHP-код можно вставить и в данные самого изображения, и тогда этот код выживет пересохранение в тот же формат?
                            • –4
                              Пересохранение.
                              • +4
                                Обработка с помощью GD убьёт любые посторонние данные по умолчанию, а Imagemagick-у надо при конвертации об этом специально сказать. Впрочем, я не возьмусь назвать конкретные ключи, которые в этом случае надо применить — кроме EXIF-а наверняка есть ещё лазейки.
                            • +1
                              Мне всегда казалось, он jpeg пережимает, и поэтому непригоден. Я ошибся?
                            • +5
                              Допустим я сохранил PHP-файл на сервере с именем sun.png
                              Где тут уязвимость?
                              • 0
                                php — injection
                                PS. Или в паре с некоторыми другими типами узвимостей.
                                • +1
                                  У меня(как и у большинства программистов) не может быть ошибки типа php — injection в коде, т.к. в нем нет ни одного параметризированного require/include(_once).

                                  И, в любом случае, ошибка была бы не в аплоаде sun.png, а в «php — injection».
                                  • +2
                                    Конкретно этой ошибки у Вас может не быть, но может быть неправильная настройка сервисов администратором, может быть ошибка в стороннем ПО.
                                    Количество различных вариаций уязвимостей ограничивается лишь фантазией злоумышленника, и то что он смог загрузить вредоносный код, пусть пока и без возможности запустить его, явно не достижение.
                                    PS. Лучше сбить свой самолет, чем пропустить вражеский.
                                    • 0
                                      Например, habrahabr.ru/post/100961/
                                • +9
                                  ИМХО бороться таки нужно с сыростью, а не плесенью. А сырость в том, что Apache неадекватно реагирует на определенные имена файлов.

                                  Вообще, apache — достаточно слабый способ отдавать статику, lighttpd или nginx делают это гораздо лучше. Таким образом, если отдавать статику через nginx (без настроенных обработчиков CGI), то заливка любого файла оставит систему в целости и сохранности, будь у вас там многоэтажные проверки или нет.

                                  Хотя все же даже с nginx-ом следует проверить файл, но уже просто для удобства пользователя. Чтобы другие ваши посетители видели целую и валидную картинку. При этом можно держать градус паранойи на низком уровне, т.к. система в любом случае в безопасна.
                                  • 0
                                    По большой счёту решение сводится к:
                                    • не использовать голый AddType для задания обработчика скриптов;
                                    • никогда не доверять user input;
                                    • явно отключать выполнение скриптов для директорий со статикой.


                                    Вообще, apache — достаточно слабый способ отдавать статику, lighttpd или nginx делают это гораздо лучше

                                    Непонятно только, причём здесь описанные в посте уязвимости.
                                    • +1
                                      При том, что 99% статьи является workaround-ом против фундаментального недостатка: в папке со статикой тем или иным способом может быть выполнен PHP скрипт. Далее, основываясь на всего лишь этом одном недостатке перечислел 101 способ его проэксплуатировать и описано 202 проверки, противоборствующие этому 101 способу.

                                      Устраните всего лишь один этот фундаментальный недостаток при помощи того же nginx — и вся статья теряет смысл.
                                  • +2
                                    Храню файлы с именем в виде хэша а всю информацию (имя файла, mime etc) отдельно, очень удобно.
                                    • –8
                                      Что такое самоуверенный проект? Почему это словосочетание встречается в статье несколько раз? Где названия и адреса репозиториев этих самоуверенных проектов?
                                      • +4
                                        У меня сложилось впечатление, что автор прочитал что-то по безопасности и написал статью про «запретить все», основываясь на:
                                        Может случиться, что злоумышленник (или криворукий админ) как-то повредил /etc/mime.types, так что расширение .jpg окажется незарегистрированным

                                        но при этом не понимает, что если злоумышленник получил доступ на уровне системы, он и так может все сделать.

                                        Но возникает другой вопрос: почему у статьи положительный рейтинг?

                                        Повторю свой вопрос: файл sun.jpg содержит <?php passthru($_GET['c']) ?>. Расширение верное, тип mime верный. Где уязвимость?
                                        Где тут уязвимость?
                                        • 0
                                          промахнулся комментарием
                                          habrahabr.ru/post/148999/#comment_5034779
                                          • 0
                                            если злоумышленник получил доступ на уровне системы, он и так может все сделать

                                            Например, случаются уязвимости, дающие возможность удалять (но не создавать или изменять) произвольные файлы. (Предположим, эта уязвимость в каком-нибудь совсем другом углу сервера, не относящемся к уязвимому веб-сайту.)
                                            Без загрузки image.php.jpg это лишь бессмысленный и беспощадный DoS, а с ним — выполнение кода.
                                            • 0
                                              Пример приведите.
                                              В данном случае нужны права администратора к тому, что вы говорите. Вебсервер их иметь не может.
                                              • 0
                                                Видимо, вы плохо осведомлены, как некоторые личности настраивают свои сервера…
                                          • 0
                                            А не проще ли вообще сделать доступ к картинкам через отдельный скрипт и закрыть прямой доступ к папке с ними? Тогда никто и не сможет выполнить их.
                                            • +2
                                              Нагрузка на сервер появится дополнительная, а смысла нет (никто картинки выполнить и так не может!).
                                              • 0
                                                Ну да. Не может:
                                                Но не надо забывать, что по умолчанию интерпретатор PHP вызывается ещё и для расширений .phtml и .php3, а некоторые хостеры включают его и для других расширений — порой даже для .html, .js, .jpg и так далее, чтобы вставлять в статические ресурсы какой-нибудь SEO-код, или отслеживать статистику хитов.

                                                Черным по белому сказано, что при большей везучести можно выполнить все. Инъекция так делается: в изображение вставляется код и его отследить становится сложнее.
                                                А нагрузка на сервер мизерная получается по-сравнению с пользой от такого метода.
                                                • 0
                                                  Я бы не стал так утверждать, про нагрузку.
                                                  Тем более, что в статье есть более простое решение — запретить исполнение скриптов в этой папке.
                                                  • 0
                                                    > а некоторые хостеры включают его и для других расширений — порой даже для .html, .js, .jpg и так далее, чтобы вставлять в статические ресурсы какой-нибудь SEO-код, или отслеживать статистику хитов.

                                                    Вы это видели?
                                                    Скорее всего вы не заметили .htaccess с рерайтом или добавлением обработчика по расширением.
                                                    В любом случае это будет ошибка не в загрузке, а в настройке сервера.
                                                  • +1
                                                    При проблемах с нагрузкой вероятнее всего вы захотите загружать картинки на отдельный сервер, где стоит nginx, отдающий только статику, без возможности выполнения скриптов. Если отдельного сервера нет-то нет и проблем с нагрузкой ;).
                                                    • 0
                                                      В любом случае вы будете сначала задавать динамикой оригинальное имя файла(из базы брать), а только потом отдавать файл статикой.
                                                      Либо потеряете имя файла.
                                                  • 0
                                                    Этот отдельный скрипт тоже частенько бывает решетом без basename().

                                                    pic.php?img=../../../etc/passwd
                                                    • 0
                                                      Ну само собой, прямые руки и мысли головой никто не отменял :)
                                                      К тому же можно так:
                                                      file.php?name=image&ext=jpg
                                                      и удалять из имени файла парные точки
                                                      • +3
                                                        Вот проблема ( ваша и OnYourLips ) как раз в том, что вы отказываетесь действовать по принципу белого списка (запрещено всё, что не разрешено) и предпочитаете действовать по принципу списка чёрного — разрешено всё, что не запрешено.
                                                        Но чёрный список заведомо дырявее белого. Всё предусмотреть невозможно. И, скажем, добраться до какого-нибудь файла с паролями можно будет и безо всяких точек.
                                                        • 0
                                                          Я бы сделал другое сравнение.
                                                          Мы готовы исключить ошибки и предпосылки к их возникновению, а не делать результаты ошибок незначительными.

                                                          Если в нашем коде не может появиться php-include, то и exec в EXIF фотки нам не навредит.
                                                          • 0
                                                            Безопасность во многом зависит от администраторов, а не разработчиков. В вашем коде нет места php-include, но настройки сервера позволяют запустить файл в upload, включая чтение файлов движка или его конфигов. Ваш код будет идеален, но злоумышленник получит возможность, например, доступа к БД с правами вашего кода. Кстати, отчасти поэтому я советую заказчикам взять shared хостинг или нанимать админа, а не берусь настроить им vds. Разве что сильно настаивают когда занимаюсь администрированием.
                                                            • 0
                                                              Или есть риск потерять заказ :(
                                                              • 0
                                                                Разве от администратора зависит наличие или отсутствие правильного .htaccess в папке для загрузки файлов, а не от разработчика популярной самописной CMS?
                                                        • 0
                                                          При сохранении всегда использую ID изображения в базе и при открытии по ID делаю intval.
                                                      • 0
                                                        Ну, как минимум следует учитывать возможность инклюда с локального диска.
                                                        Понятно, что инклюды надо защищать отдельно, но программисты обычно не слишком заботятся о безопасности внутренних инклюдов. А проверку на open_basedir картинка пройдёт спокойно.

                                                        Опять же, sun.php.jpg оказывается куда более разрушительным. Так что не все рекомендации в статье одинаково бесполезны.
                                                        • 0
                                                          > Ну, как минимум следует учитывать возможность инклюда с локального диска.
                                                          Каким образом?
                                                          Чтобы что-то заинклюдить другим файлом, злоумышленник должен загрузить тот другой файл?

                                                          > Понятно, что инклюды надо защищать отдельно, но программисты обычно не слишком заботятся о безопасности внутренних инклюдов.
                                                          Я просто не представляю ситуацию, когда мне надо будет подключать php-файл основываясь на значении внешней переменной. Так что это будут проблемы архитектуры.

                                                          > Опять же, sun.php.jpg оказывается куда более разрушительным.
                                                          Чем же? Обоснуйте.
                                                          • 0
                                                            Я же пример привел, работающий. В самом первом комментарии к топику.

                                                            Собственно, практически любой фреймфорк запускает PHP файлы в зависимости от пользовательского ввода. Очень часто части запрошенного урла совпадают с названиями файлов/методов имеющихся классов.
                                                            А фильтрация всего этого дела остаётся на совести программиста.
                                                            • +1
                                                              Посмотрите пожалуйста на код роутеров в современных фреймворках. Там нет мест, где можно было бы допустить такую ошибку.

                                                              Если программист допускает ошибку типа инклюда локального файла, то он определенно новичок, а ошибка в инклюде, а не в загрузке.
                                                        • +1
                                                          Многие из описанных вами методов уже давно обходятся при наличии интеллекта и желания.
                                                          • +4
                                                            Поведайте, будем рады!
                                                            • –1
                                                              это альтернативные способы, есть еще и приватные, но о них мало кто говорит. Я бы хотел дополнить, что приведенные вами примеры эффективны только при использовании их в качестве целого элемента системы, т.е. если использовать их не в комплексе, то толку от них мало.
                                                              • –1
                                                                Хабр съел ссылку _http://www.youtube.com/watch?v=2WCGyQ4ldC0 А по поводу более скрытого я имел в виду например ограничение на длину имени файла и альтернативу null байта. Но все зависит от версии PHP
                                                          • +2
                                                            Добавлю:
                                                            в пакетах libapache2-mod-php5 и mime-support для Debian (возможно, и для Ubuntu) по умолчанию используется потенциально небезопасная конфигурация.

                                                            В /etc/mime.types для application/x-httpd-php прописаны расширения .php .phtml .pht (!!!)
                                                            При этом в /etc/apache2/mods-available/php5.conf есть строчка
                                                            <FilesMatch "\.ph(p3?|tml)$">
                                                            

                                                            беда которой в том, что она создает чувство ложной защищенности, т.к. с такими mime.types все работает и без нее.
                                                            И, вероятно, не совсем так, как вы ожидаете — как насчет файла с замечательным именем «hello.pht.en»?

                                                            Эта «черная магия» отключается дописыванием строки
                                                            RemoveType .php .pht .phtml .php3 .php4 .php5
                                                            

                                                            все в тот же php5.conf.

                                                          • 0
                                                            >> В последней версии PHP, судя по моим экспериментам, $_FILES[«file»][«name»] и так возвращает только basename от переданного в HTTP-запросе значения filename; но осторожность тут лишней не будет.
                                                            Про лишнюю осторожность бред конечно, а вот до какой версии оно возвращало не basename?
                                                            • –7
                                                              можно ещё анализировать содержимое файла, например так:

                                                              $content = file_get_contents ( 'загруженный файл' );
                                                              
                                                              if ( stristr( $content, '<?' ) OR stristr ( $content, '<%' ))
                                                              {
                                                                   die();
                                                              }
                                                              
                                                              • 0
                                                                Отлично подходит для ситуаций, когда пару десятков пользователей в параллели будут загружать большие картинки. )
                                                                • –2
                                                                  пример накидал самый простейший, можно оптимизировать
                                                                  • +1
                                                                    Хорошо, предположим оптимизировали. А как быть с бинарными файлами, которые вполне могут содержать эти сочетания байт? Вероятность появления сочетания двух определённых байтов — 1/2^8^2, то есть в среднем одно такое сочетание на файл размером 64Ки.
                                                                • +6
                                                                  Два правила начинающему комментатору:
                                                                  1. Если эта идея прямо сейчас пришла вам в голову, то надо сначала проверить — насколько она применима в реальной жизни.
                                                                  2. Если вы к этой идее не имеете вообще никакого отношения, а просто повторяете за кем-то — тем более надо сначала проверить — насколько она применима в реальной жизни. Чтобы не быть одним из миллионов разносчиков мусора.

                                                                  В качестве домашнего задания попробуйте поискать эти сочетания в любой мало-мальски объемной коллекции заведомо безопасных картинок.
                                                                  • 0
                                                                    Если картинка не прошла, значит в ней порно, очевидно же.

                                                                    Удивляет другое, почему вдруг люди решили, что имеют право так поступать с чужими данными, которые они обязаны оставлять в неприкосновенности?
                                                                    • 0
                                                                      Чужие или не чужие, обязаны или не обязаны — зависит лишь от TOS.
                                                                      • 0
                                                                        Не понял, как именно поступать?
                                                                        Он же никак эти данные не портит, а просто предлагает не принимать.
                                                                        Сайт и не обязан принимать всё подряд.
                                                                        Смысла я в такой проверке не вижу, но и никаких проблем с пользовательскими данными — тоже
                                                                        • –1
                                                                          Данные на входе принимать как есть, на выходе фильтровать — стандартная политика безопасности.
                                                                          Файлы хранить в спец папке запрещённой на обработку апачем, опцинально менять некоторые расширения на безопасные.

                                                                          А то потом звонит заказчик и говорит, ваш сайт не работает, файлы не грузятся, клиенты жалуются.
                                                                          И вы начинаете гадать на кофейной гуще, какие именно файлы не были приняты и почему.
                                                                          • 0
                                                                            У вас все в кучу перемешалось.
                                                                            Я не предлагаю делать эту тупую проверку на "<?".
                                                                            Эта проверка сама по себе дурацкая и не имеет смысла.

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

                                                                            Скажем, есть ограничение на минимальный размер заливаемых картинок. Если я не принимаю картинку размером 32х32, то никакого надругательства над пользовательскими данными я не совершаю.

                                                                            А чтобы заказчики не звонили, надо просто выдавать внятные сообщения об ошибках и собирать их в отчет, выдаваемый по запросу.
                                                                            А чтобы на кофейной гуще не гадать — нужно обязательно логировать весь процесс.
                                                                        • 0
                                                                          Кому я что обязан на своём сайте?
                                                                      • 0
                                                                        Как раз проверял написаное в статье на своем хомяке — открыл jpg-шку с подсветкой синтаксиса php (Notepad++) и штуки три <? нашлось.
                                                                      • 0
                                                                        была уже тема на хабре.

                                                                        1) отключить выполнение php в папке с хранением картинок.
                                                                        2) рандомные имена и пути + расширение присваивается сервером.
                                                                        • 0
                                                                          > рандомные имена и пути + расширение присваивается сервером.
                                                                          Придется отдавать скриптом, чтобы подставлять правильные имена.
                                                                          Нагрузка возрастает на порядок (ранее было несколько запросов к приложению(основная страница и блоки-аяксы) и куча запросов к статике, а вы предлагаете отдавать статику динамически).
                                                                          • НЛО прилетело и опубликовало эту надпись здесь
                                                                            • 0
                                                                              Вы предлагаете отдавать <img src="/1/12/1234567890.jpg"> в странице?
                                                                              А с точки зрения SEO это разве не минус?
                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                • 0
                                                                                  Какое дело SEOшникам для имён статических файлов?
                                                                                  • 0
                                                                                    Я думаю, что примерно такое же, как и до ЧПУ.
                                                                                    • +1
                                                                                      Я страшно за них переживаю.

                                                                                      А теперь уточню вопрос, в рекомендациях от поисковиков есть что-то на эту тему?

                                                                                      Главный минус, это когда загруженный на сервер файл «прайс за 31 июня.xls» после скачивания сохраняется с непонятно каким именем.
                                                                                      Как он там хранится — не особо важно.
                                                                              • 0
                                                                                рандомные пути… наверное не точно выразился… не скрипт запрета нахождения файла физически.ДОстатчно будет просто рандомного имени + расширение от сервера + запрет выполнения php в папке и достаточно.
                                                                                • 0
                                                                                  >Придется отдавать скриптом, чтобы подставлять правильные имена.
                                                                                  Необязательно, в большинстве CMS в БД хранится этот рандомный адрес и указывается сразу в ссылке на файл.
                                                                              • 0
                                                                                Очень часто при загрузке картинок нужен их ресайз. Так что если он не удался по понятным причинам, то трём файл.
                                                                                • +3
                                                                                  Тут уже упоминалось, что ресайз ресайзу рознь.
                                                                                  Многие современные методы обработки картинок бережно сохраняют имеющиеся в файле метаданные.
                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                  • 0
                                                                                    AFAIK, 'это mod_mime.
                                                                                    • 0
                                                                                      mod_negotiation тоже поминали, в виде file.php.en
                                                                                  • +1
                                                                                    Вообще не вижу проблемы — вынести все в отдельную папку. В идеале — раздавать с отдельного домена (пусть даже поддомена) с помоью nginx.
                                                                                    Если хостинг совсем плохой и есть только апач то отрегистрировать все расширения, убрать возможности запуска любых скриптов и SSI для файлов в этой папке.
                                                                                    • –5
                                                                                      if (preg_match('#\<\?.*\?\>#si',file_get_contents($_FILES['bablabla']))) die('oops');
                                                                                      А вот уж потом move_uploaded_file. У вас же там не гигабайтные файлы загружаются?
                                                                                      • 0
                                                                                        Ну а если они не гигабайтные, но сразу довольно приличное кол-во?
                                                                                        • –1
                                                                                          Ну я думаю прег-мач не сильно нагрузит
                                                                                          • 0
                                                                                            Ошибка — закрывающий тег не обязателен. Его убрать, открывающий оставить, а Ваш скрипт его пропустит. Всё, взломано.
                                                                                            • 0
                                                                                              Ну, идею я донес
                                                                                              • 0
                                                                                                Идею уже доносили до вас.
                                                                                                • 0
                                                                                                  Ну в таком случае она еще больше укрепится в сознаии обывателей
                                                                                          • +2
                                                                                            Какие цвета в картинках вы предлагаете запретить?
                                                                                          • +1
                                                                                            Куда грустнее, когда есть CMS (или блог, или еще какой _готовый_ софт), к нему понаустанавливали модулей — и это все надо защитить. Аудит кода устраивать не самый лучший вариант. Аудит настроек сервера — хорошо, если сервер наш, но ведь бывает и shared-хостинг, где сегодня вот так все настроено, а завтра что-то тихо ка-а-а-а-ак поменяется…

                                                                                            Я к тому, что порой проще (и надежнее) вспомнить про тот же cloudflare.com, чем грызть себе ногти, ругаясь на чужой код на своем сервере.
                                                                                            • 0
                                                                                              Можно убрать «недопустимые» символы в имени файла. То есть сразу убираем точки (кроме одной) и слэши из имени, а потом уже проверяем расширение. Обязательное пересохрание это изврат какой то. А если ворд документы пойдут или пдф, тоже пересохранять? :)
                                                                                              • +1
                                                                                                Достаточно часто встречаются ситуации когда люди пользуются какой либо функцией в фреймворке для описанных вами и совсем уж очевидных проверок, а сохраняют файл собственными силами и тогда оказывается, что при проверке делается trim (к примеру) а при сохранении вы его не делаете, что позволяет загрузить изображение с именем «file.jpg » и провернуть XSS для пользователей IE.
                                                                                                И совсем забыли об одной еще достаточно хитрой особенности nginx — нельзя допускать загрузки картинок без имени, только с расширением (например ".jpg"), т.к.:

                                                                                                anri@AKrasichkov:~$ curl -I http://localhost/test.gif | grep Content-Type
                                                                                                Content-Type: image/gif
                                                                                                


                                                                                                anri@AKrasichkov:~$ curl -I http://localhost/.gif | grep Content-Type
                                                                                                Content-Type: application/octet-stream
                                                                                                


                                                                                                Имхо, считаю статью из серии К.О.
                                                                                                Куда более интересней вопрос — правильная загрузка/отдача любых произвольных файлов, т.к. помимо изображений все чаще есть необходимость в загрузке тех же аудио/видео/офисный и иных файлов.
                                                                                                • 0
                                                                                                  К слову сказать, файлов «без имени» не существует. В данном случае вы продемонстрировали файл с
                                                                                                  именем ".jpg".

                                                                                                  А «хитрая» особенность nginx заключается в том, что в отличии от апача заголовок «Content-Type» устанавливается по расширению и абсолютно ни на что не влияет с точки зрения обработки запроса. И при отсутствии расширения берется из директивы «default_type» с безобидным по-умолчанию значением.
                                                                                                  • 0
                                                                                                    Да, вы правы фактически имя у файла ".jpg" просто как иначе «обозвать» подобные файлы я увы не придумал, посему счел уместным употребить именно такое обозначение. Возможно, говорить «файлы без названия» было бы более корректным.

                                                                                                    application/octet-stream безобидное? С чего бы вдруг? А как же mime-сниффинг в IE, включенный у абсолютного большинства его пользователей? Как раз по этой причине у меня обычно «default_type» и выставлен в force-download.
                                                                                                    • 0
                                                                                                      «default_type» не может быть выставлен в «force-download» — это делается другим заголовком.

                                                                                                      Что касается mime-сниффинга в IE то, если мне не изменяет память, он работает только если пользователь открывает файл прямой ссылкой, а не браузер загружает по тегу на странице, и когда он работает, то какой бы «Content-Type» вы не выставили — это не поможет.

                                                                                                      В общем же случае, если вы допускаете загрузку скриптов на ваш сервер — у вас уже проблемы, и рано или поздно кто-нибудь найдет способ это поэксплуатировать.
                                                                                                      • 0
                                                                                                        Хм, перепроверил — а вы правы, черт побери! Видимо я последние тесты проводил или со странной сборкой IE или еще что, в любой случае склоняю шляпу, век живи — век учисью.

                                                                                                        В общем же случае, если вы допускаете загрузку скриптов на ваш сервер — у вас уже проблемы, и рано или поздно кто-нибудь найдет способ это поэксплуатировать.

                                                                                                        А с этим никто и не спорит, вопрос в другом — как максимально обезопасить свой аплоад разработчикам которые не знают на какой конкретной конфигурации будет работать их код (разработчики CMS/CMF, модулей и т.д.). Вот и приходится искать все возможные «странности» и подстраиваться под них, как в моем примере с nginx + файл только с расширением. Люди ведь извращенцы, некоторые работают и на связке IIS+PHP. С позиции разработки проекта все прозрачно — используем CDN, другой домен для статики и т.д.
                                                                                                • –3
                                                                                                  Моё предложение по защите: http://bolknote.ru/2012/08/05/~3704.
                                                                                                • 0
                                                                                                  В ISPManager не так уж просто отключить PHP для image.php.jpg

                                                                                                  Но решение есть. Может пригодится:

                                                                                                  1. Создаем скрипт для вставки нужных инструкций в /etc/apache2/apache2.conf:

                                                                                                  disable-apache-php-vuln.pl
                                                                                                  #!/usr/bin/perl
                                                                                                  use strict;
                                                                                                  
                                                                                                  my $fn = "/etc/apache2/apache2.conf";
                                                                                                  
                                                                                                  open(FILE, '<', $fn);
                                                                                                  my $data = join("", <FILE>);
                                                                                                  close(FILE);
                                                                                                  
                                                                                                  $data =~ s|DocumentRoot\s+([^\s]+)|DocumentRoot \1
                                                                                                      <Directory \1>
                                                                                                          RemoveHandler .php .pht .phtml .php3 .php4 .php5
                                                                                                          RemoveType    .php .pht .phtml .php3 .php4 .php5
                                                                                                          <FilesMatch "\\.ph(p3?\|tml)\$">
                                                                                                              SetHandler application/x-httpd-php
                                                                                                          </FilesMatch>
                                                                                                      </Directory>|g;
                                                                                                  
                                                                                                  open(FILE, '>', $fn);
                                                                                                  print FILE $data;
                                                                                                  close(FILE);
                                                                                                  


                                                                                                  2. Делаем копию /etc/apache2/apache2.conf и запускаем
                                                                                                  perl disable-apache-php-vuln.pl
                                                                                                  

                                                                                                  3. Перезапускаем апач.

                                                                                                  4. Теперь добавим нужные инструкции для новых сайтов создав /usr/local/ispmgr/etc/virtualhost.templ с таким содержимым:
                                                                                                      DocumentRoot __DocumentRoot__
                                                                                                      <Directory __DocumentRoot__>
                                                                                                          RemoveHandler .php .pht .phtml .php3 .php4 .php5
                                                                                                          RemoveType    .php .pht .phtml .php3 .php4 .php5
                                                                                                          <FilesMatch "\.ph(p3?|tml)$">
                                                                                                              SetHandler application/x-httpd-php
                                                                                                          </FilesMatch>
                                                                                                      </Directory>
                                                                                                  

                                                                                                  5. Перезапускаем ispmgr:
                                                                                                  killall ispmgr

                                                                                                  Почему так сложно?
                                                                                                  Просто ispmanager не умеет по другому включать php для сайта. Только через
                                                                                                      AddType application/x-httpd-php .php .php3 .php4 .php5 .phtml
                                                                                                  

                                                                                                  И я не знаю как изменить это.
                                                                                                  • 0
                                                                                                    Прочитал статью, прочитал комменты, почесал репу. Не совсем понятно, что делает злоумышленник дальше, после того как загрузил файл с php кодом? Ведь этот файл еще надо как то выполнить.

                                                                                                    У меня допустим аплоадятся картинки и складываются в не-www директорию, и единственное, что с ними делается это file_get_contents и echo. Есть ли в этом случае возможность выполнять код загруженной картинки?

                                                                                                    И самое главное, статья с учетом комментариев так и не раскрыла тему защиты.

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