Don't Believe Your Eyes - A WhatsApp Phishing Vulnerability
Imagine you have received a WhatsApp message with a link to ln.instagram.com
.
Where do you think the link leads? Instagram? Think again.
I have found a phishing vulnerability in WhatsApp that enables the best phishing attacks possible.
An attacker can send anyone a crafted message with a link that appears to lead to a legitimate website, but in fact leads to any website of the attacker’s choice.
Discovery Process
I started my research looking for a way to make a message recipient perform an HTTP request. My initial thought was to check WA’s link preview feature. I hoped the the link would be rendered twice, once by the sender and once by the receiver. In order to check my theory, I created a webhook and sent it to a friend. Sadly only one request was made, only by me.
That got me thinking. If the receiver does not render the link for themselves, that must mean I send both the link and the preview. If so, I wonder if I can send a link to one site with a preview of another?
Issue #1 - Link Preview Mismatch
I decided to take a look into what data is sent in a WA message that contains a link with a preview. My goal was to intercept the message, change the link and the preview to mismatch, and send it.
I decided to intercept a message via WA web, as hopefully that would be a faster setup than debugging an emulator. I then ran into a problem - WA uses and E2EE so I couldn’t simply modify the message with proxy like Burp Suite.
Instead of understanding the encryption mechanism, I decided insert a breakpoint a moment before the message was encrypted and sent through the websocket. WA web’s javascript was uglified and minified, however after a while of searching I found the right place.
Exactly as I suspected, the link and the preview were sent separately!
I created a message object forinstagram.com
and changed the text
property to google.com
. Unfortunately, the message that was sent did not have a preview anymore. Only a link to Google.
Blackbox testing taught me that:
Property | Purpose |
---|---|
text | The text of the message |
canonicalURL | The domain that is shown at the bottom of the preview |
matchedText | Seems to be compared against canonicalURL , also tested that its value apears in text |
I discovered that if the matchedText
was deleted from the object, I could create a mismatch!
Success! I have created a message with a preview to one site, and when clicked, leads to another. This was a great start, but I didn’t want the real link to be shown in the message. I was looking for ways to hide the message text.
Issue #2 - Disguising Links (2K2E)
I remembered that some unicode characters can change the representation of text, so I tried fuzzing characters to see their effect:
1
2
3
4
5
6
7
8
9
function fuzz(start, end) {
for (let i = start; i <= end ; i++) {
j = i.toString(16)
if (j.length < 4) { j = "0".repeat(4 -j.length) + j}
msg += eval("\"\\u"+j+"\"")
+ "https://google.com/" + i.toString(16)
+ "\n"
}
}
I observed the results and one result caught my eye. One of the lines (202e
) was in reverse:
Apparently, the unicode character U+202E
(Right-To-Left Override) alters the way that text is presented to the user by displaying it in reverse order.
That was a great start but this link looks horrible. Nobody would click it.
Crafting A Mirror URL
I wanted to create a URL that, when reversed, looked like https://instagram.com
. This means that the link should be moc.margatsni//:sttph
(\u202e
+moc.margatsni//:sttph
= https://instagram.com
)
This fist problem is the TLD. I cannot register a .margatsni
domain.
My solution was to find a TLD that could look like a subdomain. For example the TLD .nl
(Netherlands) would result in ln.instagram.com
(ln
as in link)
Problem number two was that the URL needed to start with https://
in order to look legitimate.
My solution to this was to append the string //:sptth
(which is a valid path) to the URL, so that https://moc.margatsni.nl//:sttph
would appear as https://ln.instagram.com//:sptth
With a mirror URL and the
U+202E
character, an attacker can make any URL look like any other!
I call this vulnerability 2K2E
Final Result
Finally I have a legitimate looking URL that seems like it leads to Instagram, when in fact it leads to my blog Security Is Broken.
Attack Scenario
Utilizing both these issues can allow attackers to perform phishing attacks where they construct legitimate looking links that lead to malicious websites.
The full attack flow:
- An attacker purchases the mirror domain of the site they would like to impersonate. For example
moc.margatsni.nl
to impersonateln.instagram.com
- The attacker creates a message with a link to original domain in order to use its preview.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{ "text": "https://instagram.com/", "matchedText": "https://instagram.com/", "canonicalUrl": "https://instagram.com/", "description": "Create an account...", "title": "Instagram", "jpegThumbnail": {}, "previewType": 0, "mediaKey": {}, "mediaKeyTimestamp": 1693302818542, "thumbnailDirectPath": "/v/t62.36...", "thumbnailSha256": {}, "thumbnailEncSha256": {}, "thumbnailHeight": 1024, "thumbnailWidth": 1024, "inviteLinkGroupTypeV2": 0 }
- The attacker then removes the
matchedText
property and changes thetext
property to the following value:U+202E
+ “URL to the mirror domain” +//:sptth
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
{ "text": "\u202ehttps://moc.margatsni.nl//:sptth", "canonicalUrl": "https://instagram.com/", "description": "Create an account...", "title": "Instagram", "jpegThumbnail": {}, "previewType": 0, "mediaKey": {}, "mediaKeyTimestamp": 1693302818542, "thumbnailDirectPath": "/v/t62.36...", "thumbnailSha256": {}, "thumbnailEncSha256": {}, "thumbnailHeight": 1024, "thumbnailWidth": 1024, "inviteLinkGroupTypeV2": 0 }
- Finally the attacker sends the crafted message to their victim
Meta’s Response
Because we support many different platforms and environments, there are a significant number of ways that some platform could choose to normalize a URL differently than our server-side logic does. To address that, we have systems in place which allow us to adjust our URL normalization logic dynamically in the event of real-world spam and abuse.
Sadly, Meta has shown no intention to resolve this security issue and from their response it seems that they will try to stop these attacks only if their systems detect them as spam. This means that WhatsApp users can only cross their fingers that they won’t fall victims to 2K2E attacks.
Apposed to WhatsApp, other platforms such as X, TikTok, and Pinterest, all have sanitization of the U+202E
character.
Mitigation
Because Meta has no intention of fixing this issue, links on WhatsApp cannot be trusted. In order to not fall victim to a 2K2E phishing attack, before clicking on a link, copy it. The clipboard preview should show the link address while sanitizing the U+202E
character.
Update
I have found other services that do not have proper sanitization and are vulnerable to 2K2E as well.