Сначала разберем, что такое XSS и его виды.


XSS — Cross-Site Scripting — Одна из множества уязвимостей веб приложений, которая позволяет внедрить вредоносный код, на страницу.

Есть 2 типа XSS по способу воздействия:


  1. Активная — XSS, которая статично находится на странице
  2. Пассивная — XSS, которая динамично отображается на странице, при определенном запросе

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



Начнем с ошибочных представлений разработчиков:


  1. Разработчик думает, что XSS — это инъекция только в HTML-сущность и только. Данное ошибочное мнение складывается изо дня в день. На самом же деле, XSS могут быть не только в HTML, они могут быть почти везде, к примеру даже в картинке.


  2. Разработчик уверен, что отфильтровав спец символы, можно защититься от XSS. Данные мнение частично верно, но в большинстве случаев разработчики фильтруют только кавычки (< и >) и забывают про апострофы, которые потом могут попасть в атрибуты чего-либо.

А сейчас давайте разберем несколько типов атаки:


Обычный тип


Представим у нас есть PHTML (PHP + HTML) код


<!DOCTYPE HTML>
<html>
    <head>
        <title>Привет!</title>
    </head>
    <body>
        Привет, <?=(isset($_GET['name']) ? $_GET['name'] : "незнакомец")?>!
    </body>
</html>

Данный код выводит "Привет, незнакомец!", если нету GET параметра с именем name, но если же параметр есть, то тогда выведет "Привет, {$_GET['name']}!". При помощи GET-запроса, делаем запрос на наш скрипт: index.php?name=Admin. Выводит "Привет, Admin!".


Теперь попробуем вывести наш вредоносный код. Для этого делаем запрос: index.php?name=<script>document.write('Hacker')</script>. Теперь нам выведет "Привет, Hacker!". Как видим, наш код document.write('Hacker') выполнился.




Так-же можно внедрять вредоносный код при внедрении в атрибуты. Для примера возьмем PHTML (PHP + HTML) код:


<html>
    <head>
        <title>Привет!</title>
    </head>
    <body>
        Ваше имя: <input value="<?=(isset($_GET['name']) ? $_GET['name'] : "незнакомец")?>">
    </body>
</html>

Как видим, у нас выполнился код, где имя содержится в inpute (Текстовом поле). Теперь для внедрения нашего кода нужно всего лишь сделать запрос:index.php?name=">. Как видим мы добавили">`, тем самым закрыв атрибут и тэг:




Способ защиты:


Для защиты от данной уязвимости в PHP может использоваться функция htmlspecialchars. Использовать её довольно просто — для этого в отрезок PHP-кода просто добавим обработчик htmlspecialchars Получается:


<?=(isset($_GET['name']) ? htmlspecialchars($_GET['name']) : "незнакомец")?>

И наши XSS уже не работают.




Скриптовой тип


Представим, что у нас есть PHTML (PHP + HTML) код:


<!DOCTYPE HTML>
<html>
    <head>
        <title>Привет!</title>
        <script>
        function deleteNews(id) {
            // Удаление новости
            alert("Успешно удалено");
        }
        </script>
    </head>
    <body>
        <a onclick="deleteNews('<?=(isset($_GET['id']) ? htmlspecialchars($_GET['id']) : "0")?>')">Вывести наш ID</a>
    </body>
</html>

Данный код выводит ссылку "Удалить новость", где ID новости передается в GET параметре id. При помощи GET-запроса делаем запрос на наш скрипт: index.php?name='); alert(1);//. Теперь при нажатии на ссылку, выведет наш alert(1), несмотря на htmlspecialchars. Это происходит из-за того, что htmlspecialchars не фильтрует по умолчанию одинарные кавычки.


Способ защиты:


Есть несколько способов защиты:


  • Тот же htmlspecialchars, но на этот раз с функцией addslashes. Получается:


    <a onclick="alert('<?=(isset($_GET['id']) ? htmlspecialchars(addslashes($_GET['id'])) : "0")?>')">Вывести наш ID</a>

  • В данном способе используется функция json_encode, это поможет избежать лишнего экранирования. Получается:


    <a onclick="alert(<?=htmlspecialchars(json_encode(isset($_GET['id']) ? $_GET['id'] : "0" ))?>)">Вывести наш ID</a>

  • Оставить только числа. В данном способе используется преобразование в integer (Так как ID — это численное значение). Получается:
    <a onclick="alert(<?=(isset($_GET['id']) ? (int)$_GET['id'] : "0" )?>)">Вывести наш ID</a>

    Таким образом мы оставляем только числа.



Изображения


Многие, сейчас задаются вопросом "Почему же изображения и при чем они тут вообще?". В наше время все используют аватарки, картинки и т.п. Но неправильная фильтрация может повлечь за собой последствия! Итак, уязвимость заключается в том, что если фильтровать по типу "Изображение" или нет, то можно разрешить загрузку таких файлов, как SVG. Что такое SVG (.svg или .svgz)?


SVG — Язык для построения векторной графики на XML

Mime-Type для SVG: image/svg+xml


В чем же уязвимость?


SVG может отображаться в браузерах, но в чем же опасность? Как ни странно, но SVG может использовать JavaScript. Для этого просто нужно прописать в тэг svg атрибут onload. Пример:


<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
     width="480" height="360" onload="alert(document.cookie)">
    <image xlink:href="http://my_sniffer/" x="0" y="0" height="50px" width="50px"/>
</svg>

Теперь, если загрузить данный файл на сервер (пусть будет путь http://site.ru/uploads/avatar.svg ) и перейти по нему, то выполнится код из onload.


Способ защиты:


Фильтровать файлы по расширения (.png, .jpg, .jpeg) и после чего проверять их валидность.




Ссылки


К примеру у нас есть скрипт для защиты от OpenRedirect`a на PHTML (PHP + HTML):


<!DOCTYPE HTML>
<html>
    <head>
        <title>Переход на небезопасный сайт!</title>
    </head>
    <body>
        <a href="<?=(isset($_GET['url']) ? htmlspecialchars($_GET['url']) : "https://habrahabr.ru")?>">Перейти на небезопасный сайт!</a>
    </body>
</html>

Уязвимость заключается в том, что мы можем использовать не только http || https-протоколы, а также javascript и data-протоколы. Для демонстрации перейдем по ссылке: script.php?url=javascript:alert(document.cookie);//. Теперь при нажатии на странице на ссылку, у нас вылезут наши cookie`s.


Способ защиты:


Фильтровать URL по regex`у
/^((http|https)\:\/\/)[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z0-9\&\.\/\?\:@\-_=#])*$/is и пропускать, только ссылки проходящие его


Рекомендации:


  1. Использовать специальный HTTPHeader: X-XSS-Protection
  2. Использовать специальный HTTPHeader: Content-Security-Policy

Конструкция <?= ?>


Это конструкция Short Open Tag с выводом данных (print), следующих после =


Ссылки на ресурсы:


https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%B8%D0%BD%D0%B3
https://www.owasp.org/index.php/Cross-site_Scripting_%28%58%53%53%29
http://php.net/manual/ru/function.htmlspecialchars.php
http://php.net/manual/ru/function.addslashes.php
https://habrahabr.ru/post/157087/
https://ru.wikipedia.org/wiki/SVG
http://www.w3.org/Graphics/SVG/
https://habrahabr.ru/company/nixsolutions/blog/271575/
https://habrahabr.ru/post/168739/
https://habrahabr.ru/company/sibirix/blog/223501/

Отдельное спасибо:
michael_vostrikov

Поделиться с друзьями
-->

Комментарии (48)


  1. KlimovDm
    21.10.2016 14:26
    +4

    >>> Как ни странно, но SVG может использовать JavaScript. Для этого просто нужно прописать в тэг svg атрибут onload

    В этом месте я перестал плакать.
    Сильно все намешали, коллега. Думаю, что эта статья новичка скорее запутает, чем систематизирует его знания.


    1. PenGenKiddy
      21.10.2016 18:48

      И в правду, написано больше не для новичка, а для кодеров, которые начали задумываться о безопасности.
      «Для новичков.» Подставили модераторы


      1. KlimovDm
        21.10.2016 19:08
        +1

        Вы ошибайтесь, коллега. Не для кодеров. Не знаю — для кого. Честно.
        Не обижайтесь, но в статье у вас полная каша.

        Если коротко, ваша статья вовсе не о XSS. Она о том, что надо проверять/экранировать $_GET в php. А что там дальше… XSS или SQL injection или (подставить нужное) — это уже дело третье.


        1. M-A-XG
          22.10.2016 12:00
          -1

          >Если коротко, ваша статья вовсе не о XSS. Она о том, что надо проверять/экранировать $_GET в php. А что там дальше… XSS или SQL injection или (подставить нужное) — это уже дело третье.

          Странный вывод. :^)
          xss живет отдельно, SQL injection — отдельно.


          1. PenGenKiddy
            22.10.2016 12:16
            -1

            При чем тут вообще SQLInj? Где ты тут хоть слово о нем увидел?
            Просьба сначала прочитать тему, а потом кидать комментарий


            1. M-A-XG
              22.10.2016 12:29
              -1

              Так и я о том же.
              Вы промазали веткой с ответом. :)


          1. KlimovDm
            22.10.2016 12:48
            +1

            А вы статью-то сами читали? Или так — знакомые слова увидели?
            Еще раз объясню — что подсовывать в $_GET — без разницы — скрипт или sql.
            Вообще статью можно было сократить до фразы «Программеры, проверяйте/очищайте данные, которые получайте» (с) Капитан


            1. PenGenKiddy
              22.10.2016 13:32
              -2

              Без разницы? И в правду?


              1. funnybanana
                22.10.2016 19:18

                Да и в правду. Вообще проверка входных данных — это обязательная вещь не только для защиты от XSS, но и от SQL инъекций что немного критичнее. Предположим у вас есть url:

                http://site.ru?id=1

                вы берете $_GET['id'] и без проверки используете его для запроса строки из БД:

                SELECT * FORM `users` WHERE `id` = "{$_GET['id']}"
                


                Переходим по ссылке: http://site.ru?id="

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


                1. PenGenKiddy
                  22.10.2016 22:02
                  -3

                  Уважаемые друзья, где вы тут увидели про SQL хоть слово? Для SQL есть mysql_real_escape_string и ПДО


                  1. mayorovp
                    23.10.2016 10:17

                    Так в том-то и проблема, что у вас нет ни слова про SQL, mysql_real_escape_string и PDO!


            1. M-A-XG
              22.10.2016 14:22
              -1

              В этой статье речь шла именно об XSS.

              Не нужно тянуть сюда то, что не нужно. Не нужно мешать мух с котлетами.

              П.С.
              Согласен, так можно сократить любую статью, где дыры связаны с пользовательскими данными.
              Даны именно способы защиты.


              1. KlimovDm
                23.10.2016 14:01
                +2

                >>> Даны именно способы защиты

                Способы? Прямо так, во множественном числе?

                Ок, давайте рассмотрим примеры автора. И предлагаемые им способы защиты (названия примеров — из статьи).

                1) Обычный тип. Способ защиты — фильтрация получаемых данных.
                2) Скриптовый тип. Способ защиты — фильтрация получаемых данных.
                3) Изображения. Способ защиты — фильтрация получаемых данных.
                4) Ссылки. Способ защиты — (сюрприз, сюрприз!) фильтрация получаемых данных.

                Вас удивляет, что был упомянут способ атаки SQL injection, и автор тоже не понимает funnybanana, почему, блин? Вроде о XSS писал… А вы поглядите на способы защиты, описываемые автором — какое отношение они имеют непосредственно к XSS?
                Это просто букварь, азбука, тема второго урока.

                И автор еще пишет

                >>>я поднял несколько тем, которые не затрагивают обычные туториалы из интернета

                Прав gospodinmir — «Хабр, серьезно?» (с)


                1. PenGenKiddy
                  23.10.2016 15:20
                  -1

                  Уважаемый, как вы можете видеть фильтрация в разных способах разная.
                  Теоретически можно так ответить на все, как защититься от беременности? — Фильтрация
                  Я писал о XSS, а не о SQL. Если вам нужно SQL идите в гугл и не мешайте темы в топике.


                  1. KlimovDm
                    23.10.2016 16:26
                    +1

                    Вы писали ни о чем. Но пока понять вы это не сможете и убедить вас в этом не удастся.


                1. mayorovp
                  24.10.2016 06:23

                  Поправка: не столько фильтрация, сколько экранирование. И не получаемых, а выводимых...


                  1. KlimovDm
                    24.10.2016 10:29

                    Кмк, все-таки не совсем так. Автор везде работает с $_GET и картинками на серверной стороне, т.е. ведет обработку именно полученных данных, превращая их в выводимые :) А экранирование по большому счету — способ фильтрации. Ну не суть, тут нет предмета дискуссии.


                    1. mayorovp
                      24.10.2016 12:46

                      То, что автор везде работает с $_GET — это еще один недостаток автора. XSS бывает и хранимым — и защита от него будет точно такая же, но $_GET там уже не будет.


                      Слово "фильтрация" подразумевает выкидывание части данных. Задача же экранирования другая — донести все данные до места назначения в неизменном виде.


                      1. KlimovDm
                        24.10.2016 14:02

                        >>> XSS бывает и хранимым — и защита от него будет точно такая же, но $_GET там уже не будет.

                        Ну, собственно об этом и речь, согласен. Но разницы никакой, откуда приложение получает данные — от клиента через $_GET или из базы данных к примеру. Все равно обработка ведется именно получаемых в приложение данных.

                        >>> Слово «фильтрация» подразумевает выкидывание части данных

                        Вовсе не обязательно. Данные могут проходить через фильтр вообще без изменения.


                        1. mayorovp
                          24.10.2016 14:45

                          Данные в БД — уже внутренние для приложения.


                          1. KlimovDm
                            24.10.2016 15:04

                            С чего бы вдруг?
                            БД — всего лишь способ хранения данных. Попадать в БД они могут самыми разными способами, в том числе из других приложений. У приложения априоре нет никаких гарантий, что данные в БД — «чистые».
                            Внутренними для приложения данные становятся уже после получения их из БД, равно как и данные, полученные из $_GET. Никакой глобальной разницы.
                            Мне даже странно, что приходится объяснять такие вещи.


                            1. mayorovp
                              24.10.2016 15:19

                              Разве я писал, что данные из БД — "чистые"? Я писал, что они уже внутренние.


                              1. KlimovDm
                                24.10.2016 16:04

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


                                1. mayorovp
                                  24.10.2016 16:08

                                  Не вижу объяснений. Скажите, а какие данные, по-вашему, не являются получаемыми?


  1. gospodinmir
    21.10.2016 16:29
    +5

    Сначала разберем, что такое XSS и его виды.

    2016 год.


    Хабр, серьезно?


    1. PenGenKiddy
      21.10.2016 18:47
      -3

      Да, так как я поднял несколько тем, которые не затрагивают обычные туториалы из интернета


      1. KlimovDm
        22.10.2016 17:03
        +1

        Я так понимаю, что вы это действительно серьёзно. Ну что ж, удачи.


  1. michael_vostrikov
    21.10.2016 16:56

    <a onclick="deleteNews('<?=(isset($_GET['id']) ? htmlspecialchars($_GET['id']) : "0")?>')">Удалить новость</a>
    

    Это js, поэтому лучше использовать json_encode() И без кавычек:
    <a onclick="deleteNews(<?= htmlspecialchars(json_encode( isset($_GET['id']) ? $_GET['id'] : "0" )) ?>)">Удалить новость</a>
    


    1. PenGenKiddy
      21.10.2016 18:49
      -1

      Возможно вы и правы, но мы ведь не хотим, что бы сторонний array по не аккуратности сериализировался и сунулся в deleteNews?


    1. mayorovp
      21.10.2016 20:28
      +1

      А еще лучше — вот так:


      <a data-id="<?= htmlspecialchars(isset($_GET['id']) ? $_GET['id'] : "0") ?>" onclick="deleteNews(this)">Удалить новость</a>


      1. PenGenKiddy
        22.10.2016 11:01

        Исправил статью. Спасибо за совет


      1. M-A-XG
        22.10.2016 12:17
        -1

        А еще лучше получать ид из базы и всячески избегать прямого вывода из пользовательского кода.

        a data-id="<?=$arItem['id']?>" onclick=«deleteNews(this)»>Удалить новость/a>

        П.С.
        Дебилы разработали этот парсер, не могут нормально обработать ввод, режут все на корню.
        Как раз на тему статьи :)


        1. mayorovp
          22.10.2016 12:41

          Вы так пишите, как будто из базы всегда можно выводить напрямую...


          1. M-A-XG
            22.10.2016 14:36

            Я не пишу, что всегда, я пишу, что лучше (если при этом уже сделана выборка из базы)… :)

            В общем, думаем головой, что когда и как решать.


        1. PenGenKiddy
          22.10.2016 13:34
          -1

          Qiwi используют данный метод…
          Вы хотите сказать, что там работают дебилы?


          1. M-A-XG
            22.10.2016 14:31

            Вы писали по парсеру или по выводу из базы?.. :)


  1. funnybanana
    22.10.2016 10:41
    +1

    Фильтровать файлы по расширения (.png, .jpg, .jpeg) и после чего проверять их валидность.


    в png файл тоже можно вставить php — код, при этом если владелец сайта намудрит с правами то из этой png картинки можно загрузить на сайт любой файл, ну а дальше — зависит от фантазии…


    1. M-A-XG
      22.10.2016 12:23
      -1

      Каким образом злоумышленник выполнит этот файл?

      А также предполагается ж проверка валидности файла.

      П.С.
      Я просто пережимаю картинки в нужные мне размеры и все. :)
      Исходник удаляю. :)


      1. Finesse
        23.10.2016 08:46

        Предположим, файл одновременно и валидная PNG-картинка, и валидный PHP-файл. Злоумышленник отправляет этот файл на сервер, сервер проверяет, что это PNG, и сохраняет, после чего файл доступен по ссылке. Затем злоумышленник переходит по этой ссылке, сервер проверяет, что этот файл PHP, и исполняет его как скрипт.


        1. M-A-XG
          23.10.2016 10:44

          Принято.

          Только это называется намудрил не с правами, а намудрил с настройками веб-сервера.


  1. Nikita_ab
    22.10.2016 10:46

    Эта статья как дополнение: Атаки HTML5: что нужно знать


    1. PenGenKiddy
      22.10.2016 11:00

      Не совсем, но все же добавлю в список ссылок


  1. M-A-XG
    22.10.2016 12:07

    >Для защиты от данной уязвимости в PHP предусмотрена функция htmlspecialchars.

    Функция предусмотрена не для этого, но может использоваться для этого…


    1. PenGenKiddy
      22.10.2016 12:18

      Исправлено


  1. Finesse
    23.10.2016 08:47
    +2

    Использовать специальный HTTPHeader: X-XSS-Protection
    Использовать специальный HTTPHeader: Content-Security-Policy

    Не хватает пояснения, что делают эти заголовки и как они защищают от атаки «Ссылки».


    1. M-A-XG
      23.10.2016 10:54
      +1

      Почитал вчера об X-XSS-Protection.
      Так если браузер его поддерживает, то такая защита включена по умолчанию.
      Заголовок для случая, если пользователь сам выключил защиту в браузере. Для принудительного включения.
      Я решил не добавлять себе этот заголовок. Ну снял пользователь штаны, пусть не обижается. :)
      Да и стараюсь сам не оплошать. Ведь в первую очередь XSS эксплуатирует то, что данные становятся служебным кодом. Возможны случаи, когда пользователь не сможет использовать определенный ввод без злоумыслия.
      XSS защита в браузере — это последний рубеж.


      1. mayorovp
        24.10.2016 06:27

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