Многие производители кондиционеров сегодня выпускают устройства, которые можно подключать к Wi-Fi и управлять ими через приложение. Включение кондиционера и охлаждение воздуха дома, пока вы едете с работы — это настоящая мечта. Однако пользоваться приложениями производителей часто не так удобно, как более совершенным ПО для автоматизации дома, которое может интегрироваться с голосовыми помощниками Google и Amazon. Поэтому, когда один из нас переехал в квартиру с умным контроллером кондиционера, одной из приоритетных целей стал взлом этого устройства для работы с Home Assistant.
Начиная исследование, мы и не догадывались, насколько вопиющие уязвимости безопасности обнаружим; среди прочего, они позволяют полностью захватить кондиционер через интернет.
Wi-Fi-контроллер кондиционера
Исследуемый нами контроллер изготовлен израильским производителем кондиционеров компанией Electra. Он применяется для управления центральными кондиционерами воздуха и в последние годы устанавливается во многих новых квартирах. У Electra есть специализированное приложение, при помощи которого можно подключить контроллер к сети Wi-Fi, а затем управлять им через интернет.
Разумеется, первым делом мы начали искать готовые интеграции и библиотеки, которые позволили бы взаимодействовать с контроллером. Одна такая библиотека существовала, но она просто эмулировала приложение для общения с API-сервером Electra, который связывается с контроллером через интернет.
Хотя это был рабочий вариант, мы хотели узнать, возможна ли более тесная интеграция. Можем ли мы получить доступ к контроллеру по локальной сети, чтобы и уменьшить задержки, и сохранить контроль при отключенном интернете?
Анализ локальной сети
Мы начали со сканирования контроллера при помощи nmap (которое, что неудивительно, не дало никаких результатов). Было ясно, что контроллер работает в качестве клиента, подключенного к удалённому серверу (в противном случае требовалась бы переадресация порта, а настроить ее вряд ли под силу среднестатистическому пользователю). Поэтому мы использовали tcpdump и роутер с OpenWRT для мониторинга сетевого трафика сразу после включения контроллера.
Логи показали, что сначала контоллер ресолвит домен alk2da.messaging.internetofthings.ibmcloud.com
(в результате получается IP-адрес 62.90.149.38
). Затем он пару минут пингует этот IP-адрес, отправляя запрос примерно раз в секунду. Затем контроллер подключается к этому IP-адресу через порт 1883
, а несколько секунд позже отсоединяется и снова подсоединяется через порт 8883
. Это последнее подключение работает неограниченно долго.
Эти порты (1883
и 8883
) обычно используются MQTT (для обычных и защищённых соединений соответсвенно). И при попытке подключиться к серверу через эти порты мы и в самом деле увидели, что это MQTT-сервер.
MQTT-сервер
Поначалу мы были оптимистичны — мы настроили локальный DNS-сервер, чтобы он указывал на произвольный MQTT-сервер, в расчете на то, что контроллер подключится к нему, а не к серверу Electra (помните, что в тот момент мы и не думали об уязвимостях, нашей целью было локальное управление). Затем мы изучим сообщения, которые он отправляет и получает по MQTT, и будем управлять им локально — все просто, не так ли?
Видимо, кто-то принял меры предосторожности при разработке контроллера (однако как мы увидим ниже, они по большей мере оказались бесплодными). Контроллер отказывался подключаться к нашему MQTT-серверу, и, судя по журналу подключений на стороне сервера, проблема заключалась в сертификате (или в его отсутствии). Разумеется, мы попробовали сгенерировать самоподписанный сертификат для нужного домена (alk2da.messaging.internetofthings.ibmcloud.com
), но это не помогло — для проверки подключения к нужному серверу контроллер использовал certificate pinning. Из-за этого нам пришлось отказаться от идеи замены MQTT-сервера на локальный инстанс (по крайней мере, без патчинга прошивки, позволяющего пропустить проверку сертификата). Поэтому мы попробовали подключиться к MQTT-серверу Electra, надеясь, что сможем управлять кондиционером с него. Не локально, но, по крайней мере, максимально напрямую, не задействуя используемый приложением API.
Разумеется, для подключения к MQTT-серверу необходимы имя пользователя и пароль. А подключение через порт 8883 зашифровано TLS, поэтому сниффинг не поможет. Можно было бы использовать незащищённое соединение, но в то время мы пропустили эти несколько пакетов и сосредоточились на зашифрованном соединении. К счастью, было не сложно получить имя и пароль при помощи приложения.
Извлекаем пароль MQTT при помощи приложения
Процедура сопряжения контроллера через приложение достаточно проста:
- Если контроллер не находится в состоянии сопряжения (например, он уже был сопряжён ранее), то нужно сбросить его, удерживая кнопку сбоку в течение 15 секунд.
- Добавление нового кондиционера в приложении: нужно следовать инструкциям и ввести требуемую информацию (серийный номер, реальный SSID для подключения и так далее).
- Выбор открытой контроллером точки Wi-Fi (название начинается с
ELRSSID
). - Затем приложение подключается к кондиционеру и завершает процесс сопряжения.
Но что именно происходит на последнем этапе? Чтобы выяснить это, мы открыли собственную точку доступа с именем настоящей точки контроллера (то есть, начинающимся с ELRSSID
). Приложение ожидает, что точка будет иметь совпадающий пароль (тот же, что и SSID, но ELRSSID
заменено на ELRPASS
). Этот пароль можно узнать, если выбрать устранение неполадок подключения в приложении — оно попросит подключиться к SSID вручную и выдаст соответствующий пароль.
Настроив SSID, мы запустили сниффер и попытались выполнить сопряжение с приложением. Вскоре мы заметили, что приложение пытается коммуницировать с хостом 192.168.1.1
на порту 80
. Поэтому мы запустили для этого порта простой TCP-прослушиватель и выяснили отправляемый приложением HTTP-запрос (из соображений безопасности мы заменили реальные значения имени SSID, пароля и токена):
POST / HTTP/1.1
user-agent: Dart/2.10 (dart:io)
content-type: application/x-www-form-urlencoded
accept-encoding: gzip
content-length: 87
host: 192.168.1.1
__SL_P_UST=3&__SL_P_UPW=RealSSIDPassword&__SL_P_UTO=SecretToken&__SL_P_UAP=RealSSIDName
Легко заметить, что запрос содержит три важных элемента:
а) Имя SSID, к которому должен подключаться контроллер (в параметре __SL_P_UAP
).
б) Пароль указанного выше SSID (в параметре __SL_P_UPW
).
в) Токен, используемый для MQTT-сервера. Внимательно исследовав процесс сопряжения, мы выяснили, что приложение не выполняет никакие другие запросы, то есть этот токен — единственный элемент информации, передаваемый приложением контроллеру.
Подключение к MQTT-серверу
Получив токен, мы также должны были выяснить имя пользователя и ID клиента, используемые для подключения к MQTT-серверу. Их удалось выяснить после изучения домена alk2da.messaging.internetofthings.ibmcloud.com
. Видно, что этот сервер является частью IBM Watson IoT Platform, а в её документации написано, что имя пользователя должно выглядеть как use-token-auth
, а ID должен иметь формат d:orgId:deviceType:deviceId
(примечание: эта платформа устарела, и возможно, что приведённые выше ссылки в будущем работать не будут).
Воспользовавшись этой информацией и извлечённым из приложения токеном, мы запустили MQTT Explorer для подключения к серверу и увидели огромный объём данных:
Мы увидели сотни кондиционеров. Мы видели IP-адрес каждого из них (выдающий приблизительное местоположение), подробное состояние, в том числе установленную температуру, режим работы, текущую температуру и так далее. Это огромная проблема безопасности, но худшее ещё впереди.
Извлечение прошивки
На этом этапе последствия для безопасности нашего невинного исследования стали достаточно очевидными, поэтому настало время изучить прошивку устройства. Мы всё равно хотели научиться управлять устройством, поэтому нам обязательно нужно было понять, какие типы команд оно слушает.
Вскрыв пластмассовый корпус контроллера, мы увидели, что на плате установлена пара основных чипов: ST Microelectronics STM32F030x и Texas Instruments CC3100.
Группы контактов на обратной стороне очень походили на UART/JTAG. Проверив соединения этих контактов с разными выводами на чипах (схемы выводов можно найти в даташитах соответствующих чипов, например, здесь), мы выяснили предназначение каждого контакта. Нас интересовали контакты JTAG/SWD, которые мы сразу подключили к проверенному TIAO Tumpa и воспользовались OpenOCD с нужной конфигурацией (STM32F0) для сброса прошивки из чипа STM32.
Загрузка прошивки в Ghidra — это простой процесс. Из даташита мы знали, что это был чип ARMv7, и верно предположили, что прошивка имеет формат little-endian. Если вам любопытно, то существует множество ресурсов об анализе файлов прошивок ARM, в них объясняется присутствие векторной таблицы в начале прошивки, настройка базового адреса памяти (указанного в даташите/руководству по программированию) и так далее.
Управление кондиционером
Первым делом после автоматического анализа прошивки мы должны были найти механизм управления устройством через MQTT. Здесь стоит вкратце объяснить, как работает MQTT. Это простой протокол очереди сообщений, в котором подключенные клиенты могут подписываться на топики и публиковать сообщения по топикам. Сообщение, опубликованное по топику, будет доставлено клиентам, подписанным на этот топик.
При первом подключении к MQTT-серверу Electra мы подписались на подстановочный символ топиков #
, благодаря чему начали получать сообщения, опубликованные по всем топикам на этом сервере. Большинство сообщений публиковалось кондиционерами, но любопытен был топик iot-2/evt/CMD/fmt/json
. Было похоже, что этот топик отражает последнюю команду, отправленную приложением кондиционеру, но не ограничивается нашим экземпляром приложения или нашим кондиционером. Сообщение по этому топику постоянно переписывается, потому что кондиционеры активно используются по всей стране. Когда мы меняли температуру на нашем кондиционере, то несколько секунд видели сообщение, соответствующее нашей команде, которое затем заменялось другим сообщением (вероятно, предназначавшимся другому кондиционеру).
Упомянутый топик CMD оказался очень полезным, он позволил понять формат сообщения, используемого для управления кондиционером (это был очень удобочитаемый JSON). Однако было очевидно, что для управления кондиционерами использовался не этот топик — не было идентификаторов, позволяющих пометить кондиционер, управляемый этим сообщением. Мы предположили, что сообщение с командой публиковалось в топик, который фильтровался при использовании подстановочного символа топиков (то есть, чтобы получать его сообщения, нужно подписаться на него явным образом), а сообщения по этому топику ошибочно отображались и в общем топике.
Итак, хотя мы знали содержимое командного сообщения, которое хотим отправить, нам всё равно нужно было найти топик, по которому следует публиковать сообщение. И здесь нам помогло изучение кода прошивки. Мы поискали в коде строки, содержащие части этого топика, и вскоре нашли код, выполняющий подписку на нужный топик:
Узнав настоящий топик CMD (содержащий ID клиента устройства, связанный с его MAC-адресом), мы наконец могли попробовать управлять кондиционером без приложения. И у нас получилось! Отправка командного JSON по нужному топику действительно заставляла кондиционер дома менять свою работу в зависимости от сообщения JSON. Отлично! Приложение больше не требовалось, и кондиционер можно было интегрировать в систему домашней автоматизации при помощи обычных сообщений MQTT. Дело сделано. Оставалось проверить одну вещь…
Управление другими кондиционерами
Что произойдёт, если мы отправим командное сообщение по другому топику, использовав ID клиента другого кондиционера? Мы нашли коллегу, у которого был похожий кондиционер, и спросили у него MAC-адрес, который указан в контроллере. Благодаря MAC-адресу мы смогли узнать нужный ID клиента. Важно заметить, что мы по-прежнему использовали ID пользователя, ID клиента и токен нашего кондиционера. Мы отправили командное сообщение по соответствующему топику, и наш коллега сообщил нам, что кондиционер отреагировал!
Потрясающе?
Ужасающе.
Любой может установить приложение Electra Smart, сгенерировать учётные данные для подключения к MQTT-серверу Electra, подключиться к нему и начать управлять любыми кондиционерами. Но могло быть и хуже…
Подключаемся к MQTT-серверу… снова
На каком-то этапе наших исследований мы совершили ошибку и скопировали не тот фрагмент текста для вставки в качестве пароля MQTT-сервера. Мы осознали ошибку, но кнопка логина уже была нажата. Как ни удивительно, соединение заработало — вход был выполнен успешно, и мы увидели привычный поток сообщений от кондиционеров. Это нас встревожило. При следующей попытке залогиниться мы вообще не указали пароля… но вход был успешно выполнен. Отправка команд после логина тоже выполнялась.
То есть, на самом деле ситуация была такой: есть MQTT-сервер, открытый любому через интернет, который позволяет выполнить анонимный вход с доступом к управлению любым подключенным к сети кондиционером Electra Smart. Обнаружив такие блестящие меры защиты, мы решили пристальнее изучить прошивку устройства и попытаться найти в ней другие подобные бриллианты.
Версии оборудования
В начале нашего исследования мы заказали ещё пару контроллеров, предполагая, что можем повредить первый при попытке извлечения прошивки (эти опасения оказались необоснованными). Когда мы получили устройства, внешне они выглядели похожими на первое, и мы вскрыли их, чтобы посмотреть на плату. Оказалось, что одна из плат сильно отличается от двух других. Первый контроллер, показанный на фотографиях выше, был маркирован как Revision 005. Второй контроллер был маркирован как Revision 004, и выглядел очень похоже на Revision 005. Однако третий контроллер был маркирован как Revision 008, и чипы на плате сильно отличались. STM32F0 на нем был заменён на китайский GigaDevice GD32F303RCT6. А CC3100, использовавшийся в старых версиях просто как модуль WiFi и управляемый чипом STM32, был заменён на ещё один китайский чип — MXChip EMW110. Наверно, переход с западных чипов на китайские альтернативы был связан с экономией. Но это также означало, что ПО может существенно измениться — в конце концов, эти чипы не на 100% взаимозаменяемы со старыми. Поэтому мы снова занялись извлечением прошивки.
Тем временем, извлеченная из чипа GD32F3 (заменившего STM32F0) прошивка оказалась намного меньше и с гораздо меньшей функциональностью. Например, мы не смогли найти ничего, связанного с MQTT-сервером, поэтому внимательнее присмотрелись к чипу EMW110. Мы обнаружили, что его контакты UART удобно расположены на обратной стороне платы, подключили их к терминалу, включили плату и увидели кучу информации.
Из Китая с любовью
Первое, на что мы обратили внимание — HTTP-запрос PUT
на китайский сервер, прямо перед началом процесса запуска (JSON полезной нагрузки отформатирован для краткости):
PUT /api/device/update_version HTTP/1.1
Host: cn-api.topband-cloud.com
Content-Type: application/json
Connection: Close
Content-Length: 145
Token: <данные удалены>
{
"version": {
"wifi_software": "WIFI2230",
"wifi_hardware": "XXEMW110",
"mcu_software": "MCU00004",
"mcu_hardware": "HARD0000",
"mcu_protocol": "00000004"
}
}
Для доступа к этому API требуется токен — его генерация видна ранее в логе:
[3610][Debug: http.c: 80] Token source: product_code=04CNJJ0002product_secret=<данные удалены>mac_address=<данные удалены>pass_code=ECSGVSQOGC
[3612][Debug: http.c: 95] Token: <данные удалены>
Мы просто взяли токен, используемый этим устройством — он оказался достаточно статичным и не менялся между запусками. Позже мы нашли его генерацию в коде прошивки, и она действительно зависит только от приведённых выше значений (product_code
, product_secret
, mac_address
и pass_code
), которые не меняются на протяжении всего срока жизни устройства.
Мы беспокоились, что будет какая-нибудь проверка клиентского сертификата (мы уже видели серверы обновлений, работавшие таким образом для предотвращения скачивания неаутентифицированной прошивки), но на этом сервере таких мер не было… Похоже, указанная выше конечная точка API уведомляет китайский сервер, отвечающий за обновления прошивки, о текущей установленной версии. Кажется, она просто отражает отправленные серверу данные о версии.
Итак, тут присутствует управление версиями прошивок, но мы так и не увидели самого процесса обновления в действии.
Исследование топиков MQTT, на которые выполнена подписка
Затем мы проверили, что устройство, как и прошлые версии, общается через MQTT. Так и было. Однако оно как будто подключалось к другому серверу — iot.ecpiot.co.il
. На практике DNS ресолвился в тот же IP-адрес, что и у предыдущего сервера. Из-за несоответствия доменных имён ошибки TLS игнорировались (позже это будет важно).
При дальнейшем изучении журнала запусков мы увидели, что устройство подписано на множество топиков:
[...]
[8764][Debug: cloud.c: 966] Subscribing topic2: iot-2/cmd/OPER_CMD/fmt/json
[...]
[9786][Debug: cloud.c: 990] Subscribing topic3: <данные удалены: MAC-адрес устройства>/update/#
[...]
[10814][Debug: cloud.c:1014] Subscribing topic4: iot-2/cmd/CMD/fmt/json
[...]
Последний топик, iot-2/cmd/CMD/fmt/json
был нам знаком (использовался для управления устройством). Первый топик, iot-2/cmd/OPER_CMD/fmt/json
, кажется, схож с последним (возможно, остался от старой версии). Но как насчёт второго топика? Его название выглядит очень любопытно… Обновление! Чтобы понять формат сообщения, нужно обратиться к самой прошивке.
Извлекаем прошивку… снова
Так как оборудование в новых версиях совершенно другое, процесс извлечения прошивки тоже нужно было адаптировать. В частности, судя по представленным выше логам запуска, очевидно, что логика коммуникаций теперь находится в чипе WiFi, в то время, как старых версиях чип WiFi использовался только в качестве периферийного устройства. Пришлось изучить его даташит, чтобы узнать функции его выводов и процедуру доступа к JTAG.
Припаяв провода к нужным точкам на дочерней плате, мы снова воспользовались Tumpa для подключения к плате по JTAG и извлечения прошивки.
Анализ прошивки EMW110
На этот раз загрузить прошивку в Ghidra было чуть сложнее. Нам понадобилось несколько попыток и более глубокое изучение даташита SoC этой платы (называющейся MOC108), чтобы понять что мы имеем дело с гораздо более старой архитектурой — ARMv5. После загрузки прошивки с подходящей архитектурой мы смогли приступить к изучению кода.
Поискав соответствующие строки (например, отладочные сообщения, встречающиеся при обработке команд, полученных через топики MQTT), мы быстро нашли функции, отвечающие за обработку данных по топикам MQTT, на которые есть подписка. Дальше нам нужно было просто следовать по коду, выявляя использованные функции JSON (зная, что в этом очень помогает ожидаемый формат пакетов CMD
) и воссоздавая ожидаемое сообщение JSON с описанием обновления:
{
"type": "ota",
"data": {
"wifi": {
"wifi_hardware": "XXEMW110",
"wifi_software": "WIFI2231"
}
}
}
Принудительное обновление контроллера
Получив сообщение обновления, мы были готовы отправить его в контроллер. И логи действительно показывают, что он запросил обновление! Запрос был выполнен к следующему URL, очень похожему на URL, используемый для уведомления сервера о текущей используемой прошивке.
GET /api/device/firmware?device_code=***данные удалены***&version=WIFI2230&type=wifi&beta=0 HTTP/1.1
Host: cn-api.topband-cloud.com
Connection: Close
Content-Length: 0
Token: ***данные удалены***
И ответ сообщил об имеющемся обновлении:
{
"product_code": "04CNJJ0002",
"version": "wifi2239",
"type": "wifi",
"file": "http://file.topband-cloud.com/topband/smartsys/media/attachments/021fb2e0c5aff742e2eb7a61f82052ab/wifi2239.bin",
"beta": false,
"md5": "021fb2e0c5aff742e2eb7a61f82052ab",
"create_date": "2022-08-25 08:48:13",
"update_date": "2022-08-25 08:48:13",
"slug": "9645001f-f3cc-459d-8989-c199085e584c",
"is_active": true,
"force_update": false
}
Важно обратить внимание на множество очевидных проблем этого процесса:
- Первоначальное подключение к серверу
cn-api.topband-cloud.com
выполняется по обычному незашифрованному HTTP. - URL скачивания прошивки тоже передаётся по обычному незашифрованному HTTP.
- Проверка подписи отсутствует, единственное, что проверяется — это правильность суммы MD5.
Всё вышеперечисленное означает, что когда контроллер подключен к скомпрометированной точке доступа, нападающий может передавать на контроллер произвольную прошивку.
Объединяем наши находки
Ниже описан вектор атаки, предоставляющий нападающему полный контроль над новыми контроллерами (revision 007/008), которые пока не были настроены.Такие контроллеры можно найти в новых квартирах, когда пользователь ещё не подключил их к приложению.
- Когда контроллер кондиционера не инициализирован (например, как это бывает во многих домах, не подключенных к сети Wi-Fi), нападающий может заставить его подключиться к вредоносной точке доступа.
- После подключения к вредоносной точке доступа, контроллер ресолвит домен iot.ecpiot.co.il и подключается к нему через MQTT без верификации TLS. Следовательно, контролирующий точку доступа нападающий может использовать контроллер для подключения к управляемому им вредоносному MQTT-серверу.
- После подключения контроллера к вредоносному MQTT-серверу нападающий может отправить команду обновления, что заставит контроллер запросить обновление с сервера обновлений.
- Связь с сервером обновлений выполняется через HTTP, поэтому нападающий может её спуфить, передав контроллеру вредоносную прошивку для установки. Контроллер скачает и установит переданную нападающим прошивку, которая может содержать любой вредоносный код.
Важно повторить, что для этой атаки уязвим любой только что установленный контроллер кондиционера Electra, который не был настроен пользователем (то есть, не подключался к сети). При этом атаку запросто можно провести с улицы. Гуляя по городу рядом с недавно построенными зданиями, мы обнаружили множество SSID, указывающих на такие неподключенные контроллеры. И хотя эту проблему можно решить обновлением, оно происходит только после подключения контроллера к сети, а в этот момент он уже может быть скомпрометирован. Поэтому единственный способ гарантировать отсутствие компрометации контроллера — отзыв устройств.
Раскрытие
Мы сообщили об уязвимостях в Israeli National Cyber Directorate (INCD) 30 октября 2022 года. Отправив множество запросов об изменении статуса, мы получили ответ, что информацию наконец передали Electra (через много недель после отчёта в INCD). Поначалу Electra не подтвердила уязвимости (на самом деле, их ошибочно отклонила компания Vayosoft — субподрядчик, разрабатывавший ПО для умных кондиционеров Electra).
В конце января 2023 года, почти три месяца спустя после нашего первого отчёта, нам удалось собрать совещание с участием INCD, начальником управления информационной безопасности (CISO) Electra и представителем Vayosoft. В процессе совещания мы рассказали обо всех уязвимостях и объяснили их последствия. CISO Electra был очень понимающим и любезным. Представитель же Vayosoft пытался преуменьшить серьёзность каждой нашей находки. В частности, он утверждал, что полученная нами плата revision 008 находилась в состоянии предпродакшена и не поставлялась покупателям, и что актуальное ПО на этих платах выполняет верификацию сертификатов по SSL и pinning. Оба утверждения были откровенной ложью.
Electra/Vayosoft быстро устранили самую критическую проблему — полное отсутствие аутентификации/авторизации на MQTT-сервере. За считанные дни проблема была решена, и подключение к MQTT-серверу больше не позволяло общаться с другими кондиционерами (и требовало правильного пароля). Кроме того, Electra отправила нам для теста ещё одну плату, исходя из утверждения Vayosoft о том, что новое ПО реализует SSL. Мы изучили плату (revision 007, почти неотличимую от revision 008, которая у нас была ранее) и увидели, что различий нет — соединение было незащищённым, как с MQTT-сервером, так и с китайским сервером обновлений. Мы сообщили Electra о своих выводах, обновили отчёт INCD, и вскоре после этого, 12 марта 2023 года, были выпущены CVE.
Автор: Игорь Santry