Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 168 lines (104 sloc) 18.8 KB

Уязвимость XSS в PHP-скриптах и как ее избежать

XSS — это тип уязвимости, когда у злоумышленника получается пропихнуть вредоносный HTML- или JS- код в страницу на сайте. Напомню, что JS (яваскрипт) — это язык, программы (скрипты) на котором можно встраивать в HTML-страницу и выполнять внутри браузера. Если злодею это удается сделать, то при открытии такой страницы в браузере внедренный JS-код может выполнять любые действия на сайте от имени пользователя, например, воровать его куки (чтобы злоумышленник потом с их помощью зашел на сайт), рассылать спам, копировать личные данные пользователя, перенаправлять пользователей на другой сайт, нажимать кнопки, отправлять сообщения - в общем, такой код может сделать много нехороших вещей.

Самый простой пример уязвимости получается, когда переданные через GET-параметр значения выводятся (добавляются в код страницы) как есть, например, http://example.com?q=[... html и js код ...]. Но вообще, XSS атаку можно провести и через любые другие данные, например, POST-, куки, HTTP-заголовки, данные из базы (которые хакер записал туда раньше). То есть на более-менее сложном сайте невозможно отфильтровать все входные данные, это бесперспективный путь.

Также, пытаться фильтровать все GET/POST данные (как рекомендуют некоторые учебники) не очень перспективно, так как есть очень много способов подсунуть злой код (примеры: https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet ). Трудно написать фильтр, который сможет ловить все эти коды.

Пример XSS

Прежде чем говорить о методах борьбы, рассмотрим 2 простых примера. Допустим, школьник Вася сделал на своем сайте скрипт search.php для поиска информации на сайте. Данные для поиска передаются через GET-параметр и выводятся без фильтрации. Вот, как выглядит код:

// ....
$query = $_GET['q'];
echo "<p>Вы искали: $query</p>";
// .... дальше идет код вывода результатов поиска ...

Если посмотреть повнимательнее, то видно, что данные, переданные в URL (и попавшие в $_GET), выводятся на странице без всяких преобразований.

Злоумышленник может сделать ссылку: http://example.com/search.php?q=<script>зловредный скрипт, ворующий все куки пользователя</script>

Содержимое параметра q без изменений вставится в страницу и получится код:

<p>Вы искали: <script>зловредный скрипт, ворующий все куки пользователя</script>

Код на яваскрипте, написанный внутри тега <script>, выполняется браузером при отображении страницы. Следовательно, злоумышленник может создать ссылку, при открытии которой пользователем выполнится любой заложенный в этой ссылке JS-код.

Злоумышленник посылает ссылку Васе (автору сайта), тот ее открывает в браузере, запускается скрипт и ворует куки Васи. Злоумышленник выкладывает куки на анонимном форуме и все его посетители, с помощью кук зайдя на сайт под администратором, дружно издеваются над сайтом Васи.

Пример 2

Наученный горьким опытом, Вася почитал устаревшие учебники по PHP4 и увидел там совет "очищать" пришедшие от пользователя данные. Вася поставил фильтр, не пропускающий угловые скобки и слово «script». И переделал свой код. Теперь данные фильтруются и Вася спит спокойно (однако, поиск по ключевому слову script теперь не работает):

....
$query = $_GET['q'];
$query = preg_replace("/<|>|script/ui", '', $query);

echo "<p>Вы искали: $query</p>";
// .... дальше идет код вывода результатов поиска ...
echo "Искать еще: <input type=text value='$query'><button type=button>Искать</button>";
// .....

Хакер так просто не сдается. Он делает ссылку вида

/search.php?q=' autofocus onfocus='злой код'

и снова отправляет ее админу Васе. Админ открывает страницу, и запускается злой скрипт. Почему? Посмотрим, что получилось при подстановке параметра q в input type=text:

Искать еще: <input type=text value='' autofocus onfocus='злой код'
'><button type=button>Искать</button>

Как видим, за счет кавычки текст злоумышленника смог "выйти" за пределы атрибута value и добавить дополнительные атрибуты к тегу <input>. Атрибут autofocus вызывает автоматическую установку курсора в поле при загрузке страницы, а атрибут onfocus содержит JS-скрипт, который срабатывает при установке курсора в поле (в HTML много таких атрибутов, они все начинаются с on...). Вася опять попался (и в этот раз хакер отыгрался на его сайте по полной программе).

Если читатель не хочет повторять ошибки Васи, ему стоит выбросить устаревшие учебники и читать дальше.

Средства борьбы

Средство борьбы одно: все данные, выводимые из переменных (неважно, откуда они пришли), в HTML код, надо пропускать через функцию htmlspecialchars (мануал по ней: http://php.net/manual/ru/function.htmlspecialchars.php ):

echo "<p>Вы искали: ".htmlspecialchars($query, ENT_QUOTES)."</p>";
echo "Искать еще: <input type=text value='".htmlspecialchars($query)."'><button type=button>Искать</button>";

Так надо делать со всеми переменными без исключения, значения которых выводятся на странице. Эта функция, htmlspecialchars, заменяет символы <, >, ", ', & на HTML-мнемоники так, что любые теги и кавычки не ломают HTML-код, а просто выводятся как текст.

Символ Заменяется на
< &lt;
> &gt;
& &amp;
" &quot;
' &#039;

Теперь хакер может передавать любые спецсимволы в параметре q и уязвимости не будет, так как код <script> заменится на конструкцию &lt;script&gt;, которая воспринимается браузером как текст <script> (не как HTML-тег, а как просто текст, состоящий из угловых скобок и слова script).

Можете сделать этот скрипт, и проверить сами.

Если лень писать каждый раз htmlspecialchars($query, ENT_QUOTES), можно сделать функцию с коротким именем:

function html($text) {
    return htmlspecialchars($text, ENT_QUOTES);
}

Обратите внимание на параметр ENT_QUOTES — если его забыть указать, то одиночная кавычка не экранируется.

Хорошие шаблонизаторы вроде Twig экранируют данные при выводе автоматически.

От чего не поможет htmlspecialchars

Если на сайте пользователь может вводить и сохранять ссылки (например, в профиле в разделе «О себе»), то надо проверять, что они начинаются с http:// или https:// . Иначе пользователь подсунет ссылку вида data://... или javascript:// ...., при клике по которой выполнится заложенный в ней JS-код. Конечно, ему надо еще заманить других пользователей кликнуть по ней, но наверняка есть способы для этого. htmlspecialchars тут не спасет, но поможет определение протокола ссылки с помощью parse_url() или проверка ссылки регулярным выражением.

Дополнительно защищаем куки

Дополнительная защита никогда не помешает, верно? У кук уже несколько лет есть специальный дополнительный параметр: httpOnly. Если его указать при создании куки, то такая кука будет недоступна яваскрипту на странице и злоумышленник, даже найдя XSS, не сможет её украсть (но другие нехорошие вещи он все равно сможет сделать, так что это не дает 100% защиту, а лишь усложняет атаку).

Вот, как создать http-only куку в PHP:

setcookie ('auth', '12344567890', 0, '/', null, false, true)

последний параметр, true, включает опцию http-only. Думаю, стоит включать эту полезную опцию для всех кук, которые как-то авторизуют пользователя. Мануал: http://php.net/manual/en/function.setcookie.php

Защита от XSS в браузерах

В некоторых браузерах (Chrome например) есть простая защита от XSS (XSS Auditor). Chrome при загрузке страницы проверяет все находящиеся в ней скрипты и сравнивает с URL. Если JS-код совпадает с строкой из URL, то он вырезается и не выполняется (эта защита сработала бы в первом примере, где хакер внедрил тег <script> с кодом на страницу через параметр в строке запроса). Однако эта система защищает не от 100% атак, а только от части случаев и иногда обходится. Потому ты не должен на нее полагаться. Информация: http://habrahabr.ru/post/143022/ (хабр)

Content-Security-Policy

Новые браузеры поддерживают специальный заголовок Content-Security-Policy, который определяет какие скрипты (а также CSS файлы и файлы плагинов вроде флеш-роликов) и из каких источников можно подключать на странице. Если убрать весь JS-код в внешние скрипты, размещенные на сервере, и запретить в этом заголовке выполнять скрипты с чужих сайтов и unsafe-inline (скрипты, вписанные в страницу, а также атрибуты-обработчики вроде onclick), то вы сможете добавить дополнительную защиту своему сайту от большинства XSS. Также, браузер будет отправлять отчеты в формате JSON о попытках нарушить эти ограничения. Вот статьи, но учтите, что они могут быть сложными для начинающего:

Сложный случай: вывод HTML-кода

Иногда есть ситуации, когда надо разрешить пользователю выводить на сайте HTML-код. Ну например, если мы разрабатываем блог и хотим, чтобы пользователи могли публиковать текст с заголовками, картинками, ссылками, то есть со сложным форматированием. htmlspecialchars тут не поможет, так как она просто превратит все теги в обычный текст.

Тут есть 2 пути решения:

1. Придумать какую-то разметку, которая позже преобразуется в HTML-код. Пример такой разметки - Markdown. Для неё написаны библиотеки, преобразующие текст с разметкой в HTML-код. Например, текст вида

Hello, **world**

Превращается в HTML-код

Hello, <strong>world</strong>

Минусы этого подхода:

  • пользователям надо изучить язык разметки, нельзя просто использовать WYSIWYG-редактор
  • нужно тщательно проверить библиотеку конвертации Markdown в HTML, что в ней нет никаких способов ввести потенциально опасные HTML-теги вроде <script>. Зачастую такая возможность есть.

2. Использовать фильтр HTML-кода, который пропускает только теги и атрибуты из разрешенного белого списка, а также проверяет все ссылки (если они разрешены) на то, что в них используются разрешенные протоколы (чтобы, например ссылка вида data://... не прошла).

Есть много готовых библиотек с такими возможностями, но нужно внимательно смотреть на принцип их работы. Если библиотека использует регулярные выражения для фильтрации кода - с большой вероятностью, найдется конструкция, с помощью которой ее можно обойти, так как регулярными выражениями почти невозможно разобрать все аспекты HTML-кода. Правильно написанная библиотека разбирает HTML-код, преобразует его в дерево DOM, для работы с которым в PHP есть расширение DOM. Затем библиотека обходит полученное дерево, проверяя каждый тег, атрибут и ссылку на соответствие белому списку и удаляет те, что не разрешены. Затем дерево преобразуется обратно в HTML-код.

При использовании фильтра мы можем дать пользователю стандартный WYSIWYG-редактор (список таких редакторов: https://github.com/cheeaun/mooeditable/wiki/Javascript-WYSIWYG-editors (англ.)). Редактор отправляет текст с форматированием на сервер в виде HTML-кода, который на сервере очищается с помощью библиотеки-фильтра.

Пример хорошей библиотеки-фильтра, использующей DOM: http://htmlpurifier.org/ (англ.)

Повторим еще раз

  • Выводим данные только через htmlspecialchars
  • Проверяем протокол в ссылках, пришедших от пользователей
  • Используем httpOnly куки

Дополнительное чтение (на русском)

http://archive-ipq-co.narod.ru