XSS — это тип уязвимости, когда злоумышленник умудряется пропихнуть HTML- или JS- код в страницу на твоем сайте. Напомню, что JS (яваскрипт) — это язык, программы на котором можно встраивать в HTML-страницу и выполнять внутри браузера. Если злодею это удается сделать, то при открытии такой страницы в браузере код исполняется и может делать любые вещи от имени пользователя, например, воровать его куки (чтобы злоумышленник потом с их помощью зашел на сайт), рассылать спам, перенаправлять пользователей на другой сайт, в общем код может сделать много нехороших вещей.
Самый простой пример, когда можно передать вредные данные через GET-параметр, например, http://example.com?q=[... html и js код ...]. Но вообще, XSS атаку можно провести и через любые другие данные, например, POST-, куки, HTTP-заголовки, данные из базы (которые хакер записал туда раньше). То есть на более-менее сложном сайте невозможно отфильтровать все входные данные, это бесперспективный путь.
Также, пытаться фильтровать GET/POST не очень перспективно, так как есть очень много способов подсунуть злой код (примеры: https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet ). Трудно написать фильтр, который сможет ловить все эти коды.
Прежде чем говорить о методах борьбы, рассмотрим 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>
, выполняется браузером при отображении страницы.
Злоумышленник посылает ссылку Васе (автору сайта), тот ее открывает в браузере, запускается скрипт и ворует куки Васи. Злоумышленник выкладывает куки на анонимном форуме и все его посетители, с помощью кук зайдя на сайт под администратором, дружно издеваются над сайтом Васи.
Наученный горьким опытом, Вася почитал устаревшие учебники по PHP4 и, как там советовалось, поставил фильтр, не пропускающий угловые скобки и слово «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
содержит скрипт, который срабатывает при установке курсора в поле (в 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, а просто выводятся как текст.
Теперь хакер может передавать любые спецсимволы в параметре q
и уязвимости не будет, так как например тег <div>
заменится на код < div >
который отобразится браузером как <div>
(не как тег, а как текст).
Можешь сделать этот скрипт, и проверить сам.
Если лень писать каждый раз htmlspecialchars($query, ENT_QUOTES)
, можно сделать функцию с коротким именем:
function html($text) {
return htmlspecialchars($text, ENT_QUOTES);
}
Обрати внимание на параметр ENT_QUOTES
— если его забыть указать, то одиночная кавычка не экранируется.
Если ты используешь хороший шаблонизатор вроде twig, то он экранирует данные при выводе автоматически.
Если ты на сайте разрешаешь пользователям вводить ссылки (например, в разделе «О себе»), то надо проверять, что они начинаются с http:// или https:// . Иначе пользователь подсунет ссылку вида data://... или javascript:// .... при клике по которой выполнится код. Конечно, ему надо еще заманить других пользователей кликнуть по ней, но наверняка есть способы для этого. htmlspecialchars
тут не спасет, но поможет проверка ссылки регулярным выражением на правильность.
Дополнительная защита никогда не помешает, верно? У кук уже несколько лет есть специальный дополнительный параметр: httpOnly
. Если ты укажешь его при создании куки, то такая кука будет недоступна яваскрипту на странице и злоумышленник даже найдя XSS не сможет украсть куки (но другие нехорошие вещи он сможет сделать, так что защищаться все равно надо).
Вот, как создать http-only куку в PHP:
setcookie ('auth', '12344567890', 0, '/', null, false, true)
последний параметр, true
, включает опцию http-only. Думаю, стоит включать эту полезную опцию для всех кук, которые как-то авторизуют пользователя. Мануал: http://php.net/manual/en/function.setcookie.php
В некоторых браузерах (Chrome например) есть простая защита от XSS (XSS Auditor). Chrome при загрузке страницы проверяет все находящиеся в ней скрипты и сравнивает с URL. Если код яваскрипта совпадает с строкой из URL, то он вырезается и не выполняется (эта защита сработала бы в первом примере, где хакер внедрил тег <script>
с кодом на страницу через параметр в строке запроса). Однако эта система защищает не от 100% атак, а только от части случаев и иногда обходится. Потому ты не должен на нее полагаться. Информация: http://habrahabr.ru/post/143022/ (хабр)
Новые браузеры поддерживают специальный заголовок Content-Security-Policy
, который определяет какие скрипты (а также CSS файлы и файлы плагинов вроде флеш-роликов) и из каких источников можно подключать на странице. Если ты уберешь весь JS-код в внешние скрипты, размещенные на твоем сервере, и запретишь в этом заголовке выполнять скрипты с чужих сайтов и unsafe-inline
(скрипты, вписанные в страницу, а также атрибуты-обработчики вроде onclick
), то сможешь добавить дополнительную защиту своему сайту от большинства XSS. Также, браузер будет отправлять сообщения в формате JSON о попытках нарушить эти ограничения. Вот статьи, но учти, что они могут быть сложными для начинающего:
- http://habrahabr.ru/company/yandex/blog/206508/
- https://xakep.ru/2013/12/23/61798/
- https://developer.mozilla.org/ru/docs/Web/Security/CSP
- Выводим данные только через
htmlspecialchars
- Проверяем ссылки, пришедшие от пользователей, регуляркой
- Используем httpOnly куки
- http://html5sec.org/ (содержит примеры различных способов выполнить скрипт на странице)
- http://habrahabr.ru/post/143259/
- http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet