CVE-2026-39698

The Publisher Desk ads.txt <= 1.5.0 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The The Publisher Desk ads.txt plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.5.0. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.5.0
PublishedFebruary 27, 2026
Last updatedApril 15, 2026
Research Plan
Unverified

This research plan targets CVE-2026-39698, a Missing Authorization vulnerability in "The Publisher Desk ads.txt" plugin (<= 1.5.0). This vulnerability typically allows an unauthenticated attacker to modify the site's `ads.txt` file, potentially redirecting ad revenue. ### 1. Vulnerability Summary T…

Show full research plan

This research plan targets CVE-2026-39698, a Missing Authorization vulnerability in "The Publisher Desk ads.txt" plugin (<= 1.5.0). This vulnerability typically allows an unauthenticated attacker to modify the site's ads.txt file, potentially redirecting ad revenue.

1. Vulnerability Summary

The "The Publisher Desk ads.txt" plugin provides an administrative interface to manage the ads.txt file requirements for ad networks. The vulnerability exists because the plugin registers a function—likely to save settings or update the ads.txt content—via a hook that executes for all users (such as admin_init or wp_ajax_nopriv_*) without verifying that the requesting user has administrative privileges (missing current_user_can('manage_options')).

2. Attack Vector Analysis

  • Endpoint: Likely wp-admin/admin-ajax.php or any request to wp-admin/ (triggering admin_init).
  • Action/Hook:
    • Possibility A: A wp_ajax_nopriv_ handler (unauthenticated AJAX).
    • Possibility B: An admin_init hook that processes $_POST data without checking is_admin() or user capabilities. Note that admin_init runs for admin-ajax.php even for unauthenticated users.
  • Payload Parameter: Likely a POST parameter named ads_txt_content, tpd_ads_txt_content, or similar, containing the raw text for the ads.txt file.
  • Preconditions: The plugin must be active. A nonce may or may not be required; if required, it is often leaked in the admin dashboard or via localized scripts.

3. Code Flow (Inferred)

  1. Entry Point: The plugin registers a handler:
    add_action('admin_init', 'tpd_ads_txt_save_settings'); (inferred)
    OR
    add_action('wp_ajax_nopriv_save_ads_txt', '... '); (inferred)
  2. Trigger: An attacker sends a POST request to /wp-admin/admin-ajax.php or /wp-admin/admin-post.php.
  3. Vulnerable Function: The handler function (e.g., tpd_ads_txt_save_settings) checks if a specific POST parameter exists:
    if (isset($_POST['tpd_ads_txt_content'])) { ... }
  4. Missing Check: The function performs a update_option() or writes to a file without calling current_user_can('manage_options').
  5. Sink: The ads.txt content is updated in the database or filesystem.

4. Nonce Acquisition Strategy

If the plugin uses check_admin_referer() or check_ajax_referer(), a nonce is required.

  1. Identify Nonce Action: Search the source for wp_create_nonce. Common actions: tpd_ads_txt_nonce, ads_txt_save.
  2. Locate Exposure: Check if the nonce is localized for JS using wp_localize_script.
    • Search for: wp_localize_script( ..., 'tpd_ads_txt_vars', ... ) (inferred).
  3. Extraction (if applicable):
    • The agent should create a page containing any plugin shortcode if scripts only load conditionally.
    • Navigate to the WordPress homepage or the created page.
    • Execute: browser_eval("window.tpd_ads_txt_vars?.nonce") (Verify variable name in source).

If the vulnerability is truly unauthenticated "Missing Authorization", the developer likely forgot the capability check entirely, and may have also omitted the nonce check.

5. Exploitation Strategy

The goal is to overwrite the ads.txt content.

Step 1: Identify Parameters
Search the plugin for update_option or file_put_contents within functions hooked to admin_init or wp_ajax.

  • Keywords: tpd_ads_txt, ads_txt, save, update.

Step 2: Construct POST Request
Using the http_request tool:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php (or admin-post.php if admin_init is used).
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body (Example):
    action=tpd_save_ads_txt&tpd_ads_txt_content=google.com, pub-1337133713371337, DIRECT, f08c47fec0942fa0&_wpnonce=[NONCE_IF_REQUIRED]
    

Step 3: Alternative Trigger (admin_init)
If no AJAX action is found, try a generic POST to admin:

  • URL: http://localhost:8080/wp-admin/admin-post.php
  • Body: tpd_ads_txt_save=1&tpd_ads_txt_content=EXPLOITED_ADS_TXT

6. Test Data Setup

  1. Install Plugin: Ensure the-publisher-desk-ads-txt version <= 1.5.0 is installed.
  2. Initial State: Check if http://localhost:8080/ads.txt exists and note its content.
  3. Discovery: Search for the option name where content is stored:
    wp option list --search="*ads_txt*"

7. Expected Results

  • Response: The server returns a 200 OK or a 302 Redirect (if using admin-post.php).
  • Outcome: The content of the site's ads.txt is updated to the attacker-supplied value.

8. Verification Steps

  1. HTTP Check:
    http_request("http://localhost:8080/ads.txt")
    Verify the output matches the payload: google.com, pub-1337133713371337...
  2. WP-CLI Check:
    wp option get tpd_ads_txt_content (Verify option name in source).
  3. Direct File Check:
    If the plugin writes to a file, check the file content in the WordPress root directory.

9. Alternative Approaches

  • Settings Injection: If the plugin uses register_setting, check if the settings group is accessible via the standard options.php update mechanism, which sometimes lacks proper capability scoping in older plugins.
  • Path Traversal: If the plugin allows specifying the path for the ads.txt file (e.g., ads_txt_path), attempt to write the content to a .php file (e.g., wp-content/uploads/shell.php) to escalate to Remote Code Execution (RCE).
  • Variable Override: If the plugin uses extract($_POST), check for global variable overwrites that could bypass authentication.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Publisher Desk ads.txt plugin for WordPress (<= 1.5.0) fails to verify user capabilities or nonces within its settings-saving routine. This allows unauthenticated attackers to overwrite the site's ads.txt file, potentially redirecting ad revenue to their own accounts.

Vulnerable Code

// File: the-publisher-desk-ads-txt/the-publisher-desk-ads-txt.php

add_action('admin_init', 'tpd_ads_txt_save_settings');

function tpd_ads_txt_save_settings() {
    // Missing current_user_can('manage_options') check
    // Missing check_admin_referer() nonce check
    if (isset($_POST['tpd_ads_txt_content'])) {
        $content = $_POST['tpd_ads_txt_content'];
        update_option('tpd_ads_txt_content', $content);
        // Usually followed by code that writes $content to the local ads.txt file
    }
}

Security Fix

--- a/the-publisher-desk-ads-txt/the-publisher-desk-ads-txt.php
+++ b/the-publisher-desk-ads-txt/the-publisher-desk-ads-txt.php
@@ -3,6 +3,11 @@
 
 function tpd_ads_txt_save_settings() {
-    if (isset($_POST['tpd_ads_txt_content'])) {
+    if (isset($_POST['tpd_ads_txt_content'])) {
+        if (!current_user_can('manage_options')) {
+            wp_die(__('You do not have sufficient permissions to access this page.'));
+        }
+        if (!isset($_POST['tpd_ads_txt_nonce']) || !wp_verify_nonce($_POST['tpd_ads_txt_nonce'], 'tpd_ads_txt_action')) {
+            wp_die(__('Security check failed.'));
+        }
         $content = sanitize_textarea_field($_POST['tpd_ads_txt_content']);
         update_option('tpd_ads_txt_content', $content);

Exploit Outline

The exploit targets the 'admin_init' hook, which executes even for unauthenticated users when accessing administrative scripts like admin-post.php or admin-ajax.php. An attacker sends a POST request to /wp-admin/admin-post.php containing the parameter 'tpd_ads_txt_content' (or the specific content parameter identified in the source). Because the plugin lacks a capability check (current_user_can) and a nonce check, the plugin processes the request and updates the ads.txt settings. The attacker can then verify the change by visiting the site's /ads.txt URL.

Check if your site is affected.

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