Opened 5 months ago
Last modified 2 months ago
#64507 reviewing defect (bug)
Fatal errors can ensue in WP_Query when requests are made with array query vars
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Future Release | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Query | Keywords: | has-patch has-unit-tests |
| Focuses: | Cc: |
Description
Investigating 5XX errors, I see numerous fatal errors triggered because user provided value author_name is not validated to be a string.
(HTTP-provided arguments are strings or arrays)
To reproduce:
curl -g "http://localhost/?author_name[x]=bob"
[15-Jan-2026 10:51:57 UTC] PHP Fatal error: Uncaught TypeError: str_contains(): Argument #1 ($haystack) must be of type string, array given in /var/www/html/wp-includes/class-wp-query.php:2422
Stack trace:
#0 /var/www/html/wp-includes/class-wp-query.php(2422): str_contains()
#1 /var/www/html/wp-includes/class-wp-query.php(3958): WP_Query->get_posts()
#2 /var/www/html/wp-includes/class-wp.php(704): WP_Query->query()
#3 /var/www/html/wp-includes/class-wp.php(824): WP->query_posts()
#4 /var/www/html/wp-includes/functions.php(1343): WP->main()
#5 /var/www/html/wp-blog-header.php(16): wp()
#6 /var/www/html/index.php(17): require('...')
#7 {main}
thrown in /var/www/html/wp-includes/class-wp-query.php on line 2422
Attachments (1)
Change History (8)
#2
@
5 months ago
- Milestone changed from Awaiting Review to 7.0
- Owner set to westonruter
- Status changed from new to reviewing
Also relates to #64238, as PHPStan could in the future identify problems like this.
@leedxw Are you aware of the code which is making these malformed requests?
#3
@
5 months ago
This came as a result of investigating fatal errors caused by a hack attempts on a website - not legitimate code.
(Some of the patterns indicate the use of the Acunetix testing tool.)
This ticket was mentioned in PR #10792 on WordPress/wordpress-develop by @westonruter.
5 months ago
#5
Trac ticket: https://core.trac.wordpress.org/ticket/64507
#6
@
5 months ago
- Keywords needs-patch added; has-patch removed
- Milestone changed from 7.0 to Future Release
- Owner westonruter deleted
- Summary changed from Fatal error when author_name is not a string to Fatal errors can ensue in WP_Query when requests are made with array query vars
I opened a PR for the patch and I started iterating on it a bit more to account for other query vars which may erroneously be arrays.
I'm sure there are many more query vars that need to be accounted for to prevent possible fatal errors from bad requests. It seems strange to limit the scope to author_name only when the error could occur for others. So I'll leave this open for other to find more opportunities to harden WP_Query in how the query vars are passed.
This ticket was mentioned in PR #11466 on WordPress/wordpress-develop by @sanket.parmar.
2 months ago
#7
- Keywords has-patch has-unit-tests added; needs-patch removed
## Summary
Fixes a class of fatal TypeError exceptions in WP_Query on PHP 8+ that occur when HTTP requests use PHP's bracket notation (e.g. ?author_name[x]=bob, ?feed[]=rss2) to pass array values for query vars that expect strings.
## Root Cause
PHP's bracket notation delivers arrays via $_GET. WP_Query::fill_query_vars() only sets defaults for missing keys — it does not coerce types. So an array from HTTP passes through as-is.
PHP 8 made str_contains(), str_replace(), and wp_basename() strict about argument types; passing an array where a string is expected now raises a TypeError fatal rather than the E_WARNING PHP 7 issued.
## Confirmed Crash Sites
| Query var | Fatal expression | Location |
|---|---|---|
author_name | str_contains( $query_vars['author_name'], '/' ) | get_posts() ~L2424
|
feed | str_contains( $query_vars['feed'], 'comments-' ) | get_posts() ~L1036
|
feed | str_replace( 'comments-', '', $query_vars['feed'] ) | get_posts() ~L1037
|
attachment | wp_basename( $query_vars['attachment'] ) | get_posts() ~L2224
|
## Fix
In WP_Query::parse_query(), normalise author_name, feed, and attachment with an is_string() guard before they reach the crash sites:
$query_vars['author_name'] = is_string( $query_vars['author_name'] ) ? $query_vars['author_name'] : '';
$query_vars['feed'] = is_string( $query_vars['feed'] ) ? $query_vars['feed'] : '';
$query_vars['attachment'] = is_string( $query_vars['attachment'] ) ? $query_vars['attachment'] : '';
This follows the same established pattern already used for numeric vars (p, paged, year, etc.) in the same normalization block.
## Why Only These Three Vars — Not Others
Every other default string query var was considered and ruled out for one of three reasons:
| Var(s) | Reason no guard needed |
|---|---|
cat, tag | Arrays handled intentionally downstream — get_posts() explicitly checks if ( is_array( $query_vars['cat/tag'] ) ) { implode(...) } before any string function touches them
|
category_name | Never passed to str_contains, str_replace, or wp_basename; only used in '' !== comparisons and get_term_by()
|
sentence, embed, preview, tb | Boolean flags — only ever checked with ! empty() or '' !=; an is_string() guard would silently coerce true to '', breaking their intended boolean usage
|
fields | Only used in switch / === string comparisons — no PHP 8 type-strict function call
|
meta_key, meta_value | Documented as string\||string[]; WP_Meta_Query handles the array form; guarding would silently break new WP_Query(['meta_key' => ['k1', 'k2']])
|
## Tests
Two data-driven test methods added to Tests_Query_ParseQuery (ticket #64507), covering all three guarded vars:
test_parse_query_string_var_string_value— regression guard ensuring valid string values pass through unchanged.test_parse_query_string_var_array_value— confirms array input is coerced to''without fataling.
---
Trac ticket: https://core.trac.wordpress.org/ticket/64507
---
## Use of AI Tools
AI assistance: Yes
Tool(s): GitHub Copilot
Model(s): Claude Sonnet 4.6
Used for: Root cause analysis, crash site identification, initial patch and test implementation; final implementation, type contract verification, and correctness of guards reviewed and edited by me.
patch to check if author_name is a string