Server can invalidate client protections against chat chain breaking
A server can, by sending two ClientboundPlayerInfoPacket (one removing, and one re-adding the player), invalidate the client protection for chained chat messages. This is mostly transparent for the client, unless explicitly holding tab (where you'll see a tiny flicker) or staring at the player, which has a small flicker.
There are several attack vectors for this exploit (requires both a malicious client and a malicious server), like being able to remove messages from the context, or making a vanilla client be issuing invalid (tampered-looking) reports. This is a significant issue because it breaks about everything you wpuld expect from the chat report system (valid-looking reports may actually have been tampered with, and invalid-looking reports may actually be valid).
How to exploit this, case 1: Make anyone reporting your messages be issuing an invalid (tampered-looking) report:
Player A: Message 1 (last message: 1234)
Server: Player A removed, and re-added
Player A: Message 2 (last message: 4321)
Message 2 is very bad, so a 3rd party player reports it. The report includes Message 2, does NOT include message 4321, and includes message 1. From a validity PoV, this is completely tampered with, there's something missing in the context, and the reporter could be seen as intentionally producing a fake report with tampered evidence.
How to exploit this, case 2: Gaslight removing messages from context in the report:
Player A: Message 1
Player A: Message 2 (offensive/baiting), last message: 1
Server: Player A removed, and re-added
Player A: Message 3, last message: 1
Player B: replies to the bait in an offensive manner
Player A can now make a report for player B, include message 1, message 3, and player B's response.
This report is completely valid, the chains were not violated, and player b is acking message 3 (and by extension, 1) but never is message 2 anywhere in the report.
Code analysis:
Chat chains are validated by SignedMessageValidator, this is (for the client) stored in the PlayerInfo for each other player online in the server. When it is initialized, the first message is not validated against anything. Removing a player and re-adding (from the server) will cause the "history" to be wiped client-side, so the first message after that is not validated by the victim's client.
Concerns and solutions:
There are valid situations under which a server may disconnect a player and later send more messages which break the chain (think of proxied servers, player joins server A, types, goes to server B and types, goes back to server A. Anyone in server A can't validate the chain because it would be broken anyways).
A solution for exploit case 1 would be for the client to test for the same report-validity rules that the backend checks for, and prevent the user from sending an invalid report on a vanilla client (warn them the context has been tampered with and that the report is invalid). This however does little to prevent case 2.
The client could enforce validity of chains by searching for the last message from that profile uuid's in their chat log (the one kept for issuing reports) when a PLAYER_ADD is issued, and enforce that the first message they send must be either a null last_signature (meaning, they truly re-logged, and restarted a chain) or that the lastmessage is correctly pointing to their last message sent.
Potentially, the first message of a player after a rejoin could be considered a broken chain and be painted red as unsafe, however, not disconnect the client? This would make it so proxies don't get as affected by it and don't need to re-implement the whole chat system at a proxy level, and maintain headers across multiple servers.
An alternative solution, is to only consider the first message of a player (after a rejoin) valid, if:
a) last message signature is null
b) last message signature is not found anywhere in your message log
c) last message signature is the last message in your log for that player
This i believe fixes/solves most of the issues:
- You can't just skip a message by pointing to an earlier message
- If you just joined the server, log is empty, hence, you accept any first-signature as ok (players who had joined prior to you)
- If a player uses /vanish (common staff command) or goes to a different server in the proxy, the server doesn't need to rely headers for all messages
- If a player types in another server of the network and comes back, their message won't point to anything known, so it's ok, or will point to their last msg, which is also ok
- You may not send a message which points to an earlier message of yours in an attempt to skip one in a report later
This still has the issue of ack by extension, in the sense that you may send a message which they do not receive, then send a message they do receive, and they're "acking" the whole chain, but that's a separate issue (commented in MC-255004)