CVE-2025-12067

Table Field Add-on for ACF and SCF <= 1.3.30 - Authenticated (Contributor+) Stored Cross-Site Scripting via Table Cell Content

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.3.31
Patched in
1d
Time to patch

Description

The Table Field Add-on for ACF and SCF plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Table Cell Content in all versions up to, and including, 1.3.30 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Author-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.3.30
PublishedJanuary 5, 2026
Last updatedJanuary 6, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-12067 (Stored XSS in Table Field Add-on for ACF) ## 1. Vulnerability Summary The **Table Field Add-on for ACF and SCF** plugin (<= 1.3.30) fails to properly sanitize and escape table cell content. When a user with "Contributor" or higher permissions creates or…

Show full research plan

Exploitation Research Plan: CVE-2025-12067 (Stored XSS in Table Field Add-on for ACF)

1. Vulnerability Summary

The Table Field Add-on for ACF and SCF plugin (<= 1.3.30) fails to properly sanitize and escape table cell content. When a user with "Contributor" or higher permissions creates or edits a post containing an ACF Table field, they can inject malicious JavaScript into individual cells. This script is then stored in the database as post metadata and executed in the context of any user (including Administrators) who views the post on the frontend or edits it in the backend.

2. Attack Vector Analysis

  • Endpoint: wp-admin/post.php
  • Action: editpost (standard WordPress post update)
  • Vulnerable Parameter: acf[field_key] (where the table data is sent, typically as a JSON-encoded string or a nested array structure).
  • Required Authentication: Contributor-level access or higher.
  • Preconditions:
    1. Advanced Custom Fields (ACF) or Smart Custom Fields (SCF) must be active.
    2. A Field Group must exist containing a "Table" field type.
    3. The Field Group must be assigned to a post type accessible by the Contributor (typically "Post").

3. Code Flow (Inferred)

  1. Input: The user submits a post update via the WordPress admin interface. The $_POST data includes an acf array containing the table field's data.
  2. Processing: The plugin (likely via a class such as acf_field_table) captures this data during the ACF save process. ACF's update_value logic is invoked.
  3. Storage: The data is stored in the wp_postmeta table under a meta key corresponding to the ACF field name.
  4. Sink (Rendering): When the post is viewed, the plugin's rendering function (likely render_field for admin or a template helper for frontend) retrieves the data. The plugin fails to use esc_html() or wp_kses() on the contents of the table cells before echoing them into the HTML table structure.

4. Nonce Acquisition Strategy

To exploit this via an automated script, we must obtain the standard WordPress post-edit nonce.

  1. Identify the Post: The agent should find an existing post owned by the Contributor or create a new one.
  2. Navigate: Use browser_navigate to wp-admin/post.php?post=POST_ID&action=edit.
  3. Extract Nonce: The nonce required for the editpost action is stored in the #_wpnonce hidden input field.
  4. Extract ACF Field Key: ACF uses unique keys (e.g., field_65abc123d456) to identify fields. To inject data correctly, we must find the key for the Table field on the page.
    • JS Extraction:
      // Use browser_eval to find the ACF table field key
      const tableField = document.querySelector('.acf-field[data-type="table"]');
      const fieldKey = tableField ? tableField.getAttribute('data-key') : null;
      return fieldKey;
      

5. Exploitation Strategy

Step 1: Login

Authenticate as a Contributor.

Step 2: Prepare Payload

The Table Field plugin typically stores data in a JSON format within the POST request.
Example structure (inferred):

{
  "p": {"caption": ""},
  "h": [{"c": "Header 1"}, {"c": "Header 2"}],
  "b": [[{"c": "<img src=x onerror=alert(document.domain)>"}, {"c": "Normal Cell"}]]
}

Step 3: Send Exploit Request

Use the http_request tool to send a POST request to wp-admin/post.php.

  • URL: https://TARGET/wp-admin/post.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: editpost
    • post_ID: [POST_ID]
    • _wpnonce: [EXTRACTED_NONCE]
    • acf[[FIELD_KEY]]: {"p":{"caption":""},"h":[],"b":[[{"c":"<script>alert('XSS')</script>"}]]} (JSON-encoded)

6. Test Data Setup

  1. Plugin Installation: Ensure advanced-custom-fields and advanced-custom-fields-table-field are active.
  2. Field Group Creation: Create an ACF Field Group named "Table Group".
  3. Field Creation: Add a field of type "Table" with the slug test_table_field.
  4. Assignment: Set the Field Group to show if "Post Type is equal to Post".
  5. User Creation: Create a user with the contributor role.
  6. Post Creation: Create a post as the contributor: wp post create --post_type=post --post_status=publish --post_author=[CONTRIBUTOR_ID] --post_title='Vulnerable Post'.

7. Expected Results

  • The POST request returns a 302 redirect back to the post edit page.
  • When an Administrator navigates to the post edit page or views the post on the frontend, the browser executes the injected script (alert('XSS')).
  • In a real-world scenario, the payload would be used to exfiltrate the Administrator's cookies or perform CSRF to create a new Admin user.

8. Verification Steps

  1. Check Meta Storage: Use WP-CLI to verify the payload is stored in the database.
    wp post meta get [POST_ID] test_table_field
    
    Expectation: The output should contain the raw <script> or onerror payload.
  2. Verify Rendering: Use http_request to fetch the post HTML and grep for the payload.
    # Fetch the post frontend
    # Look for the unescaped script tags
    

9. Alternative Approaches

  • ACF AJAX Autosave: If the standard editpost is restricted, attempt to use the acf/ajax/check_screen or wp_ajax_save_post hooks which ACF often uses for background saves.
  • Import/Export: If the plugin supports importing table data (e.g., via CSV), attempt to inject the XSS payload through the import functionality.
  • Different Payload Contexts: If <script> tags are stripped by a basic filter, try attribute-based XSS:
    • [{"c": "<b onmouseover=alert(1)>Hover me</b>"}]
    • [{"c": "<img src=x onerror=alert(1)>"}]
Research Findings
Static analysis — not yet PoC-verified

Summary

The Table Field Add-on for ACF and SCF plugin (<= 1.3.30) is vulnerable to Stored Cross-Site Scripting (XSS) due to a failure to sanitize and escape table cell content. Authenticated attackers with Contributor-level permissions or higher can inject malicious JavaScript into table fields, which then executes in the browsers of users viewing the post on the frontend or editing it in the backend.

Vulnerable Code

// File: fields/class-acf-field-table.php (Inferred)
// The plugin iterates through the table data and echoes cell content directly.

if ( ! empty( $value['b'] ) ) {
    foreach ( $value['b'] as $row ) {
        echo '<tr>';
        foreach ( $row as $cell ) {
            // The 'c' key contains the cell content which is output without escaping
            echo '<td>' . $cell['c'] . '</td>';
        }
        echo '</tr>';
    }
}

Security Fix

--- a/fields/class-acf-field-table.php
+++ b/fields/class-acf-field-table.php
@@ -150,7 +150,7 @@
 				if ( ! empty( $row ) ) {
 					foreach ( $row as $cell ) {
-						echo '<td>' . $cell['c'] . '</td>';
+						echo '<td>' . wp_kses_post( $cell['c'] ) . '</td>';
 					}
 				}
 				echo '</tr>';

Exploit Outline

To exploit this vulnerability, an attacker requires Contributor-level access to a WordPress site where Advanced Custom Fields (ACF) and the Table Field Add-on are active. 1. The attacker identifies or creates a post they have permission to edit. 2. The attacker retrieves the unique ACF field key for the 'Table' field type and the standard WordPress '_wpnonce' from the post edit screen. 3. The attacker submits a POST request to 'wp-admin/post.php' with the 'action' set to 'editpost'. 4. The payload is delivered via the 'acf' parameter, containing a JSON-encoded representation of the table. Specifically, the attacker injects a script (e.g., <img src=x onerror=alert(1)>) into the 'c' key within the table body ('b') array. 5. Upon saving, the payload is stored in the wp_postmeta table. The script executes whenever an administrator or other user views the post, potentially allowing for session hijacking or further privilege escalation.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.