Security (low): multisite host lists saved by sub-site admins before 1.0.3 still rewrite the network lost-password URL #21
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: low (likelihood low, impact low)
Found by: security audit run 4 (commit
77fcd83, also applies to341c63e). Reproduced in a sandboxed harness against the real plugin classes and checked against WordPress 7.1.3 core source. Independently verified twice.Fingerprint:
multi-domain-redux:Repository.load:multisite-stale-subsite-host-list-rewrites-network-lostpassword-urlRelated: #6. This is a residual of that fix.
Summary
Up to 1.0.2, a multisite sub-site admin (
manage_options) could savemulti_domain_redux_settingsfor their own blog. The fix for #6 (unreleased 1.0.3) changes what happens to new saves: they now requiremanage_network_options, andnetwork_home_url/network_site_urlare no longer filtered on multisite.The fix does not touch host lists that were already saved under the old rule.
Repository::loadstill applies them on every request. Two things follow.wp_lostpassword_url()builds its URL withnetwork_site_url()and then passes it through thelostpassword_urlfilter, which the plugin still rewrites. Take a stale list that contains the network domain plus a host the old admin chose, marked primary. A visitor arriving on a Host that is not in the list (for example the blog's own subdomain or mapped domain) sees the "Lost your password?" link on that blog'swp-login.phppointing at the chosen host.allowed_redirect_hostsfor that blog, sowp_safe_redirect()of aredirect_toparameter can send users there.Reset-key emails are not affected. They are built from
network_site_url(), which is no longer filtered, so reset keys are not exposed. On subdirectory networks, visitors arrive on the listed network domain, so the lost-password rewrite does not trigger. The allowlist entry still applies there.Root cause
The fix tightened write authority without a lifecycle step. Existing per-blog options get no migration, no re-save requirement and no saved-by-super-admin marker (
src/Config/Repository.php:18-24,src/Plugin.php:36).The defence-in-depth network skip is keyed on filter names, not on the URL's host (
src/Rewrite/HookBindings.php:67-70,:86).lostpassword_url(HookBindings.php:27) carries anetwork_site_url()value on multisite, and it is still rewritten. An unlisted Host falls back to the stale primary (src/Request/HostResolver.php:63).Expected behaviour
On multisite, URL rewriting and the redirect allowlist should be driven only by a host list a network-trusted principal set. No per-blog list should rewrite a network-domain URL, whichever filter carries it. The 1.0.3 changelog states this guarantee for network-scoped URLs.
Source trace
src/Config/Repository.php:19(Repository::load): loads the blog option with no provenance check. A sub-site admin may have written it under 1.0.2.src/Plugin.php:36(Plugin::boot): the stale settings feed HostResolver, UrlRewriter, RedirectGuard and HookBindings.src/Rewrite/HookBindings.php:86(HookBindings::register): on multisite, only thenetwork_home_url/network_site_urlfilter names are skipped.lostpassword_url(line 27) is still hooked.src/Request/HostResolver.php:63(HostResolver::resolve): an unlisted Host resolves to the stale primary.src/Rewrite/UrlRewriter.php:64(UrlRewriter::rewrite): the network-domain URL is re-emitted on that host.src/Rewrite/RedirectGuard.php:74: stale hosts are merged intoallowed_redirect_hosts.Core reference (WP 7.1.3):
wp-includes/general-template.php:901-918(wp_lostpassword_url()), called fromwp-login.php:1195/:1608.Conditions
redirect_tolink.Local reproduction (sandboxed harness, dummy data)
{hosts:[{host:example.com},{host:attacker.test,is_primary:true}]}HTTP_HOST=sub.example.comis_multisite()true, not switchedRepository::load(),HookBindings::register(),UrlRewriterandRedirectGuard. It appliedlostpassword_urlto the URL that core produces on multisite.Observed:
Suggested fix
Host-based network guard. On multisite,
rewriteUrlFilter()should leave any URL unchanged if its host is the current network's domain and not the current blog's domain, whichever filter carries it:Existing data. Add an upgrade step, or have the sanitizer write a
saved_by_networkmarker, so that on multisite any per-blog list no super admin has re-saved is ignored, with an admin notice to super admins.Regression test. On multisite, a blog list holding the network domain plus another primary must not change
lostpassword_urloutput for a network-domain input.1.0.3 has not been released yet, so this can land before any site upgrades.