Не верь своим глазам! Обманчивый Clickjacking в WhatsApp

Не верь своим глазам! Обманчивый Clickjacking в WhatsApp

@cybred

Представьте, что вы получили сообщение в WhatsApp со ссылкой на ln.instagram.com. Как вы думаете, куда ведет эта ссылка? Instagram? Подумайте дважды.

Я нашел Clickjacking в WhatsApp, который позволяет провести фишинговую атаку. Атакующий может отправить сообщение со ссылкой, которая ведет на легитимный вебсайт, но на самом деле направляет пользователя на сайт злоумышленника.

Процесс обнаружения

В начале своего исследования я пытался найти способ, как заставить получателя отправить HTTP-запрос. Я надеялся, что у меня получится сделать это с помощью превью - для этого сообщение должно было отрендериться дважды - один раз у отправителя, и один раз у получателя. Чтобы проверить свою теорию я создал вебхук и отправил его своему другу. К сожалению, запрос отправился единожды, - только у меня.

Это заставило задуматься. Если превью рендерится не на стороне получателя, то, быть может, я могу манипулировать и ссылкой, и самим превью. Если это действительно так, могу ли я отправить ссылку на один сайт, а сгенерировать превью - уже с другим?

Проблема #1. Мисматчинг превью

Я обозначил свою цель - перехватить сообщение в WhatsApp со ссылкой на превью, подменить ссылку, убедиться, что превью и фактический редирект отличаются, и отправить его.

Я решил использовать веб-версию WA, потому что надеялся, что этот путь окажется быстрее, чем отладка через эмулятор. Затем я столкнулся с проблемой - WA использует E2EE, поэтому я не мог так просто модифицировать запрос через прокси, вроде Burp Suite.

Вместо того, чтобы разобраться в процессе шифрования, я решил поставить брейкпоинт до того, как сообщение будет зашифровано, и отправить его через веб-сокет. Javascript в веб-версии WA уродлив и минифицирован, однако, немного повозившись, я смог найти правильную точку.

К счастью, как я и предполагал, ссылка и превью отправляются раздельно. Я создал сообщение со ссылкой на instagram.com и сменил значение свойства text на google.com. К сожалению, сообщение отправилось без превью. Только со ссылкой на Google. Спустя какое-то время, я распарсил еще пару свойств:

text - текст сообщения
canonicalURL - домен, который отбражается под превью
matchedText - кажется, что оно сравнивается с canonicalURL, и является частью свойства text

Я заметил, что если удалить matchedText, то получится создать мисматч.

Успех! Я создал сообщение, превью которого ведет на один сайт, а клик - на другой. Это послужило отличным стартом, но я не хотел, чтобы настоящая ссылка отображалась в сообщении. Поэтому я решил найти способ скрыть настоящий текст сообщения.

Проблема #2. Маскировка ссылок (2K2E)

Я помнил одну интересную особенность, - некоторые Unicode-символы могут влиять на то, как будет отображаться текст. Поэтому я попытался пофаззить их, чтобы посмотреть, к чему это приведет

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" 
 }
}

Взглянув на результат, я не поверил своим глазам. Строка с 202e отображалась справа налево:

Похоже, что Unicode-символ U+202E заставляет текст отображаться наоборот.

Это действительно интересно, но ссылка по-прежнему выглядит ужасно. Никто не кликнет на нее.

Создаем зеркальную ссылку

Я хотел создать URL, который, в случае реверса, выглядел бы как https://instagram.com. Это значит, что ссылка должна быть moc.margatsni//:sttph (\u202e+moc.margatsni//:sttph = https://instagram.com).

Первая проблема - это TLD. Я не мог зарегистрировать домен .margatsni.

Возможным решением было найти такой TLD, который выглядел бы как поддомен. Например, с помощью TLD .nl (Нидерланды) можно было бы добиться результата с такой ссылкой ln.instagram.com.

Вторая проблема была в том, что URL должен был начинаться на https://, чтобы выглядеть легитимным. Моим решением было добавить в строку //:sptth (что является валидным path) в URL так, чтобы строка https://moc.margatsni.nl//:sttph превратилась в https://ln.instagram.com//:sptth.

Со ссылкой, которая содержит символ U+202E, атакующий может создать URL, который будет выглядеть не тем, чем кажется. Я назвал это 2K2E.

Финальный результат

Наконец, я нашел URL, который выглядел как ссылка на Instagram, но, на самом деле, вел на мой блог.

Сценарий атаки

Используя обе этих ошибки, атакующий может осуществить фишинговую атаку, где ссылка будет выглядеть легитимной, но будет вести на вредоносный сайт.

Полный флоу:

1. Атакующий покупает зеркальный домен для сайта, в который он хочет имперсонифицироваться. Например, moc.margatsni.nl превратится в ln.instagram.com

2. Атакующий создает сообщение со ссылкой на оригинальный домен, который он будет использовать в качестве превью

{
 "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
}

3. Дальше он удаляет свойство matchedText и заменяет text на значение U+202E + "URL to the mirror domain" + //:sptth

{
 "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
}

4. Наконец, атакующий отправляет созданное сообщение своей жертве.

Ответ Meta

Поскольку мы поддерживаем множество различных платформ и сред, существует значительное количество способов, с помощью которых та или иная платформа может нормализовать URL-адрес иначе, чем это делает наша серверная логика. Для решения этой проблемы у нас есть системы, которые позволяют нам динамически корректировать логику нормализации URL в случае возникновения спама и злоупотреблений в реальном мире.

К сожалению, Meta не захотела исправлять эту ошибку. На основе их ответа, я сделал вывод, что они смогут оставить такую волну фишинга, если их системы распознают ее как спам. Это означает, что пользователи WhatsApp могут только скрестить свои пальцы, чтобы не стать жертвами 2K2E-атак.

Другие сервисы, вроде X, TikTok, и Pinterest, санитизируют символ U+202E. Но есть и те, кто этого не делает:

  • WhatsApp (вся статья о нем)
  • Facebook
  • Android Messages
  • Google Keep
  • Google Photos

Оригинал статьи на английском языке.

Переведено и адаптировано специально для @cybred.

Report Page