Как известно, любой текст, который вводится пользователем и затем отображается на сайте, нужно обезопасить от XSS-атак — вставок JavaScript, которые могут украсть идентификатор сессии или совершить какие-либо нежелательные действия от имени пользователя. Если текст не предполагает сложного форматирования, то сделать это достаточно легко: пропустить его через функцию htmlspecialchars, которая экранирует все небезопасные символы и превратит HTML в обычный текст. Но как быть, если пользователю нужно разрешить использовать форматирование текста, например, вставку картинок, ссылок, текста с курсивом и жирным начертанием, видео?
Первое, что приходит в голову — это воспользоваться функцией strip_tags со списком разрешённых тегов. Увы, эта функция имеет существенный недостаток: если тег разрешён, то она позволяет использовать в нём любые атрибуты, в том числе и атрибуты обаботчиков событий (onclick, onmouseover и так далее), на которые легко можно повесить вредоносный код.
Другой вариант — это использование специальных языков разметки, например, BBCode или Markdown, которые затем преобразовывать в HTML. Главный недостаток такого подхода — в том, что это существенно сужает выбор WYSIWYG-редакторов, так как далеко не в каждом из них есть поддержка этих языков.
Поэтому приходится прибегать к другому решению — использованию расширения DOM и удалению все тех тегов и атрибутов, которых нет в белом списке. Для начала решим, как будем задавать этот белый список. На мой взгляд, самый эффективный вариант — это хеш-массив, где ключи — это теги, а значения — массивы разрешённых для тега атрибутов (в примерах кода дальше будем считать, что он лежит в $tags, а HTML-код для очистки — в HTML)
Для начала просто удалим все те теги, которых нет в списке разрешённых, с помощью функции strip_tags:
$html = strip_tags($html,'<'.join('><',array_keys($tags)).'>');
Теперь загрузим HTML-код в объект DOMDocument и создадим объект XPath для поиска атрибутов тегов и выполним этот поиск:
$html = mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'); // без этого не будет корректно работать с UTF-8
if (!class_exists('DOMDocument')) throw new Exception('DOM extension not loaded!');
$dom = new DOMDocument();
$dom->formatOutput = false;
$dom->loadHTML($html,LIBXML_NONET|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//@*');
В переменной $nodes лежит список таких узлов-атрибутов. Пройдёмся по ним и проверим, есть ли тег (на него указывает $node->parentNode->nodeName) в списке разрешённых тегов и есть ли сам атрибут ( $node->nodeName) в списке разрешённых атрибутов для этго тега. Если его там не будет, обратимся к родительскому элементу через $node->parentNode и вызовем метод removeAttribute для его удаления.
foreach ($nodes as $node) {
$tagname = $node->parentNode->nodeName;
if (!empty($tags[$tagname])) {
$attrs = $tags[$tagname];
$attrname = $node->nodeName;
if (!in_array($attr_name,$attrs)) $node->parentNode->removeAttribute($attrname);
}
}
Теперь осталась ещё одна задача: проверить атрибуты href и src на наличие адресов вида javascript:alert('Небезопасно'). Для этого найдём с помощью XPath все атрибуты href и src, и проверим, какой протокол для ссылок используется. Если там есть слово script (так как кроме javascript, можно использовать ещё и vbscript), будем считать такую ссылку небезопасной и заменим её на безопасное значение "#":
$links = $xpath->query('//@href|//@src');
foreach ($links as $link) {
$scheme = parse_url($link->textContent,PHP_URL_SCHEME);
if (strpos(strtolower($scheme),'script')!==false) {
$link->nodeValue='#'; // removing dangerous link address
}
}
Теперь осталось только сохранить обработанный HTML-код из DOM-дерева обратно в строку:
$html = $dom->saveHTML();
Посмотреть полный код, оформленный в виде класса HTMLCleaner со статическим методом clean, можно в приложенном файле. В этом же классе определён набор констант с наиболее часто требующимися тегами и их атрибутами: TAGS_MINIMUM — только a и img, TAGS_MEDIA — для мультимедиа-тегов audio и video, TAGS_INLINE — для самых частых строчных тегов оформления, TAGS_FORMAT — для типичных блочных тегов. При необходимости их можно объединять через операцию +.

- Перово, Москва, Россия
- me@4xpro.ru
- XXXXPro
Новости сайта в Telegram
t.me/4x_proКомпьютерное
Выборочная очистка HTML

Как добавить ReCAPTCHA на свой сайт

Недавно столкнулся с тем, что боты поумнели настолько, что начали обходить обычную CAPTCHA на одном из сайтов, поддержкой которых я занимаюсь. Пришлось искать альтернативные решения, и выбор пал на reCAPTCHA от Google. Как выяснилось, подключить ее достаточно просто. Нужно зарегистрировать свой сайт на странице ReCAPTCHA (нужно иметь учетную запись в Google) и получить пару ключей: публичный (он будет размещаться на сайте) и секретный, который будет храниться только на сервере.
Дальше в форму, для которой нужна проверка CAPTCHA вставляем код такого вида:
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="ваш-публичный-ключ"></div>
Google рекомендует вставлять тег script после head, однако на самом деле это не обязательно, и можно вставить его и прямо в форму или вообще в конец страницы перед </body>. Последний вариант, пожалуй, даже предпочтительнее, так как в этом случае код будет загружаться в самом конце загрузки страницы и не будет ее замедлять.
Если вы все сделаете правильно, то в том месте сайта, куда вы вставили код, появится такой блок:

В нем же будет скрытое поле g-recaptcha-response, которое будет отправлено на сервер вместе с данными формы. Для проверки корректности прохождения CAPTCHA нам нужно сделать POST-запрос на сервер Google. Читать далее…
Делаем Интернет снова быстрым или JavaScript по белому списку

Современные сайты слишком активно стали обвешивать всякими ненужными скриптами: то онлайн-консультанты, то обратные звонки, то надоедливые предложения подписаться, закрывающие контент в самый неподходящий момент. Не говоря уж о том, что это дополнительное время загрузки и дополнительный расход памяти. В результате я все чаще и чаще задумывался о том, что JavaScript должен быть по белому списку, то есть только на тех сайтах, где он действительно нужен, и где я разрешил его явно. К тому же это устраняет часть потенциальных угроз, с которыми можно столкнуться на просторах Интернета.
Но увы, каждый раз лезть в настройки Opera и явно включать JavaScript для каждого сайта штатными средствами Opera очень неудобно. Стал задумываться об установке стороннего дополнения. Хотел было использовать NoScript, которым когда-то пользовался под Firefox, но под Opera его нет. Есть одноименное расширение от других разработчиков, но с ужасным интерфейсом и весьма неудобное.
Стал искать другие варианты и наткнулся на ScriptSafe. Понравилось сразу: и интерфейс приятный, и есть возможность в два щелчка мыши разрешить или запретить загрузку JavaScript как для основного домена сайта, так и по отдельности для каждого из сторонних доменов, что дает возможность заблокировать сторонние скрипты типа социальных виджетов или тех же обратных звонков. Любителям приватности также пригодится множество опций для защиты от отслеживания.
Единственная проблема, которую я на данный момент выявил — неудобно производить оплату картой онлайн, так как при этом происходит несколько редиректов между сайтами (сайт магазина, сайт платежной системы, сайт банка), каждый из которых приходится добавлять в белый список, чтобы скрипты сработали.