Draft Notify <= 1.5 - Authenticated (Administrator+) Stored Cross-Site Scripting
Description
The Draft Notify plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.5 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.5This research plan outlines the investigation and exploitation of **CVE-2025-67627**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Draft Notify** plugin. Since source files were not provided, the following plan uses standard WordPress plugin patterns and provides specific `grep` comm…
Show full research plan
This research plan outlines the investigation and exploitation of CVE-2025-67627, a Stored Cross-Site Scripting (XSS) vulnerability in the Draft Notify plugin.
Since source files were not provided, the following plan uses standard WordPress plugin patterns and provides specific grep commands for the execution agent to identify exact identifiers within the target environment.
1. Vulnerability Summary
The Draft Notify plugin (<= 1.5) fails to sufficiently sanitize and escape user-supplied settings. While the vulnerability requires Administrator-level privileges, it is considered a security flaw because it allows for Stored XSS in contexts where unfiltered_html is disabled (e.g., Multi-site installations or hardened single-site instances). An attacker can inject a payload into a plugin setting which then executes in the browser of any user (including Super Admins) who visits the affected page.
2. Attack Vector Analysis
- Vulnerable Endpoint: Plugin settings page (likely under
wp-admin/options-general.phpor a custom menu page). - Vulnerable Parameter: (Inferred) Settings fields such as notification templates, email subjects, or display messages.
- Authentication Level: Administrator+.
- Preconditions:
unfiltered_htmlmust be disabled. This can be simulated by addingdefine( 'DISALLOW_UNFILTERED_HTML', true );towp-config.php.
3. Code Flow (Inferred)
- Input: An Administrator submits a POST request to update plugin settings.
- Storage: The plugin receives the input and uses
update_option()or the Settings API to save the data to thewp_optionstable without usingsanitize_text_field()orwp_kses(). - Sink: The plugin retrieves the setting using
get_option()and outputs it directly to the HTML viaechoorprintfwithout applyingesc_html(),esc_attr(), orwp_kses(). This likely occurs in the admin dashboard or on the settings page itself.
4. Nonce Acquisition Strategy
Since the attacker is an Administrator, they can access the settings page directly to extract the necessary CSRF tokens (nonces).
Step-by-step Acquisition:
- Identify the Page: Search for where the plugin adds its menu:
grep -rn "add_options_page\|add_menu_page" /var/www/html/wp-content/plugins/draft-notify/ - Navigate: Use
browser_navigateto go to the identified settings page. - Extract Nonce:
- If using the standard Settings API, the nonce is in a hidden input field:
_wpnonce. - If using a custom AJAX handler, search for
wp_localize_scriptin the source to find the JS object containing the nonce. - Agent Command:
browser_eval("document.querySelector('input[name=\"_wpnonce\"]')?.value || window.draft_notify_params?.nonce")
- If using the standard Settings API, the nonce is in a hidden input field:
5. Exploitation Strategy
Step 1: Identification of Fields
The agent must first determine which settings are available.
- Action: Grep for
register_settingorupdate_option.grep -rn "register_setting\|update_option" /var/www/html/wp-content/plugins/draft-notify/
Step 2: Payload Injection
Assuming a setting named draft_notify_options[message] (inferred), the agent will send a POST request.
- Request Method: POST
- URL:
https://<target>/wp-admin/options.php(if using Settings API) oradmin-ajax.php. - Content-Type:
application/x-www-form-urlencoded - Payload:
option_page=draft_notify_settings_group& action=update& _wpnonce=[EXTRACTED_NONCE]& draft_notify_options[message]=<script>alert(document.domain)</script>
Step 3: Triggering Execution
Navigate to the page where the message is displayed (e.g., the plugin settings page itself or the "Posts" list if the notification appears there).
6. Test Data Setup
- Hardening: Disable
unfiltered_htmlto ensure the vulnerability is valid for an Administrator.wp config set DISALLOW_UNFILTERED_HTML true --raw - Plugin Activation: Ensure the plugin is active.
wp plugin activate draft-notify - User Creation: Create a standard Administrator user for the exploitation.
wp user create admin_tester admin@example.com --role=administrator --user_pass=password123
7. Expected Results
- The POST request should return a
302 Foundredirecting back to the settings page with asettings-updated=trueparameter. - Upon navigating to the page where the setting is rendered, a JavaScript
alertbox showing the document domain should appear, confirming the script execution.
8. Verification Steps
- Database Check: Use WP-CLI to verify the payload is stored raw in the database.
wp option get draft_notify_options(or the specific option name found in Step 5.1). - Output Check: Use
http_requestto fetch the settings page and check for the unescaped script tags in the raw HTML.http_request --url "https://<target>/wp-admin/options-general.php?page=draft-notify" --method GET
Look for:<script>alert(document.domain)</script>in the response body.
9. Alternative Approaches
- XSS via Email Templates: If the plugin allows editing the email body sent to users, test if the XSS triggers when the plugin "previews" the email in the admin interface.
- Attribute Injection: If the input is reflected inside an HTML attribute (e.g.,
value="[INPUT]"), use a payload like:"><img src=x onerror=alert(1)>. - Admin Notices: Check if the plugin uses
admin_noticesto display success/error messages containing the unsanitized input.grep -rn "admin_notices" /var/www/html/wp-content/plugins/draft-notify/
Summary
The Draft Notify plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to and including 1.5. This occurs because the plugin fails to sanitize input and escape output for its settings, allowing administrators to inject malicious scripts that execute in the context of other users' browsers when they access the plugin's administration pages.
Exploit Outline
To exploit this vulnerability, an authenticated attacker with Administrator-level privileges must first identify the plugin's settings page and extract the valid security nonce (e.g., from the '_wpnonce' field). The attacker then submits a POST request to update the plugin settings—typically through wp-admin/options.php—injecting a JavaScript payload such as <script>alert(document.domain)</script> into a setting parameter like a notification message or template. The payload is stored in the database and will execute whenever an administrative user views the page where that setting is rendered. This exploit is effective in WordPress Multi-site installations or instances where the DISALLOW_UNFILTERED_HTML constant is set to true.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.