Elementor позволяет подставлять на страницы значения из произвольных полей, метаданных записей, параметров URL, комментариев и данных пользователей. Это удобно для динамических шаблонов, но становится опасным, если полученное значение без проверки попадает в HTML, атрибут или JavaScript.
XSS-атака (Cross-Site Scripting) возникает, когда злоумышленник заставляет сайт выполнить контролируемый им код в браузере другого посетителя. Последствиями могут стать кража данных текущей сессии, подмена содержимого страницы, фишинговые формы или выполнение действий от имени пользователя. Ниже приведён практический подход к защите Elementor, собственных виджетов, шорткодов и кода WordPress.
Где в Elementor возникает риск XSS
Опасность связана не с самим использованием динамических тегов, а с неправильной обработкой данных на пути от источника к браузеру. Значение может быть добавлено администратором или автором записи, отправлено посетителем через форму или передано в URL. Если оно выводится в небезопасном контексте, браузер может воспринять часть строки как HTML или JavaScript.
Особое внимание нужно уделять собственным решениям: PHP-шаблонам, шорткодам, кастомным виджетам Elementor, интеграциям с полями и JavaScript-коду, который читает параметры URL. Стандартный виджет может выполнять собственную обработку, но это не означает, что любой окружающий его код безопасен.
- текст из произвольных полей и метаданных записей;
- значения из комментариев, форм и профилей пользователей;
- параметры URL, например поисковый запрос или идентификатор товара;
- динамические значения в атрибутах href, src, title, class или data-*;
- HTML-виджеты, Custom Code и вставки JavaScript;
- собственные шорткоды и виджеты, в которых используется echo без контекстного экранирования.
Санитизация и экранирование: в чём разница
Санитизация очищает или нормализует данные до их сохранения. Например, для короткого текстового поля можно использовать sanitize_text_field(), а для URL — esc_url_raw() во время сохранения. Это полезный уровень защиты, но он не заменяет экранирование при выводе.
Экранирование зависит от места, в котором окажется значение. Одну и ту же строку нельзя одинаково безопасно вставлять в текст, HTML-атрибут, URL и JavaScript. Простое правило: проверяйте данные при получении и сохранении, но экранируйте их непосредственно перед каждым выводом.
Не следует полагаться на то, что поле уже очищено в административной панели или обработано плагином. Данные могут поступить другим способом, измениться после сохранения или использоваться в другом контексте.
- sanitize_text_field() — для простых текстовых значений во время обработки или сохранения;
- sanitize_email() — для адресов электронной почты;
- absint() или проверка по допустимому списку — для чисел и идентификаторов;
- esc_html() — для обычного текста внутри HTML;
- esc_attr() — для значений HTML-атрибутов;
- esc_url() — для URL, выводимых на странице;
- wp_kses_post() — когда необходимо разрешить ограниченный безопасный HTML.
Правильное экранирование для разных контекстов
Выбирайте функцию не по названию поля, а по месту вывода. Например, значение поля «Заголовок» может выводиться как текст в заголовке, как значение атрибута title или как часть ссылки. Для каждого варианта требуется отдельное экранирование.
Для обычного текста не используйте wp_kses_post(), если HTML не нужен. Безопаснее вывести строку как текст с помощью esc_html(). Если логика проекта действительно предполагает форматирование, разрешайте только ограниченный набор HTML-тегов через wp_kses_post() или собственный список разрешённых тегов.
- Текст в разметке: esc_html($value).
- Атрибут: esc_attr($value).
- Ссылка: esc_url($url).
- Ограниченный HTML: wp_kses_post($html).
- Числовое значение: absint($value) или проверка диапазона.
- Значение для JavaScript: не вставляйте его простым объединением строк; используйте безопасную передачу данных, например wp_json_encode(), с учётом контекста.
<?php
$title = get_post_meta( get_the_ID(), 'custom_title', true );
$url = get_post_meta( get_the_ID(), 'custom_url', true );
$body = get_post_meta( get_the_ID(), 'custom_content', true );
?>
<h2><?php echo esc_html( $title ); ?></h2>
<a href="<?php echo esc_url( $url ); ?>" title="<?php echo esc_attr( $title ); ?>">
<?php echo esc_html( $title ); ?>
</a>
<div class="content">
<?php echo wp_kses_post( $body ); ?>
</div>
Безопасная работа с URL и параметрами запроса
Параметры URL часто считают безобидными, поскольку они не сохраняются в базе данных. На самом деле это типичный канал отражённого XSS: злоумышленник формирует специальную ссылку, а сайт выводит значение параметра в заголовке, сообщении или атрибуте без экранирования.
Сначала получите значение с ожидаемым типом, а перед выводом используйте нужную функцию. Если параметр должен быть числом или одним из нескольких допустимых значений, не принимайте произвольную строку. Для перенаправлений дополнительно проверяйте разрешённые хосты и схемы, чтобы исключить опасные или неожиданные адреса.
<?php
$query = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : '';
$item_id = isset( $_GET['item'] ) ? absint( $_GET['item'] ) : 0;
?>
<p>Результаты для: <?php echo esc_html( $query ); ?></p>
<div data-item-id="<?php echo esc_attr( $item_id ); ?>"></div>
Собственные виджеты, шорткоды и Elementor Custom Code
В собственном виджете Elementor не следует выводить данные через echo без проверки. Экранируйте значения в методе, который формирует итоговую HTML-разметку. Если виджет позволяет пользователю вводить HTML, чётко определите разрешённые теги, атрибуты и протоколы URL.
У шорткодов возникает та же проблема: атрибуты шорткода могут изменяться пользователем, поэтому их значения нельзя считать доверенными. Даже если шорткод предназначен только для администраторов, защита должна обеспечиваться на уровне кода — роли, импорт или скомпрометированная учётная запись могут изменить его данные.
HTML-виджет и Custom Code фактически позволяют вставлять произвольную разметку или скрипты. Доступ к ним следует оставлять только доверенным администраторам. Не предоставляйте такие права авторам, редакторам или клиентским ролям без чёткой необходимости и контроля.
- не выводите значения полей через необработанный echo;
- не используйте esc_html() для URL вместо esc_url();
- не разрешайте произвольный HTML только потому, что он хранится в произвольном поле;
- не вставляйте данные пользователя напрямую во встроенный JavaScript;
- проверяйте права на редактирование HTML, Custom Code и шаблонов;
- после изменений проверяйте и опубликованную страницу, и предварительный просмотр.
<?php
function webhva_notice_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'text' => '',
'type' => 'info',
),
$atts,
'webhva_notice'
);
$allowed_types = array( 'info', 'warning', 'success' );
$type = in_array( $atts['type'], $allowed_types, true ) ? $atts['type'] : 'info';
return '<div class="notice notice-' . esc_attr( $type ) . '">' .
esc_html( $atts['text'] ) .
'</div>';
}
Типичные ошибки, создающие уязвимость
Чаще всего XSS возникает не в одном очевидном месте, а из-за неправильного предположения: «это поле редактирует только администратор» или «мы уже очистили значение при сохранении». Защита должна сохраняться даже после изменения способа импорта, ролей или шаблона.
Отдельно проверяйте HTML, атрибуты и JavaScript. Экранирование, безопасное для одного контекста, может быть недостаточным для другого. Например, HTML-строку нельзя бездумно переносить в JavaScript, а href или src не следует формировать конкатенацией с непроверенным параметром.
- вывод get_post_meta(), get_field() или $_GET без экранирования;
- использование esc_attr() для полного HTML-блока вместо wp_kses_post();
- разрешение raw HTML там, где достаточно обычного текста;
- доверие к данным из базы только потому, что когда-то их ввёл администратор;
- формирование href или src из непроверенного значения;
- передача данных в data-* атрибуты без esc_attr();
- вставка строки в script через конкатенацию без корректного JSON-кодирования.
Как проверить сайт на Elementor после изменений
Тестирование следует проводить в тестовой среде или после создания резервной копии. Проверьте все поля, которые могут редактировать авторы, пользователи или посетители, а также все шаблоны, в которых эти поля подставляются динамическими тегами.
Для ручной проверки используйте безопасные тестовые маркеры, показывающие, превращается ли ввод в HTML, а не в текст. Не проводите эксперименты с реальными пользовательскими сессиями и не вставляйте вредоносные скрипты на рабочий сайт.
- найдите в коде места с echo, print, innerHTML и конкатенацией HTML;
- проверьте кастомные виджеты, шорткоды и PHP-шаблоны;
- просмотрите поля, выводимые в тексте, атрибутах, URL и скриптах;
- проверьте страницу как гость и как авторизованный пользователь;
- убедитесь, что HTML-виджет и Custom Code доступны только доверенным ролям;
- обновляйте WordPress, Elementor, тему и дополнения и анализируйте ошибки после обновлений;
- при необходимости дополните защиту политикой Content Security Policy, но не воспринимайте её как замену правильному экранированию.
Краткий чек-лист перед публикацией
Перед запуском динамического шаблона проследите значение по всей цепочке: источник, сохранение, получение, формирование разметки и отображение в браузере. Для каждой точки должно быть понятно, какой формат разрешён и какая функция защищает конкретный контекст.
- обычный текст выводится через esc_html();
- атрибуты выводятся через esc_attr();
- ссылки проверяются и выводятся через esc_url();
- HTML ограничивается с помощью wp_kses_post() или собственного списка разрешённых тегов;
- числа, идентификаторы и значения из списка проходят проверку типа или allowlist;
- данные из URL, форм и комментариев не считаются доверенными;
- собственные виджеты и шорткоды не содержат необработанного вывода;
- права на HTML и Custom Code ограничены;
- после тестирования введённые теги или скрипты не выполняются в браузере.
Частые вопросы
Достаточно ли санитизировать поле при сохранении?
Нет. Санитизация снижает риск при приёме или сохранении данных, но при каждом выводе требуется экранирование в соответствии с контекстом: esc_html(), esc_attr(), esc_url() или wp_kses_post().
Безопасны ли динамические теги Elementor по умолчанию?
Нельзя автоматически считать безопасной всю цепочку. Стандартные компоненты могут обрабатывать данные, но риск часто возникает в собственных виджетах, шорткодах, шаблонах, HTML-виджетах или коде, расширяющем Elementor.
Когда использовать wp_kses_post(), а не esc_html()?
esc_html() используется, когда значение должно быть обычным текстом. wp_kses_post() применяйте только тогда, когда функциональность действительно требует ограниченного HTML. Разрешать произвольный нефильтрованный HTML не следует.
Может ли XSS возникнуть через параметр URL?
Да. Это возможный сценарий отражённого XSS, если значение из URL выводится в заголовке, сообщении, атрибуте или скрипте без обработки. Получайте параметр с нужным типом и экранируйте его непосредственно перед выводом.
Вывод
Защита Elementor от XSS начинается с контроля контекста, в котором выводится динамическое значение. Не доверяйте данным только потому, что они хранятся в WordPress или вводятся через административную панель.
Проверяйте данные при получении и сохранении, а экранирование выполняйте при выводе: текст — через esc_html(), атрибуты — через esc_attr(), URL — через esc_url(), разрешённый HTML — через wp_kses_post(). Особенно тщательно проверяйте собственные виджеты, шорткоды, HTML-виджет и Custom Code.