[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fSIR9qOg_-HuPIfihDnpi2mejek92lYVpXToPf8L3618":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":26,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":26,"source_links":36},"CVE-2026-9104","draft-list-authenticated-author-stored-cross-site-scripting-via-draft-post-title","Draft List \u003C= 2.6.3 - Authenticated (Author+) Stored Cross-Site Scripting via Draft Post Title","The Draft List plugin for WordPress is vulnerable to Stored Cross-Site Scripting via Draft Post Title in all versions up to, and including, 2.6.3 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. The unescaped injection path is triggered specifically when the viewing user lacks edit capabilities, meaning payloads embedded in draft post titles via attribute-breakout techniques execute for unauthenticated users and subscribers.","simple-draft-list",null,">=2.6.3 \u003C=2.6.3","2.6.4","medium",6.4,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-05-21 14:52:57","2026-05-22 03:39:20",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F07361278-7abb-4d22-a8df-218d3f982483?source=api-prod",1,[22,23,24],"inc\u002Fcreate-lists.php","readme.txt","simple-draft-list.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-9104 (Draft List Plugin)\n\n## 1. Vulnerability Summary\nThe **Draft List** plugin (versions \u003C= 2.6.3) contains a stored cross-site scripting (XSS) vulnerability. The plugin allows authenticated users with **Author** level permissions or higher to inject arbitrary web scripts via a draft's post title. This occurs because the plugin fails to sanitize or escape the post title when it is rendered through the `[drafts]` shortcode, particularly when the viewing user does not have `edit_posts` capabilities (e.g., Unauthenticated Guests or Subscribers).\n\n## 2. Attack Vector Analysis\n- **Entry Point**: WordPress Post Editor (`wp-admin\u002Fpost.php`).\n- **Vulnerable Parameter**: `post_title`.\n- **Authentication Requirement**: Authenticated (Author+ role).\n- **Trigger**: Viewing a page\u002Fpost containing the `[drafts]` shortcode as a user without edit permissions.\n- **Preconditions**:\n    - An Author creates a draft with a malicious title.\n    - A site administrator (or the Author) publishes a page using the `[drafts]` shortcode.\n\n## 3. Code Flow\n1. **Injection**: An Author saves a draft post. The `post_title` is stored in the `wp_posts` table.\n2. **Shortcode Execution**: A user visits a page with `[drafts]`. WordPress calls `draft_list_shortcode()` in `inc\u002Fcreate-lists.php`.\n3. **Template Handling**: `draft_list_shortcode` calls `draft_list_generate_code()`.\n4. **Sanitization (Bypassed)**: In `inc\u002Fcreate-lists.php`, the `$template` itself is sanitized via `wp_kses()` on line 150:\n   ```php\n   $template = wp_kses( html_entity_decode( $template ), $allowed_list );\n   ```\n   However, this only sanitizes the *tags* in the template string, not the content of the draft post that will be interpolated into it.\n5. **Sink**: The function (in the truncated portion) iterates through drafts. It replaces the `{{draft}}` placeholder in the `$template` with the `$post->post_title`.\n6. **Capability Check**: The vulnerability description specifies that the unescaped path is triggered when the viewer \"lacks edit capabilities.\" This implies that the logic for non-editors omits escaping functions like `esc_html()` or `esc_attr()` when inserting the title into the HTML string.\n7. **Breakout**: If the title is placed inside a `title` attribute or a `\u003Cspan>` without escaping, a payload like `\">\u003Cscript>alert(1)\u003C\u002Fscript>` breaks out of the attribute\u002Ftag.\n\n## 4. Nonce Acquisition Strategy\nThis vulnerability is triggered by viewing a standard page, so no nonce is required for the **victim** to execute the payload. To **inject** the payload as an Author, the standard WordPress post-editing nonce is required.\n\n**Injection Nonce Acquisition (for Author):**\n1. Log in to the WordPress dashboard as an **Author**.\n2. Navigate to `wp-admin\u002Fpost-new.php`.\n3. Use the `browser_eval` tool to extract the nonce required for saving the post:\n   ```javascript\n   document.querySelector('#_wpnonce').value\n   ```\n4. This nonce is used in the `POST` request to `wp-admin\u002Fpost.php`.\n\n*(Note: Based on the provided source, this plugin does not appear to use `wp_localize_script` for shortcode rendering, as it is a purely server-side generation process.)*\n\n## 5. Exploitation Strategy\n### Step 1: Inject Stored XSS Payload\nThe attacker (Author) creates a draft post.\n- **URL**: `http:\u002F\u002Fvulnerable-wp.local\u002Fwp-admin\u002Fpost.php`\n- **Method**: `POST`\n- **Headers**: `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body Parameters**:\n    - `action`: `editpost`\n    - `post_ID`: (The ID of a newly created draft)\n    - `post_title`: `\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>`\n    - `post_status`: `draft`\n    - `_wpnonce`: (Acquired from Step 4)\n\n### Step 2: Create a Trigger Page\nThe attacker or an admin ensures a page exists with the `[drafts]` shortcode.\n- **Shortcode**: `[drafts limit=10]`\n\n### Step 3: Trigger Execution\nAccess the page as an unauthenticated guest.\n- **URL**: `http:\u002F\u002Fvulnerable-wp.local\u002Fpage-with-shortcode\u002F`\n- **Mechanism**: The plugin renders the list of drafts. Because the guest cannot edit the draft, the plugin enters the vulnerable code path and echoes the `post_title` unsafely.\n\n## 6. Test Data Setup\n1. **User Creation**: `wp user create attacker attacker@example.com --role=author --user_pass=password`\n2. **Draft Creation**: `wp post create --post_type=post --post_status=draft --post_title='Initial Title' --post_author=(ID of attacker)`\n3. **Shortcode Page**: `wp post create --post_type=page --post_status=publish --post_title='Coming Soon' --post_content='[drafts]'`\n\n## 7. Expected Results\n- When the guest visits the 'Coming Soon' page, the HTML source will contain:\n  `... \u003Cspan title=\"\">\u003Cscript>alert(document.domain)\u003C\u002Fscript>\"> ...` (or similar depending on the exact attribute used).\n- A JavaScript `alert` box will appear displaying the domain name.\n\n## 8. Verification Steps\n1. **Check Database**: `wp db query \"SELECT post_title FROM wp_posts WHERE post_status='draft'\"` to confirm the payload is stored.\n2. **Verify Response**: Use `http_request` as an unauthenticated user to fetch the page content and grep for the raw string `\">\u003Cscript>alert`.\n3. **Escaping Check**: If the output shows `&quot;&gt;&lt;script&gt;`, the vulnerability is patched. If it shows `\">\u003Cscript>`, it is vulnerable.\n\n## 9. Alternative Approaches\n- **Attribute Breakout**: If `\u003Cscript>` is filtered by a firewall, use an event handler: `post_title = '\" onmouseover=\"alert(1) \" '`.\n- **Custom Template**: If the default template is somehow escaped, an Author might attempt to override it via the shortcode itself: `[drafts template='\u003Cdiv title=\"{{draft}}\">Check this out\u003C\u002Fdiv>']`. The plugin's `wp_kses` call on the template (line 150) may allow the `title` attribute, but it won't prevent the title data from breaking out of that attribute.","The Draft List plugin (versions \u003C= 2.6.3) is vulnerable to Stored Cross-Site Scripting due to insufficient output escaping of draft post titles within the `[drafts]` shortcode. Authenticated attackers with Author-level permissions or higher can inject malicious scripts into draft titles that execute when any user views a page where the shortcode renders those drafts.","\u002F\u002F inc\u002Fcreate-lists.php (approx line 386 in version 2.6.3)\n\n\t\t\t\t\t\u002F\u002F Replace the draft tag.\n\t\t\t\t\tif ( '' !== $draft_title ) {\n\t\t\t\t\t\t$draft = $draft_title;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$draft = __( '(no title)', 'simple-draft-list' );\n\t\t\t\t\t}\n\t\t\t\t\tif ( $can_edit ) {\n\t\t\t\t\t\t$draft = '\u003Ca href=\"' . home_url() . '\u002Fwp-admin\u002Fpost.php?post=' . $post_id . '&action=edit\" rel=\"nofollow\">' . esc_html( $draft ) . '\u003C\u002Fa>';\n\t\t\t\t\t}\n\t\t\t\t\t$this_line = str_replace( '{{draft}}', $draft, $this_line );","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fsimple-draft-list\u002F2.6.3\u002Finc\u002Fcreate-lists.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fsimple-draft-list\u002F2.6.4\u002Finc\u002Fcreate-lists.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fsimple-draft-list\u002F2.6.3\u002Finc\u002Fcreate-lists.php\t2026-03-15 08:00:42.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fsimple-draft-list\u002F2.6.4\u002Finc\u002Fcreate-lists.php\t2026-05-20 18:50:42.000000000 +0000\n@@ -386,12 +386,13 @@\n \n \t\t\t\t\t\u002F\u002F Replace the draft tag.\n \t\t\t\t\tif ( '' !== $draft_title ) {\n-\t\t\t\t\t\t$draft = $draft_title;\n+\t\t\t\t\t\t$draft = esc_html( $draft_title );\n \t\t\t\t\t} else {\n-\t\t\t\t\t\t$draft = __( '(no title)', 'simple-draft-list' );\n+\t\t\t\t\t\t$draft = esc_html__( '(no title)', 'simple-draft-list' );\n \t\t\t\t\t}\n \t\t\t\t\tif ( $can_edit ) {\n-\t\t\t\t\t\t$draft = '\u003Ca href=\"' . home_url() . '\u002Fwp-admin\u002Fpost.php?post=' . $post_id . '&action=edit\" rel=\"nofollow\">' . esc_html( $draft ) . '\u003C\u002Fa>';\n+\t\t\t\t\t\t$edit_url = esc_url( admin_url( 'post.php?post=' . absint( $post_id ) . '&action=edit' ) );\n+\t\t\t\t\t\t$draft    = '\u003Ca href=\"' . $edit_url . '\" rel=\"nofollow\">' . $draft . '\u003C\u002Fa>';\n \t\t\t\t\t}\n \t\t\t\t\t$this_line = str_replace( '{{draft}}', $draft, $this_line );","1. Log in to the WordPress site as a user with Author role permissions or higher.\n2. Create a new draft post and set the `post_title` to an XSS payload (e.g., \">\u003Cscript>alert(1)\u003C\u002Fscript>).\n3. Ensure a page exists on the site that utilizes the `[drafts]` shortcode.\n4. Access the page containing the shortcode as an unauthenticated guest or a subscriber (any user without the 'edit_posts' capability).\n5. The plugin renders the draft list, and because the viewer cannot edit the post, it injects the raw, unescaped title into the page's HTML, executing the script.","gemini-3-flash-preview","2026-06-04 22:09:46","2026-06-04 22:10:52",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","2.6.3","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsimple-draft-list\u002Ftags\u002F2.6.3","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fsimple-draft-list.2.6.3.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsimple-draft-list\u002Ftags\u002F2.6.4","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fsimple-draft-list.2.6.4.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fsimple-draft-list\u002Ftags"]