Пользователь
59,6
рейтинг
4 августа 2012 в 09:25

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

Один французский «исследователь безопасности» этим летом опубликовал невиданно много найденных им уязвимостей типа 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, чтобы удалить ненужные метаданные и непрошенные приписки в конце файла.
@tyomitch
карма
315,7
рейтинг 59,6
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (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
        • 0
          Я всегда при загрузке картинок вписываю картинку в рамку, то есть я меняю размер картинки через GD и сохраняю в jpg всякие фотки, аватарки и т.д.
    • +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
          Идею уже доносили до вас.
          • 0
            Ну в таком случае она еще больше укрепится в сознаии обывателей
            • +1
              Надеюсь, наоборот.
              • –1
                Ну ок, дело ваше
    • +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. Есть ли в этом случае возможность выполнять код загруженной картинки?

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

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