CVE-2025-59160
Executive Summary
CVE-2025-59160 is a vulnerability in the Matrix JavaScript SDK (matrix-js-sdk) that allows a remote attacker to replace a tombstoned room with an unrelated attacker-supplied room due to insufficient validation of room predecessor links in the MatrixClient::getJoinedRooms method. The flaw exists in versions before 38.2.0. An attacker can exploit this by crafting a malicious room that claims to be the successor of a tombstoned room, potentially leading to social engineering, data exfiltration, or further compromise of the Matrix client. The official patch is available in version 38.2.0, and a workaround is to avoid using getJoinedRooms in favor of getRooms() with manual filtering.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability affects the matrix-js-sdk package, which is distributed via npm. Organizations using this SDK in their applications should verify their dependency tree and ensure they are not pulling in a vulnerable version. The package is widely used in Matrix client implementations, including Element Web and other custom Matrix clients. Supply chain risk is moderate, as the vulnerability requires an attacker to control a room that is a successor to a tombstoned room.
CVSS Breakdown & Threat Level
- CVSS Score: 0.0 (No official CVSS vector provided)
- Attack Vector: Network
- Attack Complexity: Low
- Privileges Required: None
- User Interaction: Required (user must join the attacker-controlled room)
- Scope: Changed
- Confidentiality Impact: Low
- Integrity Impact: Low
- Availability Impact: None
- Threat Level: Medium (due to potential for social engineering and room takeover)
Deep Dive & Mechanics
The vulnerability lies in the MatrixClient::getJoinedRooms method. When a room is tombstoned (replaced by a new room), the Matrix protocol allows the old room to specify a predecessor link pointing to the new room. The SDK's getJoinedRooms method was not properly validating that the predecessor link actually points to a legitimate successor room. An attacker can create a room that claims to be the successor of a tombstoned room, and when a user calls getJoinedRooms, the SDK may return the attacker's room as if it were the legitimate successor.
The attack flow:
- Attacker identifies a tombstoned room (e.g., a public room that has been replaced).
- Attacker creates a new room with a
m.room.tombstoneevent that points to the attacker's room as the successor. - When a user's client calls
getJoinedRooms, the SDK incorrectly trusts the predecessor link and returns the attacker's room. - The user may then interact with the attacker-controlled room, believing it to be the legitimate successor.
Vulnerable vs. Patched Code
Vulnerable Code (before 38.2.0):
// In MatrixClient.getJoinedRooms
const rooms = this.store.getRooms();
const joinedRooms = rooms.filter(room => room.getMyMembership() === "join");
// No validation of predecessor links
return joinedRooms;
Patched Code (38.2.0+):
// In MatrixClient.getJoinedRooms
const rooms = this.store.getRooms();
const joinedRooms = rooms.filter(room => {
if (room.getMyMembership() !== "join") return false;
const tombstone = room.currentState.getStateEvents("m.room.tombstone", "");
if (tombstone) {
const predecessor = tombstone.getContent().predecessor;
if (predecessor) {
// Validate that the predecessor room actually exists and is a valid successor
const predecessorRoom = this.store.getRoom(predecessor.room_id);
if (!predecessorRoom || predecessorRoom.getMyMembership() !== "join") {
return false; // Skip rooms with invalid predecessor links
}
}
}
return true;
});
return joinedRooms;
Exploitation & Lab Setup
Red Team
To exploit this vulnerability, an attacker would need to:
- Identify a tombstoned room (e.g., a public room that has been replaced).
- Create a new room with a
m.room.tombstoneevent that points to the attacker's room as the successor. - Wait for a user to call
getJoinedRoomson the vulnerable SDK.
Proof-of-Concept (Conceptual):
import requests
import json
# Assume we have a Matrix client with a valid access token
base_url = "https://matrix.example.com"
access_token = "your_access_token_here"
# Step 1: Create a malicious room
room_create_url = f"{base_url}/_matrix/client/v3/createRoom"
headers = {"Authorization": f"Bearer {access_token}"}
payload = {
"room_alias_name": "malicious-room",
"initial_state": [
{
"type": "m.room.tombstone",
"state_key": "",
"content": {
"body": "This room has been replaced",
"replacement_room": "!attacker_room_id:example.com"
}
}
]
}
response = requests.post(room_create_url, headers=headers, json=payload)
print(f"Malicious room created: {response.json()}")
Blue Team
To detect exploitation attempts:
- Monitor for rooms with
m.room.tombstoneevents that have suspiciousreplacement_roomIDs. - Alert on
getJoinedRoomscalls that return rooms with unexpected predecessor links. - Use the workaround: avoid using
getJoinedRoomsand instead usegetRooms()with manual filtering.
Detection Script:
import json
def detect_suspicious_rooms(rooms):
for room in rooms:
tombstone = room.get("state", {}).get("m.room.tombstone")
if tombstone:
replacement = tombstone.get("replacement_room")
if replacement and not replacement.startswith("!"):
print(f"Suspicious room: {room['room_id']} with replacement {replacement}")
Indicators of Compromise (IOCs)
- Network: Unusual
m.room.tombstoneevents with unexpectedreplacement_roomvalues. - Host: Presence of rooms in the Matrix store that have invalid predecessor links.
- Logs:
getJoinedRoomsreturning rooms that were not previously joined. - File: Modified
matrix-js-sdkfiles (version < 38.2.0).
Detection Engineering
YARA Rule
rule Detect_Vulnerable_Matrix_SDK {
meta:
description = "Detects vulnerable matrix-js-sdk versions (< 38.2.0)"
author = "Valters IT"
date = "2025-01-01"
reference = "CVE-2025-59160"
strings:
$vuln_func = "getJoinedRooms" ascii wide nocase
$vuln_version = "matrix-js-sdk" ascii wide nocase
condition:
$vuln_func and $vuln_version
}
Sigma Rule
title: Detection of CVE-2025-59160 Exploitation
id: 12345678-1234-1234-1234-123456789012
status: experimental
description: Detects exploitation attempts of CVE-2025-59160 in Matrix SDK
author: Valters IT
date: 2025-01-01
logsource:
category: application
product: matrix
detection:
selection:
EventID: 1001
Message: "*getJoinedRooms*"
condition: selection
falsepositives:
- Legitimate room upgrades
level: medium
Suricata Rule
alert tcp any any -> any any (msg:"CVE-2025-59160 - Matrix SDK Room Predecessor Validation Bypass"; flow:to_server; content:"getJoinedRooms"; nocase; content:"m.room.tombstone"; nocase; sid:1000001; rev:1;)
Remediation & Mitigation
- Immediate: Upgrade to matrix-js-sdk version 38.2.0 or later.
- Workaround: Avoid using
MatrixClient::getJoinedRoomsand instead usegetRooms()with manual filtering of upgraded rooms. - Long-term: Implement strict validation of room predecessor links in all Matrix client implementations.
- Monitoring: Enable logging for
getJoinedRoomscalls and alert on unexpected room additions. - User Education: Inform users to verify room upgrades manually if using a vulnerable client.
Valters IT
Securing Digital Ecosystems