CVE-2026-39616

Download Attachments <= 1.4.0 - Unauthenticated Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Download Attachments plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.4.0 due to missing validation on a user controlled key. This makes it possible for unauthenticated attackers to perform unauthorized actions.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.4.0
PublishedFebruary 10, 2026
Last updatedJune 18, 2026
Affected plugindownload-attachments
Research Plan
Unverified

## Vulnerability Summary The **Download Attachments** plugin for WordPress (versions <= 1.4.0) is vulnerable to an **Insecure Direct Object Reference (IDOR)**. The vulnerability exists because the plugin's download handler (typically processing requests via a query variable or AJAX) does not suffici…

Show full research plan

Vulnerability Summary

The Download Attachments plugin for WordPress (versions <= 1.4.0) is vulnerable to an Insecure Direct Object Reference (IDOR). The vulnerability exists because the plugin's download handler (typically processing requests via a query variable or AJAX) does not sufficiently validate whether the user has permission to access the requested attachment ID. Specifically, it fails to check the "Post Status" (e.g., Private, Draft, Pending) of the parent post associated with the attachment, allowing unauthenticated users to download files that should be restricted.

The "user-controlled key" refers to the attachment ID parameter (likely download_id or attachment_id) provided in the request.

Attack Vector Analysis

  • Endpoint: The vulnerability is reachable via a GET request to the WordPress frontend using a specific query parameter or via an AJAX action.
  • Vulnerable Parameter: download_id (or da_id / id depending on the exact implementation).
  • Authentication: Unauthenticated (No login required).
  • Preconditions: An attachment must exist that is associated with a restricted post (e.g., a "Private" post).

Code Flow

The trace assumes the standard structure of the dFactory Download Attachments plugin:

  1. Entry Point: The plugin registers a hook to monitor frontend requests, usually in includes/class-frontend.php.
    • Hook: add_action( 'init', array( $this, 'download_attachment' ) ); or template_redirect.
  2. Trigger: The download_attachment() function checks for the existence of a specific $_GET variable.
    • Code: if ( isset( $_GET['download_id'] ) ) { ... }
  3. Processing: The function retrieves the attachment ID from the "user-controlled key" $_GET['download_id'].
  4. Vulnerable Sink: The function fetches the attachment details:
    • $attachment_id = (int) $_GET['download_id'];
    • $file_path = get_attached_file( $attachment_id );
  5. Missing Validation: The code proceeds to stream the file or redirect to the file URL without calling get_post_status() on the attachment's parent post or verifying current_user_can( 'read_post', $parent_id ).
  6. Response: The server sends the restricted file content to the unauthenticated attacker.

Nonce Acquisition Strategy

While many IDORs in download plugins are direct GET requests without nonces (to support direct links), if an AJAX handler is used, the plugin typically localizes a nonce.

  1. Shortcode Identification: The plugin uses the [download-attachments] shortcode to display attachment lists.
  2. Page Creation: Create a public page containing this shortcode to force the plugin to enqueue its frontend scripts and localizer.
    • wp post create --post_type=page --post_status=publish --post_content='[download-attachments]'
  3. Localization Variable: The plugin localizes data into a JS object, often named da_frontend_args or da_args.
  4. Extraction:
    • Navigate to the created page.
    • Run browser_eval("window.da_frontend_args?.nonce") (inferred key name) to retrieve the nonce if required for the request.
    • Note: If the vulnerability is in the GET query parameter handler (e.g., ?download_id=X), a nonce is likely not required.

Exploitation Strategy

Step 1: Discover Restricted Attachment ID

Since this is an IDOR, we need a valid ID of an attachment belonging to a private post. In a real-world scenario, this is done via enumeration (ID 1, 2, 3...). For the PoC, we will create a private post and find its attachment ID.

Step 2: Perform the IDOR Attack

Request the attachment directly as an unauthenticated user.

  • Request Method: GET
  • URL: http://localhost:8080/?download_id=[ATTACHMENT_ID]
  • Alternative URL (AJAX): http://localhost:8080/wp-admin/admin-ajax.php?action=da_download_attachment&id=[ATTACHMENT_ID]

Step 3: Verify Access

If the response is a 200 OK and contains the file content (or a 302 redirect to the file path), the bypass is successful.

Test Data Setup

  1. Create a Secret File: Upload a text file named secret_data.txt to the Media Library.
  2. Create a Private Post:
    • wp post create --post_type=post --post_status=private --post_title="Sensitive Internal Info" --post_content="This post is hidden."
  3. Attach File to Private Post:
    • Get the Post ID of the private post.
    • Get the Attachment ID of secret_data.txt.
    • The "Download Attachments" plugin usually stores metadata in the _da_attachments post meta or similar. However, for a simple IDOR, just having the attachment exist in the database is often enough.
    • Ensure the attachment is registered within the plugin's system if it uses a custom table.

Expected Results

  • Vulnerable Version: The request to /?download_id=[ID] returns the contents of secret_data.txt or redirects to its URL, despite the parent post being "Private".
  • Fixed Version: The request returns a 403 Forbidden, 404 Not Found, or redirects to the homepage.

Verification Steps

  1. Observe Response: Check if the HTTP response body matches the content of the secret_data.txt file created during setup.
  2. Check Logs: Use wp-cli to verify no authentication cookies were sent in the request logs (if available).
  3. Verify Object Ownership:
    • wp post get [ATTACHMENT_ID] --field=post_parent
    • Check parent status: wp post get [PARENT_ID] --field=post_status (should be private).

Alternative Approaches

If the ?download_id parameter does not work:

  1. Check Query Vars: Check if the plugin registers a custom query var.
    • grep -r "add_filter.*query_vars" .
    • Example: /?da_id=[ID] or /?download=[ID].
  2. Check AJAX Handlers:
    • grep -r "wp_ajax_nopriv_da_download" .
    • Payload: action=da_download_attachment&id=[ID]&nonce=[NONCE]
  3. Check Unique Hashes: Some versions use a display_id (a hash). If the plugin allows fallback to the numeric ID when the hash is missing, that is a common bypass vector. Try providing the numeric ID in place of a hash.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Download Attachments plugin for WordPress is vulnerable to an unauthenticated Insecure Direct Object Reference (IDOR) because its download handler does not verify the visibility of the parent post associated with a requested attachment. This allows attackers to bypass access controls and download files that are intended to be restricted, such as those attached to 'Private' or 'Draft' posts.

Vulnerable Code

// includes/class-frontend.php
// Inferred logic from plugin init/template_redirect hook
if ( isset( $_GET['download_id'] ) ) {
    $attachment_id = (int) $_GET['download_id'];
    $file_path = get_attached_file( $attachment_id );
    // ... Missing authorization check for parent post status ...
}

Security Fix

--- includes/class-frontend.php
+++ includes/class-frontend.php
@@ -100,6 +100,11 @@
         if ( isset( $_GET['download_id'] ) ) {
             $attachment_id = (int) $_GET['download_id'];
 
+            $parent_id = wp_get_post_parent_id( $attachment_id );
+            if ( ! current_user_can( 'read_post', $parent_id ) && get_post_status( $parent_id ) !== 'publish' ) {
+                wp_die( __( 'You do not have permission to download this file.', 'download-attachments' ), 403 );
+            }
+
             $file_path = get_attached_file( $attachment_id );
             // ... proceed with download ...

Exploit Outline

The vulnerability is exploited via a direct GET request to the site's frontend. An unauthenticated attacker identifies or enumerates numeric IDs of attachments. By appending '?download_id=[ID]' to the site's base URL (or targeting the 'da_download_attachment' AJAX action), the attacker triggers the file download handler. Because the plugin does not verify if the parent post of the attachment is public or if the user has permission to read it, the server serves the restricted file content to the attacker.

Check if your site is affected.

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