CVE-2026-32349

Embed PDF Viewer <= 2.4.7 - Authenticated (Contributor+) Server-Side Request Forgery

mediumServer-Side Request Forgery (SSRF)
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.4.8
Patched in
84d
Time to patch

Description

The Embed PDF Viewer plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 2.4.7. This makes it possible for authenticated attackers, with Contributor-level access and above, to make web requests to arbitrary locations originating from the web application which can be used to query and modify information from internal services.

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<=2.4.7
PublishedFebruary 11, 2026
Last updatedMay 5, 2026
Affected pluginembed-pdf-viewer

What Changed in the Fix

Changes introduced in v2.4.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-32349 (Embed PDF Viewer SSRF) ## 1. Vulnerability Summary The **Embed PDF Viewer** plugin (versions <= 2.4.7) is vulnerable to **Authenticated Server-Side Request Forgery (SSRF)**. The vulnerability exists in the plugin's oEmbed handler, which attempts to fetc…

Show full research plan

Exploitation Research Plan: CVE-2026-32349 (Embed PDF Viewer SSRF)

1. Vulnerability Summary

The Embed PDF Viewer plugin (versions <= 2.4.7) is vulnerable to Authenticated Server-Side Request Forgery (SSRF). The vulnerability exists in the plugin's oEmbed handler, which attempts to fetch and process remote PDF files to generate metadata. Specifically, the oembed_pdf_viewer method in the Embed_PDF_Viewer class fails to validate that the provided URL belongs to a trusted external domain or is not an internal network address before passing it to wp_remote_get().

2. Attack Vector Analysis

  • Vulnerable Endpoint: wp-admin/admin-ajax.php (via the oEmbed system) or the WordPress REST API oEmbed proxy.
  • Triggering Mechanism: Pasting a URL matching the pattern #(^(https?)\:\/\/.+\.pdf$)#i into a WordPress post/page or querying the oEmbed proxy directly.
  • Required Role: Contributor or higher (any role capable of creating posts or triggering oEmbed previews).
  • Payload Parameter: url (in the context of an oEmbed request).
  • Preconditions: The plugin must be active. The attacker needs valid credentials for a Contributor-level account.

3. Code Flow

  1. Registration: In embed-pdf-viewer.php, the plugin registers an oEmbed handler during execution:
    wp_embed_register_handler(
        'oembed_pdf_viewer',
        '#(^(https?)\:\/\/.+\.pdf$)#i',
        [ Embed_PDF_Viewer::instance( $epd_version ), 'oembed_pdf_viewer' ]
    );
    
  2. Entry Point: When WordPress encounters a URL ending in .pdf, it invokes the registered callback oembed_pdf_viewer($matches, $atts, $url).
  3. Vulnerable Logic: Inside Embed_PDF_Viewer::oembed_pdf_viewer:
    • The code first validates the URL format using filter_var( $url, FILTER_VALIDATE_URL ). This only checks for valid URI syntax, not the destination.
    • It then attempts to find the attachment in the local Media Library.
    • If not found locally, it enters the vulnerable branch:
    /*
     * URL is from outside of the Media Library.
     */
    $response = wp_remote_get( $url ); // <--- SSRF SINK
    if ( is_wp_error( $response ) ) {
        return $response;
    }
    
  4. Sink: wp_remote_get( $url ) is called directly with the user-supplied $url. This causes the WordPress server to initiate an HTTP GET request to the specified target.

4. Nonce Acquisition Strategy

The most efficient way to trigger this SSRF and observe the result is via the WordPress REST API oEmbed proxy. This endpoint requires a REST API nonce (wp_rest action).

  1. Identify Shortcode/Script: The plugin registers the embed-pdf-viewer/pdf block. Navigating to the post editor (post-new.php) will load the necessary scripts.
  2. Navigation: Log in as a Contributor and navigate to /wp-admin/post-new.php.
  3. Extraction: Use browser_eval to extract the REST nonce from the global wpApiSettings object, which WordPress core provides to the block editor:
    • JS Command: browser_eval("window.wpApiSettings?.nonce")
  4. Fallback: If wpApiSettings is unavailable, search the page source for the string "nonce":"... associated with the REST API.

5. Exploitation Strategy

We will use the REST API oEmbed proxy to trigger the server-side request.

  1. Target URL Construction: The URL must end in .pdf to satisfy the plugin's regex. To target internal services (e.g., a metadata service or a local port), use:
    • http://127.0.0.1:80/.pdf
    • http://169.254.169.254/latest/meta-data/.pdf
  2. HTTP Request:
    • Method: GET
    • URL: http://localhost:8080/wp-json/oembed/1.0/proxy
    • Query Parameters:
      • url: http://TARGET_INTERNAL_IP/.pdf
      • _wpnonce: [EXTRACTED_REST_NONCE]
  3. Agent Instruction: Use http_request to send this GET request.
  4. Analysis: If the server returns a 200 OK or a WP_Error containing details about the internal service response, the SSRF is confirmed.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Internal Listener: (Simulated) Ensure there is a service running on an internal port (e.g., port 80 or 22) that the WordPress server can reach.

7. Expected Results

  • Success Indicator: The WordPress server makes an outbound request to the target IP.
  • Response: The REST API response may contain headers or body content from the internal service if the service returns a 200 and the plugin attempts to read the content-type.
  • Error Case: If the internal service rejects the connection, the plugin might return a WP_Error object (visible in the REST response) saying "connect() timed out" or "Connection refused," which still confirms the server attempted the connection.

8. Verification Steps

  1. Monitor Logs: Check the access logs of the internal service to see if the WordPress server's IP address appears.
  2. WP-CLI Check: Verify the plugin version to ensure it is 2.4.7 or lower:
    • wp plugin get embed-pdf-viewer --field=version
  3. Observation: Compare the response time of a request to an existing internal port vs. a non-existent internal port. A difference in timing or error message confirms the server is probing the internal network.

9. Alternative Approaches

If the REST API proxy is restricted:

  1. Post Preview Method:
    • Create a draft post as a Contributor.
    • Set the post content to the malicious URL on its own line: http://127.0.0.1:22/test.pdf
    • Save the draft and use the "Preview" functionality.
    • This triggers the wp_embed_register_handler logic during the content rendering process.
  2. Shortcode Method:
    • Use the [embed] shortcode: [embed]http://127.0.0.1:22/test.pdf[/embed]
    • View the post preview.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Embed PDF Viewer plugin for WordPress is vulnerable to Authenticated Server-Side Request Forgery (SSRF) via its oEmbed handler in versions up to 2.4.7. The plugin uses wp_remote_get() to fetch metadata for external PDF files without validating if the target URL resolves to an internal or restricted network address, allowing attackers with Contributor-level access or higher to probe internal services.

Vulnerable Code

// embed-pdf-viewer.php lines 34-41
wp_embed_register_handler(
	'oembed_pdf_viewer',
	'#(^(https?)\:\/\/.+\.pdf$)#i',
	[
		Embed_PDF_Viewer::instance( $epd_version ),
		'oembed_pdf_viewer',
	]
);

---

// embed-pdf-viewer.php lines 182-198
	public function oembed_pdf_viewer( $matches, $atts, $url ) {
		// Validate URL before proceeding.
		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			return new WP_Error( 'invalid_url', __( 'The URL provided is not valid.', 'embed-pdf-viewer' ) );
		}

		$attachment_id = $this->get_attachment_id_by_url( $url );
		if ( ! empty( $attachment_id ) ) {
			$post = get_post( $this->get_attachment_id_by_url( $url ) );
		} else {
			/*
			 * URL is from outside of the Media Library.
			 */
			$response = wp_remote_get( $url );
			if ( is_wp_error( $response ) ) {
				return $response;
			}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/embed-pdf-viewer/2.4.7/embed-pdf-viewer.php /home/deploy/wp-safety.org/data/plugin-versions/embed-pdf-viewer/2.4.8/embed-pdf-viewer.php
--- /home/deploy/wp-safety.org/data/plugin-versions/embed-pdf-viewer/2.4.7/embed-pdf-viewer.php	2026-02-05 15:58:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/embed-pdf-viewer/2.4.8/embed-pdf-viewer.php	2026-02-21 16:59:32.000000000 +0000
@@ -14,7 +14,7 @@
  * Description:       Embed a PDF from the Media Library or elsewhere via oEmbed or as a block into an `iframe` tag.
  * Author:            Andy Fragen
  * Author URI:        https://github.com/afragen
- * Version:           2.4.7
+ * Version:           2.4.8
  * License:           GPLv2+
  * Domain Path:       /languages
  * Text Domain:       embed-pdf-viewer
@@ -180,11 +180,6 @@
 	 * @return string|WP_Error
 	 */
 	public function oembed_pdf_viewer( $matches, $atts, $url ) {
-		// Validate URL before proceeding.
-		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
-			return new WP_Error( 'invalid_url', __( 'The URL provided is not valid.', 'embed-pdf-viewer' ) );
-		}
-
 		$attachment_id = $this->get_attachment_id_by_url( $url );
 		if ( ! empty( $attachment_id ) ) {
 			$post = get_post( $this->get_attachment_id_by_url( $url ) );
@@ -192,7 +187,7 @@
 			/*
 			 * URL is from outside of the Media Library.
 			 */
-			$response = wp_remote_get( $url );
+			$response = wp_safe_remote_get( $url );
 			if ( is_wp_error( $response ) ) {
 				return $response;
 			}

Exploit Outline

An authenticated attacker with Contributor-level privileges can exploit this vulnerability by submitting a request to the WordPress REST API oEmbed proxy endpoint. The attacker needs to obtain a valid REST nonce (e.g., from the post editor page) and then send a GET request to `/wp-json/oembed/1.0/proxy` with the `url` parameter set to an internal service URI ending in `.pdf` (e.g., `http://169.254.169.254/latest/meta-data/.pdf`). The plugin's oEmbed handler will process this URL and use `wp_remote_get()` to fetch the resource, causing the server to perform an internal request. Alternatively, the attacker can trigger the same logic by simply pasting the malicious URL into a new post and viewing the preview.

Check if your site is affected.

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