[Перевод] Как превратить умную колонку Google в «жучок» и получить за это $100 тысяч

c4m-wd31-yxryj6ea4obtlb9wie.jpeg


Недавно компания Google выплатила мне награду в $107500 за ответственное раскрытие проблем безопасности в умной колонке Google Home. Эти уязвимости позволяли нападающему, находящемуся в пределах беспроводного доступа, создать на устройстве бэкдор-аккаунт и удалённо отправлять команды через Интернет, получать доступ к микрофону и выполнять произвольные HTTP-запросы в локальной сети жертвы (что потенциально позволит нападающему узнать пароль Wi-Fi или получить прямой доступ к другим устройствам жертвы). Все эти проблемы были устранены.

(Примечание: я тестировал всё на Google Home Mini, но предполагаю, что такие атаки аналогичным образом работают и с другими моделями умных колонок Google)

Расследование


Я экспериментировал с Google Home и обратил внимание, насколько просто добавлять новых пользователей через приложение Google Home. Также я заметил, что привязка аккаунта к устройству даёт удивительно высокую степень контроля над ним.

В частности, функция «routines» позволяет быстро выполнять последовательности команд (например routine «доброе утро», выполняет команды «выключить свет» и «рассказать о погоде»). Через приложение Google Home можно настроить автоматический запуск routine в устройстве в определённые дни и в определённое время. По сути, routine позволяют любому человеку, аккаунт которого привязан к устройству, удалённо отправлять на него команды. Кроме удалённого управления привязанный аккаунт позволяет устанавливать на устройство «действия» (крошечные приложения).

Когда я понял, какую степень доступа даёт привязанный аккаунт, то решил изучить процесс привязки и определить, насколько легко привязать аккаунт с точки зрения нападающего.

Так с чего же начать? Существует множество разных способов реверс-инжиниринга IoT-устройств, в том числе:

  1. Получение прошивки устройства дампом или скачиванием с веб-сайта производителя.
  2. Статический анализ приложения, взаимодействующего с устройством (в данном случае это Android-приложение Google Home), например, его декомпиляция с помощью Apktool или JADX.
  3. Динамический анализ приложения во время его исполнения, например, при помощи Frida, перехватывающей методы Java и выводящей информацию о внутреннем состоянии.
  4. Перехват коммуникаций между приложением и устройством (или между приложением/устройством и серверами производителя) с помощью атаки «man-in-the-middle» (MITM).


В случае Google Home получить прошивку очень сложно, потому что на печатной плате устройства нет контактов для отладки, и единственный способ считывания флэш-памяти заключается во выпаивании чипа NAND. Кроме того, Google не публикует образы прошивок для скачивания. Впрочем, на DEFCON продемонстрировали, что это возможно (см. ниже).

При реверс-инжиниринге мне нравится по возможности начинать с атаки MITM, потому что обычно это самый простой способ разобраться, как работает устройство. Типичные IoT-устройства для общения со своими приложениями используют протоколы наподобие HTTP или Bluetooth. HTTP легко прослушать при помощи инструментов вроде mitmproxy. Я люблю mitmproxy, потому что это свободное ПО, оно имеет UI на основе терминала и предоставляет удобный Python API.

Так как у Google Home нет собственного дисплея или UI, большинство параметров контролируется через приложение Google Home. Немного погуглив, я выяснил, что люди уже начали документировать локальный HTTP API для взаимодействия с Google Home. Устройства Google Cast (в том числе Google Home и Chromecast) объявляют о себе в локальной сети при помощи mDNS, поэтому для их обнаружения можно использовать dns-sd:

$ dns-sd -B _googlecast._tcp
Browsing for _googlecast._tcp
DATE: ---Fri 05 Aug 2022---
15:30:15.526  ...STARTING...
Timestamp     A/R    Flags  if Domain               Service Type         Instance Name
15:30:15.527  Add        3   6 local.               _googlecast._tcp.    Chromecast-997113e3cc9fce38d8284cee20de6435
15:30:15.527  Add        3   6 local.               _googlecast._tcp.    Google-Nest-Hub-d5d194c9b7a0255571045cbf615f7ffb
15:30:15.527  Add        3   6 local.               _googlecast._tcp.    Google-Home-Mini-f09088353752a2e56bddbb2a27ec377a


Можно использовать nmap, чтобы найти порт, на котором работает локальный HTTP API:

$ nmap 192.168.86.29
Starting Nmap 7.91 ( https://nmap.org ) at 2022-08-05 15:41
Nmap scan report for google-home-mini.lan (192.168.86.29)
Host is up (0.0075s latency).
Not shown: 995 closed ports
PORT      STATE SERVICE
8008/tcp  open  http
8009/tcp  open  ajp13
8443/tcp  open  https-alt
9000/tcp  open  cslistener
10001/tcp open  scp-config


Мы видим HTTP-серверы на портах 8008 и 8443. Согласно неофициальной документации, ссылку на которую я привёл выше, от поддержки 8008 отказались и теперь работает только 8443. Другие порты используются для различных функций Chromecast, а неофициальная документация частично доступна в Интернете.

Давайте попробуем отправить запрос:

$ curl -s --insecure https://192.168.86.29:8443/setup/eureka_info?params=settings
{"settings":{"closed_caption":{},"control_notifications":1,"country_code":"US","locale":"en-US","network_standby":0,"system_sound_effects":true,"time_format":1,"timezone":"America/Chicago","wake_on_cast":1}}


(Мы используем --insecure, потому что устройство отправляет самостоятельно подписанный сертификат, которому доверяет приложение Google Home, но мой компьютер не доверяет)

Отлично, мы получили параметры устройства. Однако в документации говорится, что большинству конечных точек API требуется cast-local-authorization-token. Давайте попробуем что-то более интересное, например, перезагрузить устройство:

$ curl -i --insecure -X POST -H 'Content-Type: application/json' -d '{"params":"now"}' https://192.168.86.29:8443/setup/reboot
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Headers:Content-Type
Cache-Control:no-cache
Content-Length:0


Оно отклоняет запрос, потому что мы не авторизованы. Как же получить токен? В документации написано, что можно или извлечь его из папки приватных данных приложения Google Home (если на телефоне есть root), или использовать скрипт, который получает на входе имя пользователя и пароль Google, вызывает API, после чего приложение Google Home использует его для получения токена, а затем возвращает токен. Однако для обоих этих способов нужно, чтобы у нас имелся уже связанный с устройством аккаунт, поэтому я решил разобраться, как вообще выполняется привязка. Предположительно, этот токен используется, чтобы нападающий (или зловредное приложение) в локальной сети не получил доступа к устройству. То есть, для привязки аккаунта и получения токена нужно что-то большее, чем простой доступ к локальной сети? Я поискал в документации, но не нашёл упоминаний привязки аккаунтов, поэтому продолжил свое исследование.

Подготавливаем прокси


Для перехвата незашифрованного HTTP-трафика при помощи mitmproxy в Android достаточно запустить прокси-сервер, а затем сконфигурировать телефон (или только нужное приложение) так, чтобы весь трафик перенаправлялся на прокси. Однако в неофициальной документации по локальному API говорится, что Google недавно начала использовать HTTPS. Кроме того, я хотел перехватывать трафик не только между приложением и устройством Google Home, но и между приложением и серверами Google (который точно передавался по HTTPS). Я подумал, что поскольку при процессе привязки задействовались аккаунты Google, часть процесса может происходит на сервере Google, а не в устройстве.

Перехватывать HTTPS-трафик в Android сложнее, но обычно не намного. Кроме настройки параметров прокси необходимо также сделать так, чтобы приложение доверяло корневому сертификату CA mitmproxy. Новые CA можно устранавливать через настройки Android, но, к сожалению, начиная с Android 7 приложения, использующие предоставляемые системой сетевые API, больше автоматические не доверяют CA добавляемым пользователями. Если у вас есть Android-телефон с рутом, то можно напрямую изменить системное хранилище CA (расположенное в /system/etc/security/cacerts). Или же можно вручную пропатчить конкретное приложение. Однако иногда даже этого недостаточно, потому что некоторые приложения используют «SSL pinning», чтобы гарантировать, что используемый для SSL сертификат совпадает с тем, который они ожидают. Если приложение использует предоставленные системой pinning API (javax.net.ssl) или популярную HTTP-библиотеку (например, OkHttp), то это не так сложно обойти — достаточно перехватывать соответствующие методы при помощи Frida или Xposed. Хотя и Xposed, и полной версии Frida требуется рут, Frida Gadget можно использовать без рута. Если приложение использует собственный механизм пиннинга, то вам придётся выполнить его реверс-инжиринг и вручную пропатчить приложение.

Пропатчить и заново упаковать приложение Google Home невозможно, потому что оно использует Google Play Services OAuth API (то есть APK должен подписываться Google, в противном случае он будет вылетать), поэтому для перехвата его трафика необходим рут-доступ. Так как я не хотел получать рут на своём основном телефоне, а эмуляторы обычно неудобны, то я решил использовать завалявшийся у меня старый телефон. Я получил на нём рут при помощи Magisk и модифицировал системное хранилище CA, добавив в него CA mitmproxy, но этого было недостаточно, поскольку, как оказалось, в приложении Google Home используется SSL pinning. Чтобы обойти pinning, я воспользовался скриптом Frida, который нашёл на GitHub.

Теперь я мог просматривать в mitmproxy весь зашифрованный трафик:

x6bmdhlgz2lmw1kjszkq01vgptm.png


Перехватывался даже трафик между приложением и устройством. Здорово!

de5wvisczulf9wtt24uqea4-6q0.png


Следим за процессом привязки


Итак, давайте понаблюдаем, что происходит, когда новый пользователь привязывает свой аккаунт к устройству. Мой аккаунт Google уже был привязан, поэтому я создал новый аккаунт для «нападающего». Когда я открыл приложение Google Home и вошёл под новым аккаунтом (убедившись, что я подключён к той же сети Wi-Fi, что и устройство), устройство появилось в разделе «Other devices». Когда я коснулся его, то увидел такой экран:

c2diireuytbu4r4qvgdxoomxo4o.png


Я нажал кнопку, и устройство предложило для продолжения установить приложение Google Search. Предполагаю, что через это приложение выполняется настройка сопоставления голоса (Voice Match), но если я нападающий, то мне не нужно добавлять свой голос в устройство — я хочу только привязать свой аккаунт. Так можно ли привязать аккаунт без  Voice Match? Я подумал, что это возможно, ведь первоначальная настройка устройства целиком выполнялась в приложении Home, и от меня не требовали включить Voice Match на основном аккаунте. Я уже был готов выполнить сброс к заводским настройкам и понаблюдать за первоначальной привязкой аккаунта, но потом кое-что понял.

Большая часть внутренней архитектуры Google Home схожа с устройствами Chromecast. Согласно докладу с DEFCON, в устройствах Google Home используется та же операционная система, что и в Chromecast (разновидность Linux). Локальный API тоже выглядит знакомым. На самом деле, имя пакета приложения Home заканчивается на chromecast.app, и раньше оно просто называлось Chromecast. В то время его единственная задача заключалась в настройке устройств Chromecast. Теперь оно отвечает за настройку и управление не только Chromecast, а всеми умными домашними устройствами Google.

Поэтому почему бы нам не понаблюдать, как работает процесс привязки Chromecast, а затем не воспроизвести его с Google Home? Это будет проще, потому что Chromecast не поддерживают Voice Match (как и Google Assistant). К счастью, у меня под рукой было несколько Chromecast. Я подключил один из них и нашёл его в приложении Home:

hdlgbdounptojgkrpoaphr9q6ge.png


Теперь мне оставалось только коснуться баннера «Enable voice control and more» и подтвердить, после чего мой аккаунт был привязан! Отлично, теперь посмотрим, что произошло на стороне сети:

a5chtwj6pklszzk3mbighso2aq4.png


Мы видим POST-запрос к конечной точке /deviceuserlinksbatch в clients3.google.com:

nlgzxb4aa3itkaywy8urflgoax4.png


Это двоичная полезная нагрузка, но мы сразу же можем увидеть, что она содержит информацию об устройстве (например, имя устройства «Office TV»). Мы видим, что content-type является application/protobuf. Protocol Buffers — это формат сериализации двоичных данных Google. Как и в JSON, данные хранятся в парах «ключ-значение». Клиент и сервер, обменивающиеся данными protobuf, имеют копию файла .proto, определяющего имена полей и типы данных (например, uint32, bool, string, etc). В процессе кодирования данные обрезаются и остаются лишь номера полей и wire type. К счастью, wire type практически напрямую транслируются обратно в исходные типы данных (обычно есть только незначительное количество вариантов того, какой исходный тип данных может основываться на wire type). Google предоставляет инструмент командной строки protoc, позволяющий кодировать и декодировать данные protobuf. Опция --decode_raw приказывает protoc выполнять декодирование без файла .proto, угадывая типы данных. Такого сырого декодирования обычно достаточно для того, чтобы понять структуру данных. Но если она кажется неправильной, то можно создать собственный файл .proto со своими догадками о типах данных, попытаться декодировать их, и, если результат будет нелогичным, продолжать изменять файл .proto до получения нужного результата.

В нашем случае применение --decode_raw приводит к созданию идеально читаемого результата:

$ protoc --decode_raw < deviceuserlinksbatch
1 {
  1: "590C[...]"
  2: "MIIDojCCAoqgAwIBAgIEVcQZjzANBgkqhkiG9w0BAQUFADB5MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzENMAsGA1UECwwEQ2FzdDEZMBcGA1UEAwwQQ2hyb21lY2FzdCBJQ0EgMzAeFw0xNTA4MDcwMjM1NTlaFw0zNTA4MDIwMjM1NTlaMHwxEzARBgNVBAoMCkdvb2dsZSBJbmMxDTALBgNVBAsMBENhc3QxFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRwwGgYDVQQDDBMzVzM3OTkgRkE4RkNBMzJDRjBEMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAleog/oEXK6PKyGHDIYcDwT2Xl8GLOFuhxQh/K+dTahxex9+4mLAXx5v2s75Iwv9jcXEpD5NTvjNXx20B0/rfpYORHbcm3UEwFWGnP5uvKIyLar+rC7Az5ZPzPXMx7xX6Br68/gOXMGJd17OG/m0rduBZNjmasBb7+Zu8jS38cv+N3S7yTobJbagrHxIufa7gX+rO2f3/jF2EutgcA4lIm5r/2J34fkYTMXnxElJCUv/b1COuk0FZTei4mooJ+TvcQE2ljgHOSvzGnZuT+QWch8TyRjIjKuIK4dB1UIcSvmQoq9PTbfzWCTcW1fREdPtnta6pyWIzmoJ9+3AhVnWAhwIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAv11SILN9BfcUthEFq/0NJYnNIFoza21T1BR9qYymFKtUGOplFav00drHziTzUUCNUzbLGnR/yKXxXYlgUOlscEIHxN0+11tvWslHQk7Xgz2RUerBXy9l+vSwp87F8YVECny8lMFZi0T6hHUvtuM6O9qovQKS6ORx3GmZKlNOsNspPnF8IVpN+KtIiopL6vf84iCpbx+dQoOfUOZsbZ+XSxwT34yeNFXqdAIFwP1maMmPZZYnQrDYyUdyowYzk48fDG2QDhFf7dLjtCngcQ83MWWU5nx9On67hnj2VeFGKWsner4cwjs0+iVafUGiWD0tZejVXHSrR7TBouqOf9eG6Q=="
  6: "Office TV"
  7: "b"
  8: 0
  9 {
    1: 1
    2: 0
  }
  10: 2
  12: 0
}


Похоже, что полезная нагрузка запроса привязки состоит из трёх элементов: имени устройства, сертификата и «Cloud ID». Я сразу же узнал эти значения из предыдущих запросов локального API /setup/eureka_info. То есть, похоже, что процесс привязки заключается в следующем:

  1. Получаем информацию об устройстве через его локальный API.
  2. Отправляем запрос привязки с этой информацией серверу Google.


Я хотел использовать mitmproxy для повторной отправки модифицированной версии запроса, заменив информацию Chromecast на информацию Google Home. Потом мне захотелось создать файл .proto, чтобы можно было использовать protoc --encode для создания запросов привязок с нуля, но на этом этапе я просто хотел быстро протестировать, сработает ли это. Выяснилось, что можно заменять любые строки в двоичной полезной нагрузке, не вызывая никаких проблем, при условии, что они имеют одинаковую длину. cloud ID и cert имели одинаковую длину, однако длина имени («Office speaker») отличалась, поэтому я переименовал устройство в приложении Home, чтобы длина совпадала. Затем я отправил модифицированный запрос, и всё сработало. Параметры Google Home были разблокированы в приложении Home. В mitmproxy я видел, что вместе с запросами локального API передавался локальный токен аутентификации устройства.

Воссоздаём реализацию на Python


Далее я решил воссоздать реализацию процесса привязки в скрипте на Python, чтобы мне больше не приходилось возиться с приложением Home.

Чтобы получить требуемую информацию об устройстве, просто нужно было отправить следующий запрос:

GET https://[Google Home IP]:8443/setup/eureka_info?params=name,device_info,sign


Воссоздать сам запрос привязки было немного сложнее. Сначала я изучил упомянутый в неофициальной документации API скрипт, вызывающий cloud API Google. В нём используется библиотека gpsoauth, которая реализует на Python поток логина Google в Android. По сути, она превращает имя пользователя и пароль Google в токены OAuth, которые можно использовать для вызова незадокументированных Google API. Она используется некоторыми неофициальными Python-клиентами сервисов Google, например, gkeepapi для Google Keep.

Я воспользовался mitmproxy и gpsoauth, чтобы разобраться в запросе привязки и его воссоздании. Он выглядит так:

POST https://clients3.google.com/cast/orchestration/deviceuserlinksbatch?rt=b

Authorization: Bearer [токен из gpsoauth]
[...неинтересные заголовки, добавленные приложением Home...]
Content-Type: application/protobuf

[описанная выше полезная нагрузка protobuf с информацией об устройстве]


Для создания полезной нагрузки protobuf я написал простой файл .proto для запроса привязки, чтобы можно было использовать protoc --encode. Я дал известным мне полям понятные имена (например, device_name), а неизвестным полям назначил обобщённые имена:

syntax = "proto2";

message LinkDevicePayload {
    message Payload {
        message Data {
            required uint32 i1 = 1;
            required uint32 i2 = 2;
        }
        required string device_id = 1;
        required string device_cert = 2;
        required string device_name = 6;
        required string s7 = 7;
        required uint32 i8 = 8;
        required Data d = 9;
        required uint32 i10 = 10;
        required uint32 i12 = 12;
    }
    required Payload p = 1;
}


В качестве первого теста я использовал этот .proto для кодирования сообщения с теми же значениями, которые я перехватил из приложения Home, и убедился, что двоичный результат получился таким же.

В итоге у меня получился скрипт на Python, получающий на входе учётные данные Google и IP-адрес, и использующий их для привязки аккаунта к устройству Google Home по указанному IP-адресу.

Дальнейшее исследование


Написав скрипт на Python, я должен был начать думать с точки зрения нападающего. Какой уровень контроля над устройством даёт нам привязанный аккаунт и каковы потенциальные сценарии атак? Сначала я нацелился на функцию routines, позволяющую удалённо выполнять в устройстве голосовые команды. Проведя изучение предыдущих атак на устройства Google Home, я нашёл атаку «Light Commands», которая дала мне понимание того, какие команды может использовать нападающий:

  • управление умными домашними выключателями;
  • открывание умных дверей гаража;
  • совершение онлайн-покупок;
  • удалённая разблокировка и запуск некоторых автомобилей;
  • открывание умных замков скрытным брутфорсом PIN-кода пользователя.


Я хотел пойти глубже и придумать атаку, которая бы работала на всех устройствах Google Home, вне зависимости от наличия у пользователя других умных устройств. Я попытался придумать способ применения голосовой команды для активации микрофона и слива данных. Возможно, мне удастся использовать голосовые команды для загрузки в устройство приложения, включающего микрофон? После изучения документации «conversational actions» стало понятно, что можно создать приложение для Google Home, а затем вызывать его на привязанном устройстве при помощи команды «talk to my test app». Однако возможности таких «приложений» довольно ограничены. Они не имеют доступа к сырому звуку с микрофона, а получают только транскрипцию сказанного пользователем. Они даже не выполняются на самом устройстве: серверы Google общаются с приложением через веб-хуки от лица устройства. Более любопытными показались мне «действия умного дома», но их я исследовал уже позже.

Внезапно меня озарило: эти устройства поддерживают команду «call [номер телефона]». По сути, можно использовать эту команду, чтобы приказать устройству отправлять данные со своего микрофона на какой-нибудь произвольный телефонный номер.

Создаём зловредные routine


Интерфейс для создания routine в приложении Google Home выглядит так:

8iz24atq1xfzencqwmfdfqwedns.png


При помощи mitmproxy я узнал, что на самом деле это просто WebView, встраивающий веб-сайт https://assistant.google.com/settings/routines, который загружает обычный веб-браузер (если вы залогинены в аккаунт Google). Это немного упростило реверс-инжиниринг.

Я создал routine для выполнения команды «call [мой номер телефона]» по средам в 20:26 (тогда была среда, 20:25). Для routine, выполняемых автоматически в определённое время, необходимо указать «устройство для звука» (устройство, в котором будет выполняться routine). Его можно выбрать из списка устройств, привязанных к вашему аккаунту:

uaejfu0l37_mqvniplqkhcoy7do.png


Минутой позже routine выполнилась в Google Home и позвонила на мой телефон. Я принял вызов и прослушал, как говорю в микрофон Google Home. Отлично!

(Позже, изучая сетевые запросы, я обнаружил, что можно указать не только час и минуту активации routine, но и точную секунду, поэтому мне достаточно было подождать до активации routine не примерно минуту, а всего несколько секунд.)

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


У меня было ощущение, что Google не намеревалась так легко предоставлять удалённый доступ к сигналу микрофона в Google Home. Я быстренько придумал сценарий атаки:

Нападающий хочет пошпионить за жертвой.
  1. Жертва устанавливает зловредное Android-приложение нападающего.
  2. Приложение обнаруживает Google Home в сети при помощи mDNS.
  3. Приложение использует полученный автоматически обычный доступ по локальной сети для тайной отправки двух HTTP-запросов, необходимых для привязки аккаунта нападающего к устройству жертвы (никаких специальных разрешений не требуется).

Теперь нападающий может шпионить за жертвой через Google Home.


Но для этого всё равно требуется социальный инжиниринг и взаимодействие с пользователем, что неидеально с точки зрения нападающего. Можно ли сделать атаку более удобной?

С более абстрактной точки зрения суммарная информация об устройстве (name, cert и cloud ID) используется как «пароль», дающий удалённый контроль над устройством. Устройство раскрывает этот пароль через локальную сеть при помощи локального API. Есть ли у нападающего другие способы доступа к локальному API?

В 2019 году известность получил CastHack: обнаружилось, что тысячи устройств Google Cast (в том числе Google Home) были видны из публичного Интернета. Поначалу считалось, что проблема вызвана тем, что устройства использовали UPnP для автоматического открытия портов маршрутизатора, связанных с воспроизведением (8008, 8009 и 8443). Однако оказалось, что устройства Cast используют UPnP только для локального обнаружения, а не для перенаправления портов. Поэтому причиной, вероятно, была широко распространённая ошибочная сетевая конфигурация (возможно, как-то связанная с UPnP).

Обнаружившие CastHack люди не осознавали истинного уровня доступа, предоставляемого локальным API (в сочетании с cloud API):

Что с помощью этого могут сделать хакеры?

Удалённо воспроизводить медиа на вашем устройстве, переименовывать его, сбрасывать до заводских настроек или перезапускать устройство, вынудить его забыть все сети WiFi, вынудить связаться с новой Bluetooth-колонкой/точкой WiFi, и так далее.


(Всё это конечные точки локального API, уже задокументированные сообществом. Это было ещё до того, как локальный API начал требовать токена аутентификации)

Чего хакеры НЕ могут сделать с этим?

Если считать, что Chromecast/Google Home — это ваша единственная проблема, то хакеры НЕ МОГУТ получать доступ к другим устройствам в сети или отслеживать информацию вне пределов WIFI-точек и Bluetooth-устройств. Также они не имеют доступа к вашему личному аккаунту Google и к микрофону Google Home.


Существуют сервисы наподобие Shodan, позволяющие сканировать Интернет в поисках открытых портов и уязвимых устройств. При помощи простых поисковых запросов мне удалось найти сотни устройств Cast с открытым портом 8443 (локальный API). Я не исследовал этот вопрос долго, потому что в конечном итоге плохую конфигурацию маршрутизатора Google исправить не в состоянии.

Однако когда я читал про CastHack, то обнаружил датированные ещё 2014 годом (!) статьи о RickMote — разработанном исследователем безопасности Bishop Fox Дэном Петро proof of concept, захватывающем ближайшие Chromecast и воспроизводящем «Never Gonna Give You Up» с YouTube. Петро обнаружил, что когда Chromecast теряет соединение с Интернетом, то переходит в «режим настройки» и создаёт собственную открытую сеть Wi-Fi. Изначально она предназначается для того, чтобы позволить владельцу устройства подключиться к этой сети из приложения Google Home и сбросить параметры Wi-Fi (например, в случае смены пароля). RickMote пользуется этим поведением.

Оказалось, что обычно очень просто заставить близкие устройства отключиться от их сети Wi-Fi: достаточно отправить на целевое устройство набор пакетов «deauth». WPA2 обеспечивает сильное шифрование фреймов данных (если выбран хороший пароль). Однако «управляющие» фреймы, например, фреймы деаутентификации (приказывающие клиентам отсоединиться) не зашифрованы. 802.11w и WPA3 поддерживают зашифрованные управляющие фреймы, однако в Google Home Mini поддержки ни того, ни другого нет (см. примечание в конце статьи). (Но даже если бы она была, для их работы их должен был бы поддерживать и маршрутизатор, а из-за потенциальных проблем с совместимостью в домашних маршрутизаторах потребительского уровня это сейчас редкость. Кроме того, даже если бы их поддерживали и устройство, и маршрутизатор, у нападающего всё равно есть другие способы нарушить работу вашего Wi-Fi. Всегда доступен вариант простого глушения канала, хотя для этого и требуется специализированное нелегальное оборудование. В конечном итоге, Wi-Fi — это плохой выбор для устройств, которые постоянно должны быть подключены к Интернету.)

Я захотел проверить, по-прежнему ли используется это поведение «режима настройки» в Google Home. Установив aircrack-ng, я применил следующую команду для атаки deauth:

aireplay-ng --deauth 0 -a [router BSSID] -c [device MAC address] [interface]


Устройство Google Home мгновенно отключилось от сети и создало собственную:

sywjh83jaqhmmfv6hr-nj4udh5y.png


Я подключился к сети и использовал netstat, чтобы получить IP маршрутизатора (маршрутизатором был Google Home), и увидел, что он назначил себе адрес 192.168.255.249. Я отправил запрос к локальному API, чтобы проверить, работает ли он:

$ curl -s --insecure https://192.168.255.249:8443/setup/eureka_info?params=name,device_info,sign | python3 -m json.tool
{
    "device_info": {
        [...]
        "cloud_device_id": "590C[...]",
        [...]
    },
    "name": "Office speaker",
    "sign": {
        "certificate": "-----BEGIN CERTIFICATE-----\nMIID[...]\n-----END CERTIFICATE-----\n",
        [...]
    }
}


Потрясающе! Он работал! Благодаря этой информации можно было привязать аккаунт к устройству и удалённо контролировать его.

Ещё более классный сценарий атаки


Нападающий хочет пошпионить за жертвой. Нападающий может подобраться к области беспроводного действия Google Home (но у него НЕТ пароля от Wi-Fi жертвы).
  1. Нападающий обнаруживает Google Home жертвы, прослушивая MAC-адреса с префиксами, связанными с Google Inc. (например, E4:F0:42).
  2. Нападающий отправляет пакеты deauth для отключения устройства от его сети и заставляет перейти в режим настройки.
  3. Нападающий подключается к сети настройки устройства и запрашивает информацию устройства.
  4. Нападающий подключается к Интернету и использует полученную информацию об устройстве для привязки своего аккаунта к устройству жертвы.

Теперь нападающий может шпионить за жертвой по Интернету через Google Home. Больше нет необходимости находиться поблизости от устройства.


Что ещё мы можем сделать?


Очевидно, что привязанный аккаунт даёт огромную степень контроля над устройством. Мне захотелось узнать, может ли нападающий сделать что-нибудь ещё. Мы будем учитывать нападающих, ещё не находящихся в сети жертвы. Возможно ли взаимодействовать с другими устройствами жертвы (а, может быть, и атаковать их) через скомпрометированный Google Home? Мы уже знаем, что при помощи привязанного аккаунта можно:

  • получать локальный токен аутентификации и менять параметры устройства через локальный API;
  • удалённо исполнять команды в устройстве при помощи routine;
  • устанавливать «действия» (action), то есть что-то типа приложений в песочнице.


Выше мы изучали «Conversational Actions» и выяснили, что они слишком ограничены песочницей, чтобы быть полезными для нападающего. Но есть и другой тип действия: «Smart Home Actions». Производители устройств (например, Philips) могут использовать их для добавления поддержки своих устройств на платформе Google Home (например, когда пользователь говорит «включи свет», лампочки Philips Hue получат команду «включиться»).

Особенно интересным при чтении документации мне показался Local Home SDK. Smart Home Actions используются только для запуска через Интернет (как и Conversational Actions), но недавно (в апреле 2020 года) Google добавила поддержку их локального выполнения для снижения задержек.

SDK позволяет написать на TypeScript или JavaScript локальное приложение, содержащее бизнес-логику умного дома. Устройства Google Home или Google Nest могут загружать и исполнять приложение внутри устройства. Приложение напрямую общается с умными устройствами по Wi-Fi в локальной сети, выполняя команды пользователя по имеющимся протоколам.


Звучит многообещающе. Я изучил, как это работает, и выяснил, что эти локальные домашние приложения не имеют прямого доступа к локальной сети. Невозможно просто подключиться к любому IP-адресу; вместо этого нужно задать «конфигурацию сканирования» при помощи вещания по mDNS, UPnP или UDP. Google Home сканирует сеть от лица пользователя и при нахождении любого подходящего устройства возвращает объект JavaScript, позволяющий приложению взаимодействовать с устройством по TCP/UDP/HTTP.

Можно ли это обойти? Я заметил, что в документации что-то говорится об отладке при помощи Chrome DevTools. Оказалось, что когда локальное домашнее приложение запущено в тестовом режиме (развёрнуто в собственном аккаунте разработчика), Google Home открывает порт 9222 для Chrome DevTools Protocol (CDP). Доступ по CDP обеспечивает полный контроль над экземпляром Chrome. Например, можно открывать или закрывать вкладки и перехватывать сетевые запросы. Это заставило меня задуматься: возможно, я смогу создать такую конфигурацию сканирования, которая приказывает Google Home сканировать в поисках себя, чтобы можно было подключиться к CDP, получить контроль над запущенным в устройстве экземпляром Chrome и использовать его для создания произвольных запросов в локальной сети.

При помощи привязанного аккаунта я создал локальное домашнее приложение и настроил конфигурацию сканирования на поиск mDNS-сервиса _googlecast._tcp.local. Перезагрузил устройство, после чего приложение загрузилось автоматически. Оно быстро обнаружило себя и я мог отправлять HTTP-запросы к localhost!

CDP использует WebSockets, доступные через стандартный JS API. Правило ограничения домена не применяется к WebSockets, поэтому мы можем запросто инициировать WebSocket как localhost из локального домашнего приложения (хостящегося на каком-нибудь публичном веб-сайте), если у нас будет нужный URL. Так как доступ по CDP может привести к тривиальному RCE в десктопной версии Chrome, адрес WebSocket при включенной отладке каждый раз генерируется случайным образом, чтобы предотвратить подключение случайных веб-сайтов. Адрес можно получить запросом GET к http://[CDP host]:9222/json. Обычно он защищён правилом ограничения домена, поэтому мы не можем просто применить XHR-запрос, но поскольку у нас есть полный доступ к localhost через Local Home SDK, можно использовать его для создания запроса. Получив адрес, мы можем использовать JS-конструктор WebSocket() для подключения.

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

Proof of Concept


Описанные ниже PoC опубликованы здесь: https://github.com/DownrightNifty/gh_hack_PoC.

Так как проблемы с безопасностью были устранены, вероятно, всё это больше не работает, но я решил, что это стоит задокументировать и сохранить.

PoC 1: шпионим за жертвой


Я создал PoC, работающее в моём Android-телефоне (через Python в Termux), чтобы продемонстрировать, насколько быстрым и простым может быть процесс привязки аккаунта. Описанная ниже атака может быть выполнена в течение всего нескольких минут.

Для PoC я заново реализовал привязку к устройству и routines API на Python, а также создал следующие утилиты: google_login.py, link_device.py, reset_volume.py, call_device.py.

  1. Скачиваем protoc и добавляем его к PATH
  2. Устанавливаем необходимое: pip3 install requests==2.23.0 gpsoauth httpx[http2]
  3. Создаём аккаунт Google «нападающего»
  4. Логинимся при помощи python3 google_login.py
  5. Попадаем в область беспроводного действия Google Home
  6. Деаутентифицируем Google Home
    • Для инъецирования сырых пакетов (требуемого для атак deauth) нужен телефон с рутом, и оно не будет работать на некоторых чипах Wi-Fi. В конечном итоге я использовал NodeMCU — крошечную макетную плату Wi-Fi, которая стоит на Amazon меньше $5, прошив её прошивкой deauther разработчика spacehuhn. Можно использовать его веб-UI для сканирования близких устройств и их деаутентификации. Устройство быстро нашло мой Google Home (производитель на основании префикса MAC-адреса имел в списке имя «Google»), и мне удалось деаутентифицировать его.

  7. Подключаемся к сети настройки Google Home (с именем [device name].o)
  8. Выполняем python3 link_device.py --setup_mode 192.168.255.249, чтобы привязать аккаунт к устройству
    • Чтобы сделать атаку максимально незаметной, кроме привязки аккаунта я включил в устройстве «ночной режим», снижающий максимальную громкость и яркость светодиодов. Так как на громкость музыки это не влияет, а снижение громкости почти полностью компенсируется, когда громкость выше 50%, это мелкое изменение жертва, скорее всего, не заметит. Однако при этом в случае 0% громкости голос Google Assistant становится полностью заглушается (поэтому при отключении ночного режима его всё равно едва будет слышно на 0% громкости).

  9. Останавливаем атаку деаутентификации и ждём, пока устройство снова подключится к Интернету
    • Можно запустить python3 reset_volume.py 4, чтобы снизить громкость до 40% (потому что включение ночного режима снижает её до 0%).

  10. Теперь, когда аккаунт привязан, можно незаметно и в любое время заставить устройство звонить на телефонный номер по Интернету, что позволяет прослушивать сигнал микрофона.
    • Чтобы совершить вызов, запускаем python3 call_device.py [номер телефона].
    • Команды «set the volume to 0» и «call [номер]» выполняются в устройств

      © Habrahabr.ru