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 лише тому, що він зберігається в кастомному полі;
- не вставляйте дані користувача безпосередньо в inline-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, а URL не слід формувати конкатенацією з неперевіреним параметром.
- виведення 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.