CVE-2026-48878

Visual Link Preview <= 2.4.1 - Authenticated (Subscriber+) Information Exposure

mediumExposure of Sensitive Information to an Unauthorized Actor
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.4.2
Patched in
10d
Time to patch

Description

The Visual Link Preview plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 2.4.1. This makes it possible for authenticated attackers, with Subscriber-level access and above, to extract sensitive user or configuration data.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.4.1
PublishedJune 2, 2026
Last updatedJune 11, 2026
Affected pluginvisual-link-preview

What Changed in the Fix

Changes introduced in v2.4.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the technical steps to exploit **CVE-2026-48878**, an Information Exposure vulnerability in the **Visual Link Preview** WordPress plugin (<= 2.4.1). ### 1. Vulnerability Summary The `vlp_get_template` AJAX action in `VLP_Template_Manager` allows authenticated users to re…

Show full research plan

This research plan outlines the technical steps to exploit CVE-2026-48878, an Information Exposure vulnerability in the Visual Link Preview WordPress plugin (<= 2.4.1).

1. Vulnerability Summary

The vlp_get_template AJAX action in VLP_Template_Manager allows authenticated users to retrieve a preview of any post by providing its ID. The underlying logic in VLP_Link (the class processing the request) fails to verify if the current user has the necessary permissions to read the post being previewed. Consequently, any authenticated user with at least Subscriber level access can extract the title and content/summary of private, password-protected, or draft posts. Additionally, the plugin localizes sensitive configuration data (such as API keys for external URL providers) into the vlp_admin and vlp_blocks JavaScript objects, making them visible to all logged-in users who can access the admin dashboard or block editor.

2. Attack Vector Analysis

  • Vulnerable Endpoint: /wp-admin/admin-ajax.php
  • Vulnerable AJAX Action: vlp_get_template
  • Required Nonce: vlp (Action: vlp)
  • Authentication: Subscriber-level access or higher.
  • Preconditions: The attacker must be logged in to obtain a valid nonce from the admin dashboard (e.g., the Profile page).

3. Code Flow

  1. Registration: In includes/public/class-vlp-template-manager.php, the plugin registers the AJAX handler:
    add_action( 'wp_ajax_vlp_get_template', array( __CLASS__, 'ajax_get_template' ) );.
  2. Entry Point: When a request is sent to vlp_get_template, VLP_Template_Manager::ajax_get_template() is executed.
  3. Nonce Check: The function verifies the vlp nonce:
    check_ajax_referer( 'vlp', 'security', false ).
  4. Parsing: It retrieves the encoded parameter from $_POST, which is a Base64-encoded JSON object.
  5. Object Creation: new VLP_Link( $encoded ) is instantiated. This class (logic inferred from usage) parses the JSON to set attributes like type and post.
  6. Information Retrieval: It calls $link->output(), which fetches the post content corresponding to the post ID attribute without performing a current_user_can('read_post', $post_id) check.
  7. Exposure: The rendered preview containing the private post's data is returned via wp_send_json_success.

4. Nonce Acquisition Strategy

Since the vlp nonce is used for admin-side functionality, it is localized via wp_localize_script in VLP_Assets::enqueue() (for the admin dashboard) and VLP_Assets::enqueue_blocks() (for Gutenberg).

  1. Login: Authenticate as a Subscriber.
  2. Navigate: Go to /wp-admin/profile.php (accessible to all logged-in users).
  3. Extract: Open the browser console and run:
    console.log(window.vlp_admin.nonce);
    
    Alternatively, search the page source for vlp_admin to find the JSON object containing the nonce.

5. Exploitation Strategy

The goal is to retrieve the content of a private post (e.g., Post ID 1337).

  1. Craft the Payload: Create a JSON object specifying an internal post type and the target ID.
    {"type": "internal", "post": 1337}
    
  2. Encode the Payload: Convert the JSON to Base64.
    • Example: eyJ0eXBlIjogImludGVybmFsIiwgInBvc3QiOiAxMzM3fQ==
  3. Send the HTTP Request: Use the http_request tool to trigger the AJAX action.
    • Method: POST
    • URL: http://[TARGET]/wp-admin/admin-ajax.php
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body: action=vlp_get_template&security=[NONCE]&encoded=eyJ0eXBlIjogImludGVybmFsIiwgInBvc3QiOiAxMzM3fQ==

6. Test Data Setup

  1. Create a private post with a unique title and sensitive content (e.g., "Secret Project Alpha - Login: admin/password123").
  2. Identify the ID of this post (e.g., 1337).
  3. Create a user with the Subscriber role.
  4. Ensure the Visual Link Preview plugin (version 2.4.1) is active.

7. Expected Results

  • The server returns a JSON response with success: true.
  • The data.template field contains HTML markup.
  • Within that HTML, the title and content of the private post (Post 1337) are visible (e.g., inside <h3> and .summary tags as defined in VLP_Shortcode::register_blocks).

8. Verification Steps

  1. Confirm Privacy: Using WP-CLI, verify the post is actually private:
    wp post get 1337 --field=post_status (Expected: private).
  2. Confirm Exposure: Check the template output from the HTTP response for the string "Secret Project Alpha".

9. Alternative Approaches

Configuration Exposure (API Keys):
If the plugin has a Microlink or LinkPreview API key configured, it may be exposed in the localized JS object.

  1. Navigate to /wp-admin/profile.php.
  2. In the browser console, check:
    console.log(window.vlp_admin.url_providers);
    
    Or check the source for the vlp_blocks object if the user has access to the post editor. Verify if any sensitive API keys are present in these objects.
Research Findings
Static analysis — not yet PoC-verified

Summary

The vlp_get_template AJAX action in the Visual Link Preview plugin fails to perform a capability check, allowing authenticated users with Subscriber-level access to retrieve previews of any post, including private or draft content, by providing its ID. Furthermore, sensitive configuration data such as API keys are localized into JavaScript objects accessible to all logged-in users, potentially exposing external service credentials.

Vulnerable Code

// includes/public/class-vlp-template-manager.php line 152
public static function ajax_get_template() {
	if ( check_ajax_referer( 'vlp', 'security', false ) ) {
		$encoded = isset( $_POST['encoded'] ) ? sanitize_text_field( wp_unslash( $_POST['encoded'] ) ) : ''; // Input var okay.
		$link = new VLP_Link( $encoded );

		$template = VLP_Template_Manager::get_template_by_slug( $link->template() );

		$output = '<style type="text/css">' . VLP_Template_Manager::get_template_css( $template ) . VLP_Template_Style::get_css() . '</style>';
		$output .= $link->output();

		wp_send_json_success( array(
			'template' => $output,
		) );
	}

	wp_die();
}

---

// includes/admin/class-vlp-assets.php line 83
wp_localize_script( 'vlp-blocks', 'vlp_blocks', array(
	'ajax_url' => admin_url( 'admin-ajax.php' ),
	'nonce' => wp_create_nonce( 'vlp' ),
	'templates' => VLP_Template_Manager::get_templates(),
	'edit_link' => admin_url( 'post.php?action=edit&post='),
	'settings_link' => admin_url( 'options-general.php?page=bv_settings_vlp' ),
	'url_providers' => VLP_Url_Provider_Manager::get_available_providers(),
));

Security Fix

--- includes/public/class-vlp-template-manager.php
+++ includes/public/class-vlp-template-manager.php
@@ -150,21 +150,29 @@
 	 * @since    1.0.0
 	 */
 	public static function ajax_get_template() {
-		if ( check_ajax_referer( 'vlp', 'security', false ) ) {
-			$encoded = isset( $_POST['encoded'] ) ? sanitize_text_field( wp_unslash( $_POST['encoded'] ) ) : ''; // Input var okay.
-			$link = new VLP_Link( $encoded );
-
-			$template = VLP_Template_Manager::get_template_by_slug( $link->template() );
-
-			$output = '<style type="text/css">' . VLP_Template_Manager::get_template_css( $template ) . VLP_Template_Style::get_css() . '</style>';
-			$output .= $link->output();
-
-			wp_send_json_success( array(
-				'template' => $output,
-			) );
+		if ( ! check_ajax_referer( 'vlp', 'security', false ) ) {
+			wp_send_json_error( array(
+				'message' => __( 'Invalid security token.', 'visual-link-preview' ),
+			), 403 );
 		}
 
-		wp_die();
+		if ( ! current_user_can( 'edit_posts' ) ) {
+			wp_send_json_error( array(
+				'message' => __( 'You do not have permission to preview templates.', 'visual-link-preview' ),
+			), 403 );
+		}
+
+		$encoded = isset( $_POST['encoded'] ) ? sanitize_text_field( wp_unslash( $_POST['encoded'] ) ) : ''; // Input var okay.
+		$link = new VLP_Link( $encoded );
+
+		$template = VLP_Template_Manager::get_template_by_slug( $link->template() );
+
+		$output = '<style type="text/css">' . VLP_Template_Manager::get_template_css( $template ) . VLP_Template_Style::get_css() . '</style>';
+		$output .= $link->output();
+
+		wp_send_json_success( array(
+			'template' => $output,
+		) );
 	}

Exploit Outline

1. Authenticate to the WordPress site as a user with at least Subscriber-level privileges. 2. Retrieve the required 'vlp' AJAX nonce by inspecting the page source of an administrative page (like /wp-admin/profile.php) or by checking the 'vlp_admin.nonce' value in the browser console. 3. Target a private or draft post by its numeric ID. 4. Prepare a JSON object: {"type": "internal", "post": [TARGET_ID]}. 5. Base64-encode this JSON string. 6. Execute an AJAX request to /wp-admin/admin-ajax.php with the POST parameters: 'action' set to 'vlp_get_template', 'security' set to the retrieved nonce, and 'encoded' set to the Base64-encoded JSON. 7. The response will contain the HTML-rendered preview of the private post, exposing its title and summary.

Check if your site is affected.

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