A backstage pass that only works if the venue left a side door unlocked or the security guard went home
All versions through 0.9.2.6 of the paid Advanced Custom Fields: Extended PRO plugin for WordPress contain a dynamic call vulnerability in render_field: form configuration parameters are merged into field settings and passed to call_user_func_array. An unauthenticated attacker who reaches that function can choose a WordPress function name and its arguments. It is not a classic unrestricted upload despite CWE-434; it is a validation failure that leads to arbitrary function invocation.
The vendor's CVSS 9.8 assumes an unconditional unauthenticated remote path, which overstates real-world reach. The two documented entry conditions—admin-enabled shortcode_preview or a PHP environment missing OpenSSL—are not default in most WordPress fleets. The impact, however, is severe: calling functions like wp_insert_user can mint an administrator account, then plugin upload gives code execution. Scope is unchanged because the code runs in the web server process; confidentiality, integrity, and availability of the site are all at risk under the condition set.
5 steps from start to impact.
Enumerate active ACF Extended PRO instances
- Target site is publicly reachable
- ACF Extended PRO installed and active
- Version ≤0.9.2.6
- Commercial PRO plugin; lower installed base than free ACF
- Version may be hidden from public pages; attacker may need exposed
/wp-json/wp/v2/plugins
Reach render_field execution with either fallback
shortcode_preview setting, or (B) the PHP environment lacks openssl, so ACF Pro's acf_decrypt falls back to unverified base64_decode. In state B, no admin action is required.- Method 1:
shortcode_previewenabled on target form - Method 2:
opensslPHP extension missing
shortcode_previewis non-default- Most PHP/WordPress builds include OpenSSL; absence is uncommon
php -m | grep openssl and plugin settings.Submit crafted form configuration to merge into field settings
$field settings before calling call_user_func_array. The attacker controls function name and arguments.- Reachable WordPress frontend/admin-ajax path or form endpoint
- Payload matches expected parameter structure
- No Web Application Firewall block on the request
- Parameter names may require plugin knowledge
- WAF with custom rules for ACF Extended could block; generic rules likely miss it
Invoke arbitrary WordPress function
call_user_func_array($field['render_field'], $args) executes the selected function. A reliable escalation is wp_insert_user with ['user_login', 'user_pass', 'role' => 'administrator']. The function runs in the WordPress context, bypassing normal REST/registration capability checks.- The function name exists in WordPress core or active plugins
- The function does not perform capability checks on
role(many core functions do not when called PHP-direct)
- Some functions may reject roles or sanitize user data; attacker chooses from many callable functions
- If
wp_insert_userdoesn't grant admin due to hardening, other functions (update_option,wp_mail) still cause impact
call_user_func_array.Escalate to full code execution and data access
wp-config.php database credentials, export the database, and pivot into the hosting network if not segmented.- New admin login works (2FA for admins may be enabled but is bypassed because attacker controls admin account)
- Web server allows plugin/theme file writes
- 2FA could delay login but attacker can disable it as admin via database or file edit
- Container/PHP-FPM limits may restrict writes; but typical WordPress admins can install plugins
openssl extension is loaded on all WordPress hosts — This breaks Method 2, leaving only the non-default shortcode_preview path. Roll out within 30 days under the noisgate mitigation SLA for HIGH severity. Verify with php -m | grep openssl.shortcode_preview on all ACF Extended PRO forms — This breaks Method 1, leaving only the rare no-OpenSSL path. Roll out within 30 days under the noisgate mitigation SLA for HIGH severity. Audit all forms and remove the setting.call_user_func_array injection path. Schedule within 180 days under the noisgate remediation SLA for HIGH severity, or immediately if a patch is released and active exploitation surfaces.- Disabling user registration or admin approval settings:
wp_insert_useris called directly via the vulnerable function, bypassing WordPress options. - Generic WAF rules keyed on common code injection patterns: the payload travels in form configuration parameters and may look like ordinary serialized/form data; plugin-specific rules are needed.
- WordPress two-factor authentication for admins: the exploitation path is unauthenticated, so it does not intercept the initial function call.
The supporting signals.
| In-the-wild status | No known exploitation in CISA KEV or public intel as of 2026-11-03; not currently observed in active campaigns. |
|---|---|
| Proof-of-concept availability | No explicit public PoC link in provided sources; Wordfence advisory by Tin Pham, Trong Pham, Hao Ngo describes mechanics but did not publish a weaponized exploit. |
| EPSS score | Not available in the provided search context; CVSS-only scoring overstates real-world reachability. |
| CISA KEV | Not listed; no known exploited-in-wild flag as of 2026-11-03. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — scores 9.8, assuming unconditional unauthenticated RCE. |
| Affected versions | All Advanced Custom Fields: Extended PRO versions up to and including 0.9.2.6. |
| Fixed version | Not clearly stated in advisory snippets; update to vendor patched release when listed (expected greater than 0.9.2.6). |
| Scanning/exposure data | No GreyNoise/Shodan/Censys data in provided results; WordPress is high-volume but this is a paid PRO plugin, so Internet-exposed installation count is lower than core ACF. |
| Disclosure date | Wordfence published 2026-09-14; NVD/CIRCL lists 2026-11-03. |
| Reporting researcher/org | Tin Pham (TF1T), Trong Pham (dtro), Hao Ngo via Wordfence. |
Sources.
Why this verdict
- Baseline: vendor severity is CRITICAL 9.8; the underlying
call_user_func_arrayinjection is real and unauthenticated. - Friction audit: Method 1 needs the non-default
shortcode_previewsetting enabled by an admin; Method 2 needs PHPopensslabsent—both conditions shrink the exposed subset well below the full WordPress population; I apply one downgrade step from 9.8 to 8.0. - Impact when reached: the attacker can call
wp_insert_userwith controlled arguments to create an administrator account, then install a plugin to gain full PHP code execution; that is HIGH impact but conditional reach. - Role multiplier: typical deployment is a public-facing WordPress/LAMP host; worst plausible high-value role is a large WooCommerce/membership/intranet CMS where post-exploitation enables mass data egress but not domain/fleet/supply-chain takeover; not a canonical high-value-role component, so no CRITICAL floor.
Why not higher?
CRITICAL would require near-unconditional unauthenticated reach and/or canonical high-value role. Here both exploitation methods are conditional: shortcode_preview is not default and OpenSSL absence is uncommon; paid PRO plugin further narrows the installed base; no KEV/in-wild evidence.
Why not lower?
MEDIUM or LOW would treat random function-call code injection as a minor issue, but when conditions are met it yields instant unauthenticated admin creation and full site takeover. Public-facing WordPress sites are attractive and the default admin capability makes escalation reliable; so HIGH is the floor.
Crowdsourced verification payload.
Run on the WordPress host (or any host with read access to the wp-content directory), as a user that can read plugin files. Invoke e.g. bash check_acfe_pro.sh /var/www/html for a typical Linux WordPress document root; no special privileges needed. The script outputs VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env bash
# Check if ACF Extended PRO plugin version is <= 0.9.2.6
set -euo pipefail
WP_ROOT="${1:-/var/www/html}"
PLUGIN_DIR="$WP_ROOT/wp-content/plugins/acf-extended-pro"
if [ ! -d "$PLUGIN_DIR" ]; then
echo "UNKNOWN: plugin directory not found"
exit 0
fi
VERSION=$(grep -Rhoi 'Version:[[:space:]]*[0-9][0-9.]*' "$PLUGIN_DIR" 2>/dev/null | head -1 | sed -E 's/[Vv]ersion:[[:space:]]*//')
if [ -z "$VERSION" ]; then
echo "UNKNOWN: could not extract version"
exit 0
fi
LATEST_BAD="0.9.2.6"
MAX=$(printf '%s\n' "$VERSION" "$LATEST_BAD" | sort -V | tail -1)
if [ "$MAX" = "$LATEST_BAD" ]; then
echo "VULNERABLE: $VERSION"
exit 1
else
echo "PATCHED: $VERSION"
exit 0
fi