Usurping Mastodon instances - (CVE-2023-42451)

This blog post gives details about the GHSA-v3xf-c9qf-j667 vulnerability (for which GitHub issued CVE-2023-42451) and how it could be exploited. It is the second out of the 2 vulnerabilities that I reported to the Mastodon security team in August 2023 (more context can be found in the first blog post).

tl;dr: under certain conditions, an attacker could have usurped your Mastodon account.

All Mastodon versions were affected by this vulnerability. If you are a Mastodon instance admin and didn’t apply an update since September 19, please upgrade (patched versions are: 3.5.14, 4.0.10, 4.1.8, 4.2.0-rc2).


From the advisory:

Old domain name normalization code in Mastodon incorrectly stripped / from domain names, removing any occurrence from the string, not just occurrences at the end of the string. This allows attackers to impersonate domains, provided they are able to register a domain name that happens to be a textual prefix of the impersonated domain.

The bug is deadly simple: in some parts of the code, slashes are removed from the domain tied to an account (the presumed original intent is to strip trailing slashes, such as in domain.example/). This behavior could have been harmless if it wasn’t used to validate HTTP signatures. With no surprises, the patch is trivial: domain.delete('/') was replaced with domain.delete_suffix('/').

An attacker can misuse this behavior to spoof Mastodon instances. It requires the registration of a malicious domain which matches an instance domain prefix. As an example, the owner of the DNS (which was created in November 2022 according to whois) can spoof the instance.

This scenario isn’t that unrealistic since:


Let’s consider these 3 fictional actors:

Role Details Comment
Usurped account Any account on
Attacker DNS owned by the attacker, starting with mastodon.* to match
Targeted instance Whatever vulnerable instance

Bypassing the HTTP signature mechanism leads to attacks such as:


HTTP Signatures

Mastodon refers to the software, but also to the self-hosted, globally interconnected microblogging community which is composed of Mastodon instances. These instances communicate through HTTP requests which usually embed an HTTP signature to prove their authenticity. From the documentation:

HTTP Signatures is a specification for signing HTTP messages by using a Signature: header with your HTTP request. Mastodon requires the use of HTTP Signatures in order to validate that any activity received was authored by the actor generating it.

Public keys can be retrieved easily to verify HTTP signatures. For instance:

$ wget -q --header 'Accept: application/json' -O- | jq .publicKey
  "id": "",
  "owner": "",
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtpNfuGPl/WTnSq3dTurF\nMRelAIdvGVkO/VKYZJvIleYA27/YTnpmlY2g+0az4xEhOBtVNA1cTpS63CdXRyNz\ncH/GZtzxkdxN91vZSw0JVy+wG34dzwcq1KWFDz9D/5Tqf16KUJH+TDTlxdOBds91\nIZg+TTkiT+xfnSiC5SLMnn1dTzCW9P0yNJxpn37z7p6pEs63X1wstEEX1qGDUQTO\n1JICpKDjuQZMlioAAA5KG25tg2f+zKlv5M/NI33DblquyJ7TYvIpDN8hsFCRjuvA\nmjtKz/1XIRvQkeKND3UkqX8s6qTGyNOjcT86qt9BqYHYGuppjpRG/QNGoKYalio1\nwwIDAQAB\n-----END PUBLIC KEY-----\n"

Replacing the Private Key

Using the redirection mechanisms in the Webfinger code, an attacker can bypass the signature verification with a malicious DNS and eventually create or update an account on a Mastodon instance with arbitrary information.

Let’s consider the previous fictional actors. A simple account search triggers Webfinger requests. Searching for the account on leads to the following GET requests issued by and malicious responses issued by

GET /.well-known/webfinger?
200 {
      "subject": "",
      "links": [ { "href": "" } ]

GET /cial/.well-known/webfinger?
200 {
      "subject": "",
      "links": [ { "href": "" } ]

GET /cial/users/donald
200 {
      "id": "",
      "preferredUsername": "donald",
      "publicKey": {
        "id": "",
		"publicKeyPem": "-- ATTACKERKEY --"

Webfinger results in @username: donald and @domain:, and an arbitrary public key generated by the attacker.

The Account is eventually created or updated by ActivityPub::ProcessAccountService on TagManager.normalize_domain will eventually delete / from the domain, which results in a class Account instance with @domain being

The Account is created if it doesn’t exist, otherwise it’s updated using the information provided by the attacker (public key, avatar, image, URLs, etc.).


Bypassing the HTTP signature verification allows an attacker to spoof requests from an actor. An exploit has been developed to demonstrate the vulnerability and shared with Mastodon’ security team.

Test environment

To test the exploit, I installed Mastodon on a server whose name is mastodon.local. This DNS has no importance and could be anything (eg., but it’s the one displayed in screenshots below.

In order to usurp, I modified /etc/hosts and created a self-signed certificate since I don’t own the DNS:

$ openssl req -x509 -newkey rsa:4096 -nodes -out fullchain.pem -keyout privkey.pem -days 365
$ sudo cp fullchain.pem /usr/local/share/ca-certificates/
$ sudo update-ca-certificates
Role Details
Usurped account
Targeted instance https://mastodon.local


The following command line sends a (spoofed) private message from The spoofed profile looks the same as the legit one (avatar, image, URLs, etc.) but it could have been made different:

$ ./ --spoof --target mastodon.local --toot 'HACK THE PLANET! [...]'

Spoofed toot from

Super careful users can notice that the actor is spoofed because, when displayed, the original profile link is dubious (here, one has to click on menu and show the open original page link).

Dubious original profile link (

However, after having sent spoofed toots or private messages, attackers can restore the legit actor Account by triggering an action which requires an HTTP signature verification using legit URIs. Which makes spoofed toots and private messages indistinguishable from legit ones.

Follow and Private Message

The following command lines make follow admin@mastodon.local, then send a (spoofed) private message:

$ ./ --spoof --target admin@mastodon.local --follow
$ ./ --spoof --target admin@mastodon.local --pm 'URGENT! [...]'

Spoofed private message from

On the Internet

In order to prove that the exploit works outside of my local network, I bought the DNS █████████.co for 10$ and sent a spoofed private message from admin@██████████.com to a test account on

Role Details
Usurped account █████████@█████████.com
Attacker █████████.co
Targeted instance

Usurping admin@█████████.com


It seems difficult to tell whether the vulnerability was exploited in the wild and if accounts on an instance were spoofed.

I developed a quick script to list potential malicious domains, ordered by account number, which could be used by an attacker to target vulnerable instances. IIRC, I downloaded Mastodon instances lists from and it’s a bit outdated. Output excerpt:

$ ./ (214748364): ['sleeping.TO'] (1524743): ['mastodon.SO'] (909218): ['pawoo.NE'] (900701): ['mastodon.FIRE', 'mastodon.FI', 'mastodon.firefly.LA'] (479023): [daystorm.'NE', 'daystorm.NET']

Given a domain name, this Python script lists the TLDs that can used for malicious domains:

$ ./ ['SD']
$ ./ no TLD exist

If, for a given instance, no TLD exists to create a malicious domain or no malicious domain was ever registered, we are 100% sure that the vulnerability wasn’t exploited against this instance. Otherwise, I don’t know if there’s a way to tell easily if and when a domain was registered, though. Some databases and services exist, but they aren’t comprehensive and there is no data for some TLD.


Once again, if you are a Mastodon instance admin and didn’t apply an update since September 19, please upgrade (patched versions are: 3.5.14, 4.0.10, 4.1.8, 4.2.0-rc2).

Since v4.2.0, Mastodon instances automatically check for available updates and notify admins. It should help them to apply critical security updates in a timely fashion.