When Session::checkToken() redirects an unauthenticated/expired-session request, it calls Route::_('index.php') without overriding the default $xhtml = true. Route::_() runs htmlspecialchars() on the URL, converting every & into the literal 5-character string &. That value is passed straight to $app->redirect(), which writes it into the HTTP Location: response header.
& is not a valid query separator. Per RFC 3986, ampersands in a Location header must be raw &. Some crawlers (e.g. XOVI) and a few HTTP libraries do not decode HTML entities before following a redirect, so they fetch the next URL with & still embedded. On that next hop, PHP's parse_str treats &Itemid=101 as separator-& plus param-name-amp;Itemid. Any downstream redirect that rebuilds the query string from $_GET then re-encodes the already-encoded ampersands. Every traversal multiplies the amp; prefix.
End result is an exponential redirect chain like:
/component/jce/?task=editor.loadlanguages&lang=en&context=519
→ /component/jce/?task=editor.loadlanguages&lang=en&context=519
→ /component/jce/?task=editor.loadlanguages&lang=en&context=519
→ ...
→ HTTP 502 once URL length exceeds server limits
The trip-wire is Session::isNew() being true, which is the default state for any crawler request (no session cookie).
Session::checkToken('get') on a guest-reachable URL that is emitted into rendered HTML — JCE Editor's editor.loadlanguages URL (shipped in <script src=...> on every page that loads the editor) is one widely-deployed example, but the bug is in core and any such third-party endpoint reproduces it.curl -ski "https://example.test/component/jce/?task=editor.loadlanguages&lang=en&context=1&profile_id=2"HTTP/1.1 303 See other
Location: https://example.test/component/jce/?task=editor.loadlanguages&lang=en&context=1&profile_id=2
Note the literal & in the Location header.curl -L) once and inspect the next Location header — it now contains &amp;. Each follow multiplies it.On infinetinc.com (Joomla 6.1.0), the SEO crawler XOVI logged 463 distinct /component/jce/?task=editor.loadlanguages&amp;... and /component/users/?task=user.login&amp;... URLs as temporaryRedirects (303) and serverErrors (502 after URL exceeded server limits) over a 30-day window. Once the patch below was applied, the chain stopped expanding and the crawler errors dropped to zero on the next scan.
Internal audit across 47 of our managed Joomla sites (J3 through J6) found the same pattern on every site that had a guest-reachable Session::checkToken trigger — i.e. it is reliably reproducible, not site-specific.
6.1 (also confirmed in 5.4-dev, 6.2-dev, 7.0-dev — all maintained branches contain the bare Route::_('index.php') call at libraries/src/Session/Session.php line 79)
The Location header contains a URL with raw & between query parameters, per RFC 3986. A crawler following the redirect lands at the same URL it would receive on a fresh request — no exponential growth.
The Location header contains & (literal 5-character HTML entity) between query parameters. Crawlers that do not entity-decode before following the redirect produce an exponentially-growing query string on each hop until the request URL exceeds server limits and returns 502.
libraries/src/Session/Session.php line 79.libraries/src/Session/Session.php:79:
$app->redirect(Route::_('index.php'));Route::_($url, $xhtml = true, ...) — the default xhtml=true runs htmlspecialchars($url). The output of that goes into the Location: header where entity-encoding is wrong.
$app->redirect(Route::_('index.php', false));The false suppresses the entity encoding so the Location header receives raw &. There is no XHTML context here — this URL is going into an HTTP response header, not into rendered HTML — so the default is wrong for this call site.
I'm happy to open a PR against 6.1-dev (and back/forward-port to 5.4, 6.2, 7.0) if a maintainer confirms this is the right surface for the fix. An alternative would be to entity-decode inside AbstractWebApplication::redirect() itself, but that's more intrusive and could break callers that intentionally pass entity-encoded URLs.
Browsers tolerate & in Location headers (they decode entities before re-issuing the request), so a human testing in Chrome/Firefox never sees the chain. The bug only surfaces with crawlers/automated agents that follow Location: literally — and even among those, only the ones that re-build the URL from parsed $_GET on the next hop produce the multiplying chain. Google's crawler normalizes entity encoding before re-fetching, so Search Console doesn't typically flag it. XOVI is the most reliable detector we've encountered.
| Title |
|
||||||
| Labels |
Added:
No Code Attached Yet
|
||||||