Лучшие практики и рекомендации по защите php-приложений от XSS-атак

    Лучшие практики и рекомендации по защите php-приложений от XSS-атак


    Создание функционирующего веб-приложения – это только полдела. Современные онлайн-сервисы и веб-приложения, помимо собственного контента, хранят данные пользователей. Защита этих данных зависит от правильно написанного кода с точки зрения надёжности и безопасности.



    Большинство уязвимостей связано с неправильной обработкой данных, получаемых извне, или недостаточно строгой их проверкой. Одной из таких уязвимостей является межсайтовое выполнение сценариев (Сross Site Sсriрting, XSS), которая может привести к дефейсу сайта, перенаправлению пользователя на зараженный ресурс, вставке в веб-ресурс вредоносного кода, краже COOKIE-файлов, сессии и прочей информации. Противостоять XSS своими сила поможет применение лучших практик и рекомендаций по безопасному программированию, о которых и пойдет речь ниже.

    Лучшие практики и рекомендации:

    1. Используйте экранирование входных\выходных данных. Применяйте встроенные функции для очистки кода от вредоносных скриптов. К ним относятся такие функции как htmlspecialchar(), htmlentities() и strip_tags().
    Примеры использования:

    $name = strip_tags($_POST['name']);
    $name = htmlentities($_POST['name'], ENT_QUOTES, "UTF-8");
    $name = htmlspecialchars($_POST['name'], ENT_QUOTES);

    Встроенные функции PHP, в отличие от самописных, работают гораздо быстрее, а также имеют меньше ошибок безопасности и уязвимостей, т.к. постоянно совершенствуются. Также рекомендуется использовать специальные библиотеки, построенные на основе встроенных функций и фильтров. В качестве примера можно привести OWASP Enterprise Security API (ESAPI), HTML Purifier, Reform, ModSecurity.
    Для того чтобы библиотека работала правильно, её нужно предварительно настроить!

    2. Используйте подход «белые списки». Подход работает по принципу «что не разрешено, то запрещено». Это стандартный механизм валидации полей для проверки всех входных данных, включая заголовки, куки, строки запросов, скрытые поля, а также длина полей форм, их тип, синтаксис, допустимые символы и другие правила, прежде чем принять данные, которые будут сохраненные и отображены на сайте. Например, если в поле нужно указать фамилию, необходимо разрешить только буквы, дефис и пробелы. Если отклонить все остальное, то фамилия д’Арк будет отклонена — лучше отклонить достоверную информацию, чем принять вредоносные данные.
    К сожалению, со своей задачей встроенные фильтры валидации данных PHP не справляются, поэтому рекомендуется писать собственные фильтры и «допиливать» их по мере необходимости. Таким образом, со временем ваши входные методы фильтрации будут усовершенствованы. Стоит также не забывать, что существует слишком много типов активного содержимого и способов кодирования для обхода подобных фильтров. По этой же причине не используйте проверку по «черному списку».

    3. Указывайте кодировку на каждой веб-странице. Для каждой веб-страницы необходимо указывать кодировку (например, ISO-8859-1 или UTF-8) до каких-либо пользовательских полей.
    Пример использования:

    <?php
    header("Content-Type: text/html; charset=utf-8");
    ?>
    <!DOCTYPE html>
    <html>
     <head>
       <title>Сharset</title>
       <meta charset="utf-8">
     </head>

    или в файле .htaccess веб-сервера Apache дописать строчку:

    AddDefaultCharset UTF-8

    Если в http-заголовке или в метатегах кодировка не указана, браузер пытается сам определить кодировку страницы. Стандарт HTML 5 не рекомендует использовать такие кодировки, которые включают JIS_C6226-1983, JIS_X0212-1990, HZ-GB-2312, JOHAB (Windows code page 1361), а также кодировки, основанные на ISO-2022 и EBCDIC. Кроме того, веб-разработчики не должны использовать CESU-8, UTF-7, BOCU-1 и кодировки SCSU. Эти кодировки никогда не предназначались для веб-контента[1]. В случае если тег расположен до тега и заполняется пользовательскими данными, злоумышленник может вставить вредоносный html-код в кодировке UTF-7, обойдя, таким образом, фильтрацию таких символов, как ‘<’ и ‘"’.

    4. Установить флаг HttpOnly. Этот Флаг делает клиентские куки недоступными через языки сценариев, такие как JavaScript.
    Данная настройка активируется
    — в php.ini [2]:

    session.cookie_httponly = True

    — в скрипте через функцию session_set_cookie_params() [3]:

    void session_set_cookie_params ( int $lifetime [, string $path [, string $domain [, bool $secure = false [, bool $httponly = true ]]]] )

    — в веб-приложении через функцию setcookie() [4]:

    bool setcookie ( string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, bool $secure = false [, bool $httponly = true ]]]]]] )

    Эта функция поддерживается последними версиями распространенных браузеров. Однако старые версии некоторых браузеров через XMLHttpRequest и другие мощные браузерные технологии обеспечивают доступ для чтения HTTP-заголовков, в том числе и заголовка Set-Cookie, в котором установлен флаг HttpOnly[5].

    5. Использовать Content Security Policy (CSP). Это заголовок, который позволяет в явном виде объявить «белый список» источников, с которых можно подгружать различные данные, например, JS, CSS, изображения и пр. Даже если злоумышленнику удастся внедрить скрипт в веб-страницу, он не выполниться, если не будет соответствовать разрешенному списку источников.
    Для того чтобы воспользоваться CSP, веб-приложение должно через HTTP-заголовок «Content-Security-Policy» посылать политику браузеру.
    Пример использования:

    Content-Security-Policy: default-src 'self';
     script-src trustedscripts.example.com
     style-src 'self' ajax.googleapis.com;
     connect-src 'self' https://api.myapp.com realtime.myapp.com:8080;
     media-src 'self' youtube.com;
     object-src media1.example.com media2.example.com *.cdn.example.com;
     frame-src 'self' youtube.com embed.ly

    'Content-Security-Policy' — это официальный http-заголовок, утвержденный W3C, который поддерживается браузерами Chrome 26+, Firefox 24+ и Safari 7+. HTTP-заголовок «X-Content-Security-Policy» используется для Firefox 4-23 и для IE 10-11, заголовок «X-Webkit-CSP» – для Chrome 14-25, Safari 5.1-7[6].

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

    6. Регулярно проводите анализ безопасности кода и тестирование на проникновение. Используйте как ручной, так и автоматизированный подходы. Такие инструменты как Nessus, Nikto и OWASP Zed Attack Proxy помогут обнаружить уязвимости XSS в вашем веб-приложении.

    7. Пользователям рекомендуется регулярно обновлять браузер до новой версии и использовать для них расширения, например, NoScript.
    Как вы могли заметить, у каждой рекомендации есть свои достоинства и недостатки, поэтому эффективность противодействия межсайтовому выполнению сценария достигается путем применения комплексной защиты, т.е. использования описанных рекомендаций в совокупности.

    Полезные ссылки:
    1. HTML Standart. The elements of HTML.
    2. PHP: Настройка во время выполнения.
    3. PHP: session_set_cookie_params.
    4. PHP: setcookie.
    5. OWASP HttpOnly.
    6. Can I use Content Security Policy.
    7. PHP Security Guide.
    8. 2011 CWE/SANS Top 25 Most Dangerous Software Errors. CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
    9. Introducing Content Security Policy.
    10. OWASP XSS.
    11. OWASP Top 10 2013-A3-Cross-Site Scripting (XSS).
    12. OWASP XSS Filter Evasion Cheat Sheet.

    Автор статьи: Внештатный сотрудник PentestIT, Сергей Сторчак
    Pentestit 324,21
    Информационная безопасность
    Поделиться публикацией
    Комментарии 35
    • +2
      Думаю, стоило бы добавить информацию и о CSRF.
      • 0
        Поправьте по тексту
        >> Используйте экранирование выходных данных
        входных
        • –1
          Спасибо, поправили!
          • +3
            Зачем? У вас правильно было написано. Форматировать (слово точнее, чем экранировать) надо и выходные данные тоже.
            Как раз те функции, которые описываются вами — strip_tags, htmlentities, htmlspecialchars — имеют больше отношения к форматированию выходных данных, чем входных.
            • 0
              Спасибо, заменил на «входных\выходных».
        • +2
          Стоит добавить о случаях, когда никакие экранирования/конвертации символов и т.п. вещи не помогут

          • Небезопасное использование JSONP. Использование кастомных callback'ов может привести не только к утечке данных, но и к XSS. Случай раз, два
          • Ссылки в href. Пользователь может подставить в тэг <a> ссылку вида javascript:func();, что может привести к XSS. В javascript: нет никаких спец. символов, которые преобразуются функциями для защиты от подобных атак
          • Скрипты редиректа. Схожий случай с пунктом выше. Редиректы через js, вида document.location=USER_VALUE также могут привести к XSS.
          • Если пользователь может задать свой кастомный CSS стиль — у него есть возможность выполнить произвольный JS (например, в IE 7)
          • 0
            Об остальном знал, кроме второго пункта — есть ли какие-то статьи, в которых говорится о том, как защититься от подобной уязвимости (javascript в href)?
            • 0
              Использовать регулярки, вида ^(http|https), которые строго укажут начало строки для ссылок.
              • 0
                Для примера, URL для подключания jQuery: //code.jquery.com/jquery-1.10.2.min.js
                • +1
                  Извините, но не понял комментария.
                  • +2
                    Всмысле, что корректрый URL не обязательно начинается с http или https.
                    • +1
                      Согласен, но непонятно, зачем давать подобные трик с // пользователю, когда его данные подставляются в <a href=...">
                      • 0
                        Согласен, экзотика, натянуто и вряд ли будет нужно в 99.9% случаев.

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

                        Например подставляя строку:
                        http://" onmouseover="javascript:alert('ha-ha')
                        которая проходит проверку регуляркой ^(http|https)
                        в шаблон
                        <a href="%url%">bla-bla</a>
                        получаем выполнение произвольного JS.
                        • +1
                          Верно, я указал, что это возможный способ провести атаку даже при использовании функций конвертирования спец. символов.
              • 0
                Фильтровать входящие данные на уровне php-приложения с помощью функции filter_var(), для URL пример:
                if(filter_var($_POST['url'], FILTER_VALIDATE_URL)) { echo 'okay bro'; }
                • 0
                  Есть статьи такие: php.net/manual/ru/function.urlencode.php
              • +1
                По п.1:
                Меня уже забавляет как настойчиво предлагают эти функции для фильтрации данных.
                Это не камень в огород автора. Отнюдь.
                То же самое предлагают и разработчики Synfony, например.
                Но. Если мы заглянем внутрь, то неожиданно обнаружим, что на самом деле разработчики эти функции не используют.
                Лучше использовать расширение
                ua2.php.net/manual/ru/book.filter.php
                Эти функции работают гораздо лучше, имеют гибкую настройку, поддерживают callable.
                Это позволяет создать хороший, легко расширяемый и адаптируемый класс валидации и фильтрации данных в вашем проекте.
                • +6
                  В первую очередь нужно использовать мозг, а уже потом «экранирование входных\выходных данных».
                  А так все и лепят mysql_escape_string(addslases(htmlspecialchars(strip_tags($data))), просто потому-что слышали, что это защищает от каких-то «плохих» данных.
                  • +1
                    Особенно смешно подобную цепочку «волшебных» функций видеть при обработке переменной в которой должна быть цифра, чтобы наверняка :)
                  • –2
                    От себя мог бы дать только один совет: используйте нормальные фреймворки (symfony 2, yii, laravel 4), и вам не придётся писать очередные костыли.
                    • +3
                      Проблему нужно понимать, а не советовать волшебные функции/фреймворки которые защищают от чего-то страшного и непонятного, зашифрованного в аббревиатуре XSS.
                      • –1
                        Ну, как мне кажется, понимать проблему — это важное дело. Однако, когда мы говорим о продакшене, нужно использовать проверенные решения, которые точно помогут избежать примитивных проблем с уязвимостями.
                        • 0
                          Просто, имхо, не нужно наличие фреймворка делать равносильным отсутствию уязвимостей.
                          • –2
                            Ага, вполне согласен с этим утверждением.
                    • +1
                      От XSS можно ещё забавно защищаться на клиент сайде. Писал однажды такое решение. Это когда все пришедшие с сервера данные прогоняются через функцию, которая режет недопустимые тэги и параметры допустимых. Для веб приложений это решение актуально тем, что после ввода пользователем некоторых данных хочется тут же их отобразить до прогона соединения на сервер и обратно.
                      • 0
                        Самое полезное здесь — это список литературы.
                        • +1
                          Не совсем. Эту статью пишет студент, молодой специалист в области ИБ. Он изучил материал и делится им. Понятно, что такие специалисты, как BeLove, это знают уже давно :)
                          Пост рассчитан на начинающих специалистов, которые хотят также, как и автор, научиться. А PentestIT помогает ему это сделать.
                          • 0
                            Автор дает вредные советы, которые ведут к XSS.
                        • +4
                          Применяйте встроенные функции для очистки кода от вредоносных скриптов. К ним относятся такие функции как htmlspecialchar(), htmlentities() и strip_tags().
                          Вы упоролись давать такие советы?
                          Это прямой путь к XSS.

                          Данные не должны очищаться. Они должны правильно применяться непосредственно в месте использования. В данном случае (XSS) — при автоэкранировании в шаблонизаторе.
                          • 0
                            Ну вот интересно, как Вы примените входные данные в которых должен сохраниться один тек из всех? Я без HTMLPurifier это не представляю возможным.
                            • 0
                              HTMLPurifier и аналоги.
                              strip_tags для такой задачи не подойдет. Подумайте, почему.
                          • 0
                            Перед автором стояла задача объединить уже имеющиеся лучшие практики в одной статье, поэтому в ней отсутствует какая-либо новизна.
                            • +1
                              Если отклонить все остальное, то фамилия д’Арк будет отклонена — лучше отклонить достоверную информацию, чем принять вредоносные данные.

                              И где эти данные могут навредить? Лучше бы это правило на эту статью применить
                              • +2
                                Если отклонить все остальное, то фамилия д’Арк будет отклонена — лучше отклонить достоверную информацию, чем принять вредоносные данные.

                                Очень полезный совет. Пользователи будут безмерно рады, что вы встроили отличную защиту.
                                • 0
                                  >> Используйте экранирование входных\выходных данных. Применяйте встроенные функции для очистки кода от вредоносных скриптов. К ним относятся такие функции как htmlspecialchar(), htmlentities() и strip_tags().

                                  Очистки? Замена < на &lt; перед сохранением в базу — это какой-то вредный совет, а не очистка.
                                  Пользователи потом удивляются, почему у них символы «больше», «меньше» не выводятся

                                  Вообще никогда никакие входные plain/text данные не приходится вот так вот мучать, если они выводятся нормальным способом.

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

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