Advanced Custom Fields (ACF®) <= 6.8.1 - Unauthenticated Arbitrary Post Modification via Front-End Form '_post_title' and '_post_content' Parameters
Description
The Advanced Custom Fields (ACF®) plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 6.8.1. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for unauthenticated attackers to overwrite the post_title and post_content of any post bound to a publicly accessible acf_form() instance by injecting values into the _post_title and _post_content parameters of a form submission request.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=6.8.1What Changed in the Fix
Changes introduced in v6.8.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-8382 (ACF Post Modification) ## 1. Vulnerability Summary The Advanced Custom Fields (ACF) plugin (<= 6.8.1) contains a missing authorization vulnerability in its front-end form handling logic. While `acf_form()` is designed to allow developers to create limite…
Show full research plan
Exploitation Research Plan: CVE-2026-8382 (ACF Post Modification)
1. Vulnerability Summary
The Advanced Custom Fields (ACF) plugin (<= 6.8.1) contains a missing authorization vulnerability in its front-end form handling logic. While acf_form() is designed to allow developers to create limited front-end editing interfaces, the processing logic in includes/forms/form-front.php fails to verify if the specific fields _post_title and _post_content were intended to be part of the form configuration. An unauthenticated attacker can inject these parameters into a legitimate form submission to modify the title and content of the post associated with that form.
2. Attack Vector Analysis
- Endpoint: The URL of any public-facing page where
acf_form()is rendered. - Vulnerable Action: POST request to the page URL containing ACF form submission data.
- Parameters:
acf[_post_title]andacf[_post_content]. - Authentication: None (unauthenticated), provided the page containing the form is public.
- Preconditions: A post must be "bound" to a front-end
acf_form(). This happens whenacf_form()is called with a specificpost_idor when it defaults to the current loop's post.
3. Code Flow
- Entry Point: When a user submits an ACF form, the request is processed during WordPress initialization (typically
wp_loaded). - Registration: In
includes/forms/form-front.php,acf_form_front::validate_save_post()is called via theacf/validate_save_posthook. - Field Injection:
validate_save_post()callsget_default_fields(), which returns definitions for_post_titleand_post_content. - Local Field Registration: The function iterates through these defaults. If
$_POST['acf']['_post_title']is set, it callsacf_add_local_field(). This effectively "whitelists" the injected field for the current request execution. - Processing: Later,
acf_form_front::pre_save_post()is triggered via theacf/pre_save_postfilter. - Data Extraction: Inside
pre_save_post(), the code checksisset( $_POST['acf']['_post_title'] ). If present, it extracts the value usingacf_extract_var()and assigns it to$save['post_title']. - Post Update: The
$savearray is eventually passed towp_update_post(), committing the changes to the database.
4. Nonce Acquisition Strategy
ACF front-end forms require a nonce for submission. The nonce and form configuration are rendered into the HTML.
- Identify the Form: Navigate to the page containing the
acf_form(). - Scrape Nonce and Settings:
- The nonce is stored in a hidden input field:
<input type="hidden" name="_acf_nonce" value="[NONCE_VALUE]">. - The encrypted/encoded form settings are in:
<input type="hidden" name="_acf_form" value="[ENCODED_SETTINGS]">.
- The nonce is stored in a hidden input field:
- Browser Evaluation:
// Extract via browser_eval const data = { nonce: document.querySelector('input[name="_acf_nonce"]')?.value, form: document.querySelector('input[name="_acf_form"]')?.value }; return data; - Note: The nonce action used by ACF for front-end forms is typically generated internally based on the form arguments. Since we are scraping it from a legitimate rendering, the action will match.
5. Exploitation Strategy
- Preparation: Identify a public page with an
acf_form. Let's assume the URL ishttp://localhost:8080/submit-ticket/. - Capture Credentials: Navigate to the page and extract the
_acf_nonceand_acf_formvalues. - Formulate Payload:
_acf_nonce: [Extracted Value]_acf_form: [Extracted Value]acf[_post_title]:VANDALIZED BY CVE-2026-8382acf[_post_content]:This post content has been modified via unauthenticated parameter injection.
- Execute Request: Use
http_requestto send a POST request to the page URL.- Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
_acf_nonce=[NONCE]&_acf_form=[FORM]&acf[_post_title]=Vandalized&acf[_post_content]=Pwned
- Method:
- Verify: Check if the post title has changed.
6. Test Data Setup
To simulate a vulnerable environment:
- Create a Target Post:
wp post create --post_type=post --post_title="Original Title" --post_content="Original Content" --post_status=publish # Assume this returns ID 123 - Create a Public Form Page: Use a PHP snippet (e.g., via a custom page template or a plugin that allows PHP) to call
acf_form().// Example PHP to be placed in a page template acf_form_head(); // Required at the top of the file acf_form([ 'post_id' => 123, 'fields' => [], // Intentionally empty to show that injection works regardless of displayed fields 'submit_value' => 'Update' ]); - Ensure Accessibility: Ensure the page is published and accessible to logged-out users.
7. Expected Results
- The
http_requestshould return a302 Redirect(standard ACF behavior after successful submission) or a200 OKwith a "Post updated" message. - The post with the target ID should have its
post_titlechanged to the injected value.
8. Verification Steps
- Check Post State:
wp post get 123 --field=post_title wp post get 123 --field=post_content - Confirmation: If the output matches the injected payload, the vulnerability is confirmed.
9. Alternative Approaches
- Honeypot Bypass: If the form fails, check if the honeypot
_validate_email(found inget_default_fields()) is causing a rejection. Ensureacf[_validate_email]is sent as an empty string or omitted. - Query String Nonce: Some ACF configurations might look for the nonce in the URL if not in the POST body, though this is rare for forms.
- Multiple Form IDs: If a page has multiple
acf_form()calls, ensure the_acf_formsettings parameter matches the specific form instance targeting the desiredpost_id.
Summary
Advanced Custom Fields (ACF) up to 6.8.1 is vulnerable to unauthenticated post modification. This occurs because front-end forms (acf_form) do not verify if internal fields like '_post_title' and '_post_content' are explicitly enabled in the form configuration, allowing attackers to inject these parameters and overwrite post data.
Vulnerable Code
/* includes/forms/form-front.php:236 */ function validate_save_post() { // register field if isset in $_POST foreach ( $this->get_default_fields() as $k => $field ) { // bail early if no in $_POST if ( ! isset( $_POST['acf'][ $k ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere. continue; } // register acf_add_local_field( $field ); } --- /* includes/forms/form-front.php:278 */ function pre_save_post( $post_id, $form ) { // ... // save post_title if ( isset( $_POST['acf']['_post_title'] ) ) { $save['post_title'] = acf_extract_var( $_POST['acf'], '_post_title' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved. } // save post_content if ( isset( $_POST['acf']['_post_content'] ) ) { $save['post_content'] = acf_extract_var( $_POST['acf'], '_post_content' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved. } // ...
Security Fix
@@ -278,11 +278,11 @@ // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified in check_submit_form(). // save post_title - if ( isset( $_POST['acf']['_post_title'] ) ) { + if ( isset( $_POST['acf']['_post_title'] ) && ! empty( $form['post_title'] ) ) { $save['post_title'] = acf_extract_var( $_POST['acf'], '_post_title' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved. } // save post_content - if ( isset( $_POST['acf']['_post_content'] ) ) { + if ( isset( $_POST['acf']['_post_content'] ) && ! empty( $form['post_content'] ) ) { $save['post_content'] = acf_extract_var( $_POST['acf'], '_post_content' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by WP when saved. }
Exploit Outline
1. Identify a public WordPress page that renders an ACF front-end form using acf_form(). 2. Extract the required hidden field values from the HTML source: `_acf_nonce` (the security token) and `_acf_form` (the encoded form configuration). 3. Prepare a POST request to the page's URL containing the extracted `_acf_nonce` and `_acf_form` values. 4. Inject the malicious parameters `acf[_post_title]` and `acf[_post_content]` into the POST body with the desired modified content. 5. Send the request. The plugin processes the submission, identifies the injected fields as 'default' fields, and updates the post associated with the form ID regardless of whether the developer intended to allow title or content edits.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.