Page MenuHomePhabricator

CVE-2025-53481: Denial of service vector on ipinfo/v0/norevision
Closed, ResolvedPublicSecurity

Description

Loading data for an IP that has no edits (e.g. here) results in 26 seconds of MySQL query time. I think that is mitigated due to the access controls on that endpoint, but we should still investigate and fix it as soon as possible.

Event Timeline

The queries involved in checkIPisKnown are:

SELECT  1  FROM `cu_changes` FORCE INDEX (cuc_actor_ip_time) JOIN `actor` ON ((cuc_actor=actor_id))   WHERE actor_name = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1  
SELECT  1  FROM `cu_log_event` FORCE INDEX (cule_actor_ip_time) JOIN `actor` ON ((cule_actor=actor_id))   WHERE actor_name = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1  
SELECT  1  FROM `cu_private_event` FORCE INDEX (cupe_actor_ip_time) LEFT JOIN `actor` ON ((cupe_actor=actor_id))   WHERE ((actor_name = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7' OR (cupe_actor IS NULL AND cupe_ip = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7')))  LIMIT 1  
SELECT  1  FROM `abuse_filter_log` FORCE INDEX (afl_ip_timestamp)    WHERE afl_user_text = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1

These are the problem queries:

mysql:research@dbstore1008.eqiad.wmnet [enwiki]> SELECT  1  FROM `cu_private_event` FORCE INDEX (cupe_actor_ip_time) LEFT JOIN `actor` ON ((cupe_actor=actor_id))   WHERE ((actor_name = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7' OR (cupe_actor IS NULL AND cupe_ip = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7')))  LIMIT 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (58.324 sec)

mysql:research@dbstore1008.eqiad.wmnet [enwiki]> EXPLAIN SELECT  1  FROM `cu_private_event` FORCE INDEX (cupe_actor_ip_time) LEFT JOIN `actor` ON ((cupe_actor=actor_id))   WHERE ((actor_name = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7' OR (cupe_actor IS NULL AND cupe_ip = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7')))  LIMIT 1;
+------+-------------+------------------+--------+--------------------+--------------------+---------+------------------------------------+---------+-------------+
| id   | select_type | table            | type   | possible_keys      | key                | key_len | ref                                | rows    | Extra       |
+------+-------------+------------------+--------+--------------------+--------------------+---------+------------------------------------+---------+-------------+
|    1 | SIMPLE      | cu_private_event | index  | cupe_actor_ip_time | cupe_actor_ip_time | 281     | NULL                               | 5102153 | Using index |
|    1 | SIMPLE      | actor            | eq_ref | PRIMARY            | PRIMARY            | 8       | enwiki.cu_private_event.cupe_actor | 1       | Using where |
+------+-------------+------------------+--------+--------------------+--------------------+---------+------------------------------------+---------+-------------+
2 rows in set (0.001 sec)
mysql:research@dbstore1008.eqiad.wmnet [enwiki]> SELECT  1  FROM `abuse_filter_log` FORCE INDEX (afl_ip_timestamp)    WHERE afl_user_text = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1
    -> ;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (42.377 sec)

mysql:research@dbstore1008.eqiad.wmnet [enwiki]> EXPLAIN SELECT  1  FROM `abuse_filter_log` FORCE INDEX (afl_ip_timestamp)    WHERE afl_user_text = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1;
+------+-------------+------------------+------+---------------+------+---------+------+----------+-------------+
| id   | select_type | table            | type | possible_keys | key  | key_len | ref  | rows     | Extra       |
+------+-------------+------------------+------+---------------+------+---------+------+----------+-------------+
|    1 | SIMPLE      | abuse_filter_log | ALL  | NULL          | NULL | NULL    | NULL | 39928601 | Using where |
+------+-------------+------------------+------+---------------+------+---------+------+----------+-------------+
1 row in set (0.001 sec)

When running the following on my local wiki, it says the possible_keys is afl_user_timestamp. It seems like the wrong index was used for that query.

EXPLAIN SELECT  1  FROM `abuse_filter_log` WHERE afl_user_text = '2600:8800:12C1:3300:BD2E:63A5:5135:17D7'  LIMIT 1;

I'm not sure what is exactly broken in the cu_private_event query at this point.

{F59577395}

It seems that in this patch the IP is being used as the actor ID for the query to cu_private_event? Is this intentional?

{F59578200}

Updated version to find the actor id and query on that

{F59578200}

Updated version to find the actor id and query on that

LGTM, virtual +2

Now with commit message. Sorry about that; I think I read the wrong set of documentation.

Follow-ups after testing on staging:

  • actor id query was returning something unintended
  • abuse filter query was still slow due to incorrect index

This needs to be deployed to .25, where the original patch was deployed to and tested against.

Full patch for deploys on other versions

This needs to be deployed to .25, where the original patch was deployed to and tested against.

Is this something the Security-Team should plan to discretely deploy this week? I guess we'd want to determine the sense of urgency here (e.g. how likely it is that an attacker could exploit this over the next few days).

Full patch for deploys on other versions

If this works for 1.44.0-wmf.27 and beyond, and this isn't an urgent issue, I'd recommend we deploy this during next Monday's (May 5th) security deployment window.

This needs to be deployed to .25, where the original patch was deployed to and tested against.

Is this something the Security-Team should plan to discretely deploy this week? I guess we'd want to determine the sense of urgency here (e.g. how likely it is that an attacker could exploit this over the next few days).

Full patch for deploys on other versions

If this works for 1.44.0-wmf.27 and beyond, and this isn't an urgent issue, I'd recommend we deploy this during next Monday's (May 5th) security deployment window.

Sorry I forgot to follow up here. Both wmf.27 and wmf.25 are patched. The patches are in /srv/patches. So I think the next step here is to update master in IPInfo via a gerrit patch, when you are ready to do that.

sbassett changed the task status from Open to In Progress.May 5 2025, 4:28 PM
sbassett triaged this task as Medium priority.
sbassett moved this task from Incoming to In Progress on the Security-Team board.
sbassett changed Author Affiliation from N/A to WMF Product.May 6 2025, 4:00 PM
sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from N/A to Medium.
sbassett added a subscriber: gerritbot.

Change #1143141 had a related patch set uploaded (by Kosta Harlan; author: STran):

[mediawiki/extensions/IPInfo@master] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1143141

Change #1143141 merged by jenkins-bot:

[mediawiki/extensions/IPInfo@master] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1143141

Change #1143191 had a related patch set uploaded (by Dreamy Jazz; author: STran):

[mediawiki/extensions/IPInfo@REL1_44] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1143191

Change #1143191 merged by jenkins-bot:

[mediawiki/extensions/IPInfo@REL1_44] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1143191

@STran In the case that the IP exists in the actor table, should the query also include a condition for when cupe_actor is null and the cupe_ip matches? Otherwise we miss cases such as when an IP makes a failed login attempt.

Change #1146664 had a related patch set uploaded (by STran; author: STran):

[mediawiki/extensions/IPInfo@master] Always check cu_private_event in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1146664

Change #1146664 merged by jenkins-bot:

[mediawiki/extensions/IPInfo@master] Always check cu_private_event in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1146664

I have compared the output of ipinfo/v0/norevision for lots of IPs on my local docker wiki before and after the changes in this task. Since T392976#10827501, I have detected no differences.

I have not seen a query called from checkIPIsKnown which took more than 10ms.

SecurityPatchBot raised the priority of this task from Medium to Unbreak Now!.

Patch 01-T392976.patch is currently failing to apply for the most recent code in the mainline branch of extensions/IPInfo. This is blocking MediaWiki release 1.45.0-wmf.2(T392172)

If the patch needs to be rebased

To unblock the release, a new version of the patch can be placed at the right location in the deployment server with the following Scap command:

REVISED_PATCH=<path_to_revised_patch>
scap update-patch --message-body 'Rebase to solve merge conflicts with mainline code' /srv/patches/1.45.0-wmf.2/extensions/IPInfo/01-T392976.patch "$REVISED_PATCH"

If the patch has been made public

To unblock the release, the patch can be removed for the right version from the deployment server with the following Scap command:

scap remove-patch --message-body 'Remove patch already made public' /srv/patches/1.45.0-wmf.2/extensions/IPInfo/01-T392976.patch

(Note that if patches for the version don't exist yet, they will be created and the patch you specified removed)

sbassett lowered the priority of this task from Unbreak Now! to Low.May 18 2025, 12:50 PM

Mentioned in SAL (#wikimedia-operations) [2025-05-18T12:50:21Z] <sbassett> Ran scap remove-patch for T392976

-> No longer a train blocker for tomorrow?

Yeah, that is my understanding. The security patch was uploaded publicly.

-> No longer a train blocker for tomorrow?

Correct!

Change #1165043 had a related patch set uploaded (by Mmartorana; author: STran):

[mediawiki/extensions/IPInfo@REL1_43] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1165043

Change #1165045 had a related patch set uploaded (by Mmartorana; author: STran):

[mediawiki/extensions/IPInfo@REL1_44] Always check cu_private_event in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1165045

Change #1165045 merged by jenkins-bot:

[mediawiki/extensions/IPInfo@REL1_44] Always check cu_private_event in AnonymousUserIPLookup->checkIPIsKnown

https://gerrit.wikimedia.org/r/1165045

mmartorana renamed this task from Denial of service vector on ipinfo/v0/norevision to CVE-2025-53481: Denial of service vector on ipinfo/v0/norevision.Jul 8 2025, 5:46 PM

Change #1165043 abandoned by SBassett:

[mediawiki/extensions/IPInfo@REL1_43] SECURITY: Improve slow queries in AnonymousUserIPLookup->checkIPIsKnown

Reason:

The affected code seems to only be from REL1_44 forward (see also: https://github.com/wikimedia/mediawiki-extensions-IPInfo/tree/REL1_43)

https://gerrit.wikimedia.org/r/1165043