Hello Wikimedia Security Team,
Extension:UserPageViewTracker has an unauthenticated SQL injection on
Special:UserPageViewTracker. The filterusers and ignoreusers request parameters
are string-spliced into a raw SQL condition, so an anonymous visitor can read any
data the wiki's database account can access, including the user table (password
hashes, email, tokens). Reproduces logged out, no browser required.
Scope: UserPageViewTracker is not installed on Wikimedia production wikis; this
affects third-party installs that enable it.
Affected: UserPageViewTracker master 092f04a and the REL1_43, REL1_44, REL1_45,
and REL1_46 branch heads (all vulnerable, unfixed); requires MediaWiki >= 1.40.
The special page uses a MariaDB/MySQL @rownum construct, so it targets
MySQL/MariaDB installs.
Severity:
CVSS 3.1 7.5, AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. CWE-89. OWASP A1:2017 Injection.
Root cause -- includes/UserPageViewTrackerPager.php, getQueryInfo():
$includeUsers = "user_name in ( '"; $includeUsers .= implode( "', '", $this->filterUserList ) . "')"; $conds[] = $includeUsers;
filterUserList is explode('|', $request->getVal('filterusers')). The string is
spliced verbatim and pushed as an integer-keyed condition, which IDatabase treats
as a raw SQL fragment (no escaping). ignoreusers (user_name not in (...)) is
identical. Special:UserPageViewTracker declares no getRestriction() and the
extension declares no rights, so it is reachable by anonymous readers.
Reproduce:
Enviornment: (clean MediaWiki 1.43 + MariaDB + UserPageViewTracker 092f04a)
Anonymous (no cookie):
GET /index.php?title=Special:UserPageViewTracker&filterusers=
zzz')) UNION SELECT 1,user_name,user_name,0,'p',user_password,'20260721000000','x' FROM user-- -The response lists every account's user_password hash. I confirmed this locally:
the anonymous request above returned all pbkdf2 hashes (including a canary
account) in the rendered table; a benign filter returns none; the fix below
reduces the attack to zero leaked rows while normal filtering still works.
Fix: build the IN / NOT IN lists through the DB layer instead of string
splicing like $conds['user_name'] = $this->filterUserList for the escaped IN, and
$db->makeList( $this->ignoreUserList ) for NOT IN. Happy to attach a minimal patch
and pre/post fix logs if you'd like.
Coordinated disclosure welcome; happy to align on embargo and credit. Assisted by
automated review, verified by local reproduction; no production systems tested.
Thank you.