CVE-2025-58674 Analysis & POC

A Stored Cross Site Scripting (XSS) vulnerability occurs in WordPress Core prior to version 6.8.3
. The root cause is improper input handling when generating dynamic pages, affecting the menu creation feature (nav menus
).
- CVE ID: CVE-2025-58674
- Vulnerability Type: Cross Site Scripting (XSS)
- Affected Versions: <= 6.8.2
- Patched Versions: 6.8.3
- CVSS severity: Low (5.9)
- Required Privilege: Author
- Product: WordPressCore
1 Requirements
- Local WordPress & Debugging: Local WordPress and Debugging.
- WordPress Core Versions: v6.8.2 (vulnerable) and v6.8.3 (patched).
- Diff Tool - Meld or any diff/comparison tool to inspect and compare differences between the two versions.
- Theme - Astra: A very popular theme among WordPress users, which supports quick creation of nav menu items.
2 Analysis
WordPress is open source and its repository is on GitHub, so we can look at the commit related to the XSS fix to observe the changes and understand where the vulnerability occurs.
2.1 Patch diff
Vulnerable version:
updateParentDropdown : function() {
return this.each(function(){
var menuItems = $( '#menu-to-edit li' ),
parentDropdowns = $( '.edit-menu-item-parent' );
$.each( parentDropdowns, function() {
var parentDropdown = $( this ),
$html = '',
$selected = '',
currentItemID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val(),
currentparentID = parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val(),
currentItem = parentDropdown.closest( 'li.menu-item' ),
currentMenuItemChild = currentItem.childMenuItems(),
excludeMenuItem = [ currentItemID ];
if ( currentMenuItemChild.length > 0 ) {
$.each( currentMenuItemChild, function(){
var childItem = $(this),
childID = childItem.find( '.menu-item-data-db-id' ).val();
excludeMenuItem.push( childID );
});
}
if ( currentparentID == 0 ) {
$selected = 'selected';
}
$html += '<option ' + $selected + ' value="0">' + wp.i18n._x( 'No Parent', 'menu item without a parent in navigation menu' ) + '</option>';
$.each( menuItems, function() {
var menuItem = $(this),
$selected = '',
menuID = menuItem.find( '.menu-item-data-db-id' ).val(),
menuTitle = menuItem.find( '.edit-menu-item-title' ).val();
if ( ! excludeMenuItem.includes( menuID ) ) {
if ( currentparentID == menuID ) {
$selected = 'selected';
}
$html += '<option ' + $selected + ' value="' + menuID + '">' + menuTitle + '</option>';
}
});
parentDropdown.html( $html );
});
});
},
In the vulnerable version, the value menuTitle
is inserted into the <option>
tag and rendered into HTML using jQuery’s html() method without any XSS prevention. The html() function replaces the HTML content inside the element, so if menuTitle
contains malicious code it will be executed in the browser.
Patched version:
updateParentDropdown : function() {
return this.each(function(){
var menuItems = $( '#menu-to-edit li' ),
parentDropdowns = $( '.edit-menu-item-parent' );
$.each( parentDropdowns, function() {
var parentDropdown = $( this ),
currentItemID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-db-id' ).val() ),
currentParentID = parseInt( parentDropdown.closest( 'li.menu-item' ).find( '.menu-item-data-parent-id' ).val() ),
currentItem = parentDropdown.closest( 'li.menu-item' ),
currentMenuItemChild = currentItem.childMenuItems(),
excludeMenuItem = /** @type {number[]} */ [ currentItemID ];
parentDropdown.empty();
if ( currentMenuItemChild.length > 0 ) {
$.each( currentMenuItemChild, function(){
var childItem = $(this),
childID = parseInt( childItem.find( '.menu-item-data-db-id' ).val() );
excludeMenuItem.push( childID );
});
}
parentDropdown.append(
$( '<option>', {
value: '0',
selected: currentParentID === 0,
text: wp.i18n._x( 'No Parent', 'menu item without a parent in navigation menu' ),
} )
);
$.each( menuItems, function() {
var menuItem = $(this),
menuID = parseInt( menuItem.find( '.menu-item-data-db-id' ).val() ),
menuTitle = menuItem.find( '.edit-menu-item-title' ).val();
if ( ! excludeMenuItem.includes( menuID ) ) {
parentDropdown.append(
$( '<option>', {
value: menuID.toString(),
selected: currentParentID === menuID,
text: menuTitle,
} )
);
}
});
});
});
},
The patch fixes the issue by explicitly assigning menuTitle
to the text
property instead of injecting it into HTML. This ensures menuTitle
is treated as plain text and cannot contain or execute malicious JavaScript. Thus, data added into the <option>
element is safe and the XSS vector via menuTitle
is eliminated.

Comparison between vulnerable and patched versions
2.2 Vulnerable code
Inspecting the code in the browser shows menuItems = #menu-to-edit li
is an array of <li>
elements inside the <ul>
with id=menu-to-edit

Inspecting code in the browser
The updateParentDropdown
function iterates over the <li>
elements, retrieves the value of the <input>
with class edit-menu-item-title
, assigns that value to menuTitle
inside an <option>
tag, and renders it into HTML.

Inspecting DOM after adding a menu
In the UI:

Display in the admin UI
In the commit there is a change that seems useful but did not help the analysis.

HTML entity encode (1)
I used //
to comment out all lines related to html_entity_decode
of origin_title
but was still able to exploit. Previously I had set debug points at those locations but nothing happened.
Besides commenting them out, I selected the menu containing the XSS payload and clicked Add to Menu, capturing the request with Burp Suite to see whether the added value was HTML entity encoded.

HTML entity encode (2)
menu-item-title
is the value taken from the input named menu-item[-5][menu-item-title]
derived from the post-title
, which is checked to add into the request body
<input type="hidden" class="menu-item-title" name="menu-item[-5][menu-item-title]" value="<script>alert(document.domain)</script>">
One interesting thing here: the browser takes the decoded HTML entity value to add into the request, meaning the value from the server was encoded before being echoed into HTML.

Browser decodes HTML entity before sending
👉 The browser decoded the HTML entity before rendering the HTML.
On the DOM, after clicking Add to Menu => attachTabsPanelListeners
is called and appends the selected value to the bottom of the menu item list.

Appending the item to the end of the menu list
👉 edit-menu-item-title
contains the XSS payload. The updateParentDropdown
function will take it and assign it to an <option>
element => XSS occurs
2.3 Sources & Sinks
- Source:
post-title
— the post title - Sink:
parentDropdown.html( $html )
which may contain malicious HTML
$html += '<option ' + $selected + ' value="' + menuID + '">' + menuTitle + '</option>';
3 Exploit
3.1 Proof of Concept (PoC)
- Use an Author account to create a post with a title containing an XSS payload
- An Admin visits the endpoint
wp-admin/nav-menus.php
and adds menus from the created post - The JavaScript event is triggered

Proof of Concept: XSS
The
<script>
tag inside an<option>
within a<select>
can be executed, while other tags inside<option>
are not — they only return the text inside. {: .prompt-info }
- When the browser parses the HTML string, the
<script>
is not truly inside the<option>
flow; the parser “lifts” the script out of the<option>
and inserts it into the DOM tree.
<select name="" id="">
<option value="">abc</option>
<option value=""><script>alert(1)</script></option>
</select>
- Therefore, the script executes immediately, while the
<option>
remains but its content is empty or displays nothing.

Inspecting DOM after the script is lifted
- For other tags
<select name="" id="">
<option value="">abc</option>
<option value=""><b>abd</b></option>
</select>
- The browser follows the spec:
<option>
is text-only. - When parsing
<b>
or<img>
inside an<option>
:<b>
is treated as text => the tag is removed, only “abd” is displayed as text.<img>
is removed entirely.

Inspect: rendering option with inline elements
4 Conclusion
CVE-2025-58674 demonstrates the risk of inserting user-controlled data directly into HTML via html()
or innerHTML
. A <script>
inside an <option>
can be lifted out by the parser and executed, while other tags are stripped or treated as text. The patch uses the text
property to ensure safety and eliminate XSS.
5 Key takeaways
- Do not concatenate HTML strings from user data and insert them with
html()
/innerHTML
. - Use safe element creation APIs (
option.text
,document.createElement
). - Always escape/sanitize data both server-side and client-side.
- Understand parser behavior:
<script>
can escape an inert container.
6 References
Cross-site scripting (XSS) cheat sheet — PortSwigger
WordPress Core <= 6.8.2 is vulnerable to Cross Site Scripting (XSS)