Opened 5 weeks ago
Last modified 13 days ago
#65051 assigned defect (bug)
$_REQUEST['term'] used unsanitized in user search query
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.1 | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Networks and Sites | Keywords: | has-patch needs-testing has-test-info has-unit-tests |
| Focuses: | multisite, coding-standards | Cc: |
Description
User-supplied search term is concatenated directly into the get_users() search argument without
sanitize_text_field() or wp_unslash().
Attachments (1)
Change History (8)
This ticket was mentioned in PR #11530 on WordPress/wordpress-develop by rajeshcpr.
5 weeks ago
#1
#2
@
5 weeks ago
- Focuses coding-standards added
Tested the patch and confirmed the issue: https://github.com/WordPress/wordpress-develop/pull/11530/commits/5eed1c8ea50eb3dfda7605749f267bf9e3234dc3
Environment:
- WordPress: 7.1-alpha-62161-src
- PHP: 8.3.30
- Browser: Chrome
- Database: MySQL 8.4.8
- OS: Ubuntu
1) The current implementation uses $_REQUEST[term] directly without sanitization.
2) The patch correctly applies 'wp_unslash()' and 'sanitize_text_field()', which aligns with WordPress data handling standards.
3) Verified that the user search functionality continues to work as expected after the change.
This is a valid security improvement and works as expected
Screenshot for reference: https://kommodo.ai/i/s2Bol19v4cwB50UNttQp
#3
@
5 weeks ago
- Component changed from General to Networks and Sites
- Focuses multisite added
- Milestone changed from Awaiting Review to 7.1
- Severity changed from major to normal
- Version trunk deleted
#4
@
4 weeks ago
Test Report & Security Impact Analysis
- Environment
WordPress: 7.1-alpha (trunk)
Setup: Multisite Network Admin
Verification Method: Standalone PHP Mock + Browser DevTools Interception
- Security Analysis: Why a PoC is currently "Silent"
I would like to clarify the current impact of this vulnerability. While the fix is essential, a successful XSS alert is currently unlikely to trigger in the default UI for the following reasons:
Empty Response on Malicious Payload: When a payload like <script>alert('XSS')</script> is injected into the term parameter, the backend executes a get_users query. Since no user account exists with such a string, the database returns an empty set. The resulting Ajax response is an empty array [].
Safe Client-Side Handling: The current core JavaScript expects a JSON array of user objects. When it receives [], it simply does not render any dropdown items, preventing the payload from being echoed back into the DOM.
- Why the fix is still critical
Log Poisoning / Second-Order XSS: If the search term is recorded in server-side logs (e.g., Audit logs or Slow Query logs) viewed in other admin interfaces, the script could execute.
Future-Proofing: Any UI changes displaying "No results found for: [term]" would immediately turn this into a high-risk Reflected XSS.
Data Integrity: Aligning with WP coding standards by sanitizing at the entry point.
- Verification Results
[Next comment]My standalone tests confirm that the patch correctly strips HTML tags at the entry point.
Payload Intercepted via DevTools:
action: autocomplete-user
term: <script>alert('XSS')</script>Admin
site_id: 2
Backend Observation (using attached test script):
Before Patch: get_users receives the raw <script> tag.
After Patch: get_users receives the sanitized string alert('XSS')Admin.
#5
@
4 weeks ago
Test Report
Ticket: #65051 - $_REQUEST[\'term\'] used unsanitized in user search query
Environment
WordPress Version: 7.1-alpha (trunk)
PHP Version: 8.x
Test Method: Standalone Mock / Integration Test
OS: Windows (MINGW64)
Testing Methodology
I performed a deep-dive verification using a standalone mock script to isolate the data flow within wp_ajax_autocomplete_user(). By stubbing the core dependencies (is_multisite, get_users, etc.), I was able to intercept the exact arguments being passed to the user query logic.
Test Results
- Confirming the Vulnerability (Before Patch)
Using a malicious payload: <script>alert('hack')</script>Admin
The input was passed directly to the search argument without any sanitization.
Intercepted Query: [Intercepted] get_users() called with 'search' => '<script>alert(\'hack\')</script>Admin'
Status: ❌ Confirmed. Raw HTML/Script tags reached the query level.
- Verification of Fix (After Patch)
Applied sanitize_text_field( wp_unslash( ... ) ) to the $term variable.
Intercepted Query: [Intercepted] get_users() called with 'search' => '*alert(\'hack\')Admin*'
Status: ✅ Fixed. The <script> tags were successfully stripped before reaching get_users().
Execution Log Output (before patch)
--- Running Standalone Invocation Test ---
--- [Demo before patch] ---
[Intercepted] get_users() called with 'search' => '<script>alert(\'hack\')</scri p
t>Admin'
[JSON Response]: {"success":true}
--- [Demo after patch] ---
[Intercepted] get_users() called with 'search' => 'alert(\'hack\')Admin'
[JSON Response]: {"success":true}
--- [Test Target: wp_ajax_autocomplete_user] ---
[Intercepted] get_users() called with 'search' => '*<script>alert(\'hack\')</sc ipt>Admin*'
[wp_die] Value: []
Execution Log Output (after patch)
Plaintext
--- Running Standalone Invocation Test ---
--- [Demo before patch] ---
[Intercepted] get_users() called with 'search' => '<script>alert(\'hack\')</script>Admin'
[JSON Response]: {"success":true}
--- [Demo after patch] ---
[Intercepted] get_users() called with 'search' => 'alert(\'hack\')Admin'
[JSON Response]: {"success":true}
--- [Test Target: wp_ajax_autocomplete_user] ---
[Intercepted] get_users() called with 'search' => '*alert(\'hack\')Admin*'
[wp_die] Value: []
Verdict
The patch effectively resolves the issue by sanitizing the user-supplied search term. It prevents potential XSS payloads from being processed in the backend logic while maintaining the expected search functionality.
test-65051-sanitize.php
<?php /** * Standalone Test: Invoking wp_ajax_autocomplete_user directly */ define( 'DOING_AJAX', true ); define( 'WP_ADMIN', true ); // fake $wp_db $GLOBALS['wpdb'] = unserialize('O:8:"stdClass":0:{}'); // --- Stub Functions (to pass test)--- function auth_redirect() {} function check_ajax_referer( $action ) { return true; } function current_user_can( $cap ) { return true; } function wp_unslash( $data ) { return stripslashes( $data ); } function sanitize_text_field( $str ) { return strip_tags( $str ); } function get_users( $args ) { $search_term = $args['search'] ?? '(no search term)'; if ( '(no search term)' !== $search_term ) { echo "[Intercepted] get_users() called with 'search' => " . var_export($search_term, true) . "\n"; } return array(); } function wp_send_json( $response ) { echo "[JSON Response]: " . json_encode( $response ) . "\n"; } function wp_die( $msg = '' ) { echo "\n[wp_die] Value: $msg\n"; } if ( ! function_exists( 'is_multisite' ) ) { function is_multisite() { return true; } } if ( ! function_exists( 'wp_is_large_network' ) ) { function wp_is_large_network() { return false; } } if ( ! function_exists( 'get_current_blog_id' ) ) { function get_current_blog_id() { return 1; } } if ( ! function_exists( 'wp_json_encode' ) ) { function wp_json_encode( $data ) { return json_encode( $data ); } } if ( ! function_exists( 'get_current_screen' ) ) { function get_current_screen() { return null; } } if ( ! function_exists( 'wp_parse_args' ) ) { function wp_parse_args( $args, $defaults = array() ) { return array_merge( $defaults, $args ); } } // --- target source --- require_once 'wp-admin/includes/ajax-actions.php'; /** * Simulation Demo */ function wp_ajax_autocomplete_user_demo_before_patch() { $term = $_REQUEST['term']; get_users( array( 'search' => $term, 'fields' => array( 'ID', 'user_login' ), ) ); wp_send_json( array( 'success' => true ) ); } function wp_ajax_autocomplete_user_demo_after_patch() { $term = sanitize_text_field( wp_unslash( $_REQUEST['term'] ) ); get_users( array( 'search' => $term, 'fields' => array( 'ID', 'user_login' ), ) ); wp_send_json( array( 'success' => true ) ); } echo "--- Running Standalone Invocation Test ---\n"; $_REQUEST['term'] = "<script>alert('hack')</script>Admin"; echo "\n--- [Demo before patch] ---\n"; try { wp_ajax_autocomplete_user_demo_before_patch(); } catch (Exception $e) { echo "Caught: " . $e->getMessage(); } echo "\n--- [Demo after patch] ---\n"; try { wp_ajax_autocomplete_user_demo_after_patch(); } catch (Exception $e) { echo "Caught: " . $e->getMessage(); } echo "\n--- [Test Target: wp_ajax_autocomplete_user] ---\n"; try { wp_ajax_autocomplete_user(); } catch (Exception $e) { echo "Caught: " . $e->getMessage(); }

User-supplied search term is concatenated directly into the get_users() search argument without
Trac ticket: https://core.trac.wordpress.org/ticket/65051
Fixes #65051
## Use of AI Tools