Недавно я купил умный дверной звонок на Temu, китайской торговой площадке, которая набирает популярность во всем мире в последние пару лет. Я хотел узнать, насколько безопасно дешёвое подключённое оборудование, продаваемое на этой платформе. Устройство поставляется под названием «Умный дверной звонок X3» и сопрягается через мобильное приложение под названием «X Smart Home». Камера, микрофон, двусторонняя аудиосвязь, внутренний приёмник в диапазоне ниже ГГц. Такое оборудование незаметно появилось на многих входных дверях.
За несколько выходных, проведённых с одним таким устройством, я смог:
-
бесшумно украсть любой из этих дверных звонков из учётной записи владельца;
-
имитировать устройство в реальном звонке, используя выбранное злоумышленником видео на телефоне владельца;
-
украсть пароль от домашней сети Wi-Fi через отладочный порт за отвёрткой.
$12 за вход. Компрометация всей сети за выход. Первый вариант требует бесплатного аккаунта на платформе и перенаправляет все реальные звонки с двери на мой телефон вместо телефона владельца. Второй вариант не требует ничего и создаёт новые звонки на телефон владельца с любым видео, которое я захочу. Настоящий дверной звонок в любом случае остаётся в сети и никогда не узнает о вторжении. По сути, вы платите $12 за то, чтобы любой человек в интернете мог позвонить в вашу дверь.
Результаты находятся на уровне платформы, а не в каком-либо конкретном блоке в списке Temu. Дверной звонок взаимодействует с бэкендом, работающим под брендом Naxclow. Он принадлежит компании Guangzhou Qiangui IoT Technology Co., Ltd., расположенной в Гуанчжоу. Одно и то же оборудование продаётся под разными торговыми марками, и тот же поставщик запускает небольшое семейство потребительских приложений под брендом Naxclow, каждое на своём поддомене. V720 — одно из них (уже публично проведён реверс-инжиниринг, см. intx82/a9-v720). Другое приложение, которое я заметил, называется «ix cam». Я не тестировал их отдельно. Их веб-интерфейсы используют ту же структуру Vue, что и X Smart Home, и эта публичная работа уже охватывает пересечение протоколов передачи данных между V720 и дверным звонком. Общая кодовая база SPA плюс пересечение протоколов предполагают, что один и тот же код бэкенда работает под каждым брендированным хостом. Это история о платформе, а не об устройстве.
Этот блог выступает элементом ответственного раскрытия информации. Найти контактные данные Naxclow было непросто. На их сайте вообще нет страницы контактов. В конце концов, я нашёл адрес электронной почты на одной из их страниц и методом перебора подобрал распространённые псевдонимы на том же домене, чтобы расширить круг поиска. Большинство писем не дошли. 29 апреля 2026 года я отправил отчёт по адресам, с которых были доставлены письма, а также через форму обратной связи в приложении X Smart Home. На момент написания этого текста ответа не получил. Публикую отчёт через неделю после уведомления, удалив конфиденциальную информацию.
Список проблем длинный, так что возьмите любимую закуску или напиток и устраивайтесь поудобнее.
Область применения и этика
Я тестировал всё нижеперечисленное на собственных устройствах с двумя своими учётными записями X Smart Home. Трафик заходил на производственный бэкенд Naxclow, но только с использованием моих собственных учётных данных. Я никогда не трогал чужие учётные записи, устройства или трафик.
Реальные пути к конечным точкам, точные имена параметров, буквально захардкоженная соль, полная реализация подписи и любой работающий код PoC не включены в этот пост. Суть в методологии и режимах отказов, а не в рецепте.
Устройство
Три элемента:
-
дверной звонок (камера, кнопка, микрофон, динамик, Wi-Fi, передатчик в диапазоне ниже ГГц);
-
небольшой комнатный приёмник, который принимает сигнал в диапазоне ниже ГГц и издаёт звуковой сигнал при нажатии кнопки;
-
мобильное приложение-компаньон для просмотра в реальном времени, двусторонней аудиосвязи и истории событий.
Этикетка интереснее коробки. OEM-производитель оборудования — Shenzhen Ruilang Technology Co., Ltd, а не Naxclow и не тот бренд, который продавец указал в описании. FCC ID 2A5LK-X3PRO, одобрен в марте 2024 года для Wi-Fi 2,4 ГГц плюс 433,91 МГц в диапазоне ниже ГГц. Код грантополучателя Ruilang 2A5LK содержит как минимум семь заявок на Wi-Fi-камеры. Одна из них — модель A9, которая при реверс-инжиниринге intx82 была определена как мини-камера V720. Тот же завод, парк Wi-Fi камер под разными кодами моделей, все они изначально настроены на бэкенд Naxclow. В качестве импортера в ЕС и Великобритании указана компания Whaleco, корпоративное подразделение Temu в этих юрисдикциях.
Открыв плату, обнаруживаешь небольшое количество микросхем. Практически все функции выполняет один микроконтроллер: Beken BK7252N, дешёвый комбинированный модуль Wi-Fi и аудио. Разъёмы JTAG и UART расположены на лицевой стороне платы. UART имеет необычное расположение для отладочного разъёма: контакты TX и RX объединены в небольшую пару, GND отключён на краю, контакта VCC нет. Я проверил TX во время загрузки, увидел, как напряжение изменяется, подобно помехам в последовательном порте, и с тех пор всё пошло наперекосяк.
UART работает на частоте 115200 МГц в режиме 8N1. Субгигагерцовая связь осуществляется с помощью 24-битного OOK-сигнала, закодированного в Princeton, на частоте 433,91 МГц, который можно воспроизводить любым устройством, излучающим OOK на этой частоте. Wi-Fi работает на RT-Thread 3.1.0 плюс Beken SDK. Ничего экзотического в этом нет. Вся эта информация свободно просачивается наружу.
Нажатие кнопки приводит к одновременному возникновению трёх событий:
-
импульс в диапазоне ниже ГГц поступает на внутренний приёмник, который звонит внутри дома;
-
дверной звонок отправляет HTTP-запрос на бэкенд Naxclow, который посылает уведомление на телефон владельца;
-
если владелец нажимает «ответить», устройство и приложение устанавливают одноранговый вызов для двусторонней аудио- и видеосвязи.
Субгигагерцовый участок можно воспроизвести с тротуара с помощью Flipper Zero. Я один раз записал нажатие кнопки и воспроизвёл его с устройства, и внутренний приёмник зазвонил так, как будто я нажал настоящую кнопку. Я не стал дальше разбираться: это уже проторенная дорожка, и это не то, ради чего я пришёл. Я сосредоточился на стороне Wi-Fi: как нажатие кнопки достигает телефона и как начинается вызов.
Фаза 1: Чтение проводов
MITM в лаборатории, длительная сессия Wireshark, одиночный захват от нажатия кнопки до ответа на вызов.
Первым делом приходит оповещение. Звонок в дверь отправляется на сервер по протоколу HTTP, сервер отправляет уведомление на телефон владельца, и только после этого начинается сам звонок.
Запрос представляет собой многокомпонентную загрузку. Выделяются три поля: идентификатор устройства, случайная строка для каждого запроса и токен, похожий на подпись (шестнадцатеричное значение фиксированной длины, изменяющееся при изменении параметров). Также прилагается JPG-файл, который владелец видит в своем уведомлении.
Самое интересное начинается с ответа. В нём содержится ключ конфигурации, несущий учётные данные хоста и каждого устройства, необходимые для последующего P2P-звонка. Учётные данные статичны. Они привязаны к идентификатору устройства и сохраняются как при сбросе до заводских настроек, так и при перепривязке к другой учётной записи. Каждый раз возвращается одно и то же значение.
Анализируя перехваты, можно заметить, что случайные значения и значения токена меняются при каждом запросе. Устройство никогда не делает предварительных запросов для получения ни одного из них, поэтому всё, что их генерирует, выполняется локально на самом устройстве. Токен имеет форму криптографической подписи: шестнадцатеричная строка фиксированной длины, которая непредсказуемо меняется при каждом запросе. Поле случайных чисел изменяется вместе с ней, что предполагает добавление дополнительной энтропии к тому, что подписывает токен.
Я попытался воспроизвести запрос, и это сработало: бэкенд сгенерировал новый идентификатор события и отправил уведомление. Изменение любого примитивного параметра (например, поля случайных чисел) нарушило запрос, подтверждая, что токен их охватывает. Однако, если заменить тело JPG-файла, оповещение пройдёт успешно, и на телефоне владельца отобразится моё новое изображение. Изображение не является частью подписи.
После возврата оповещения устройство устанавливает TCP-соединение с хостом и портом из блока конфигурации. С этого момента протокол становится бинарным и выглядит как самодельный STUN.
Я потратил некоторое время на обратное проектирование структуры. Каждое сообщение имеет фиксированный заголовок размером 20 байт, за которым следует тело JSON. Заголовок содержит поле длины, дискриминатор типа (данные или пульс) и идентификатор сессии. Тело содержит код команды и параметры для этой команды. Как только я определил структуру, остальная часть обмена данными стала понятна сама собой.
Первое сообщение — это аутентификация. Устройство отправляет свой идентификатор, пароль ретранслятора из конфигурации и домен, полученный в том же ответе. Домен в запросе указывает на то, что один и тот же бэкенд обслуживает несколько семейств устройств за селектором домена, а не отдельный экземпляр для каждого устройства.
После подтверждения регистрации (ACK) соединение переходит в режим ожидания. Оповещение уже отправлено; устройство ожидает ответа владельца на свой телефон.
Мобильное приложение выполняет тот же процесс аутентификации на своём собственном соединении. Оно регистрируется у ретранслятора, используя свой идентификатор учётной записи и постоянный токен, а затем отправляет запрос на вызов, указывая целевое устройство. Именно это активирует соединение устройства. Ретранслятор пересылает на устройство сообщение в том же формате, содержащее все необходимое для связи с вызывающим телефоном: идентификатор учётной записи целевого клиента (мобильного приложения), соответствующий токен на стороне учетной записи, публичный и частный IP-адреса клиента, а также порты, которые клиент проверил с помощью NAT. Классическая процедура STUN-соединения, и задача ретранслятора — объединить две аутентифицированные регистрации в одноранговый вызов.
После обмена несколькими параметрами конфигурации обе стороны получают всё необходимое для прямого соединения друг с другом через UDP с помощью NAT-пробития, и вызов передаётся по этому каналу в режиме одноранговой связи.
Первое сообщение на P2P-канале — это небольшой JSON-конверт, содержащий cliToken и devToken вместе: токен устройства и токен учётной записи в одном пакете. Узел подтверждает вызов со статусом 200. С этого момента канал в основном состоит из необработанных байтов: JPEG-кадры с использованием алгоритма JFIF, а также аудиосэмплы в том же потоке. На этом участке сети также ничего не зашифровано.
Сама по себе настройка сессии представляет собой широковещательную передачу учётных данных. Любой, кто наблюдает за этим пакетом на любом из концов канала, получает долгосрочные токены как для устройства, так и для учётной записи. Поскольку медиаданные также не зашифрованы, любой, кто находится на пути следования, также получает видео и аудио в реальном времени.
Это была пассивная разведка: чтение трафика, сопоставление протоколов.
Чтобы сделать что-то большее, мне нужно было подделывать запросы, а функция подписи находится на самом устройстве. А это означало прошивку.
Фаза 2: Что говорит UART без запроса
Корпус уже открыт, и пара UART уже идентифицирована. Для удобства доступа я припаял тонкие провода к контактам TX и RX. Пробники PCBite были бы более аккуратным вариантом, но у меня не было их под рукой.
Провода были подключены к Flipper Zero в режиме моста UART, эмулятору терминала на хосте. Дверной звонок переходит в глубокий спящий режим между событиями: нажатие кнопки пробуждает его, запускает полный цикл приложения и возвращает в спящий режим. Все нижеприведённое относится к одному такому циклу, начавшемуся с момента нажатия кнопки звонка.
Первое, что приходит на ум, — это загрузочный баннер. Версия прошивки, неотредактированный дамп регистров, блок авторских прав RT-Thread и ранняя ошибка инициализации OTA:
BK7252N_1.0.14REG:cpsr spsr r13 r14SVC:0x000000D3 0xA4AAB8CC 0x22CA0058IRQ:0x000000D2 0x00000010 0x227AA88D 0x48C9A634...[I/FAL] Fal(V0.4.0)success[E/OTA] (rt_ota_init:105) Initialize failed! The download partition(download) not found.[E/OTA] (rt_ota_init:115) RT-Thread OTA package(V0.2.8-beken-1133282d-20220604) initialize failed(-2).go os_addr(0x10000).......... \ | /- RT - Thread Operating System / | \ 3.1.0 build Jun 30 2025 2006 - 2018 Copyright by rt-thread team
Три обнаружения ещё до того, как ОС завершила загрузку. Производственная прошивка выводит дамп регистров в режиме отладки при каждом запуске. Модуль OTA не инициализируется из-за отсутствия раздела загрузки, и у устройства нет пути обновления по беспроводной сети. Сборка — RT-Thread 3.1.0, датированная июнем 2025 года, на Beken SDK 3.0.76.
Дождитесь установления соединения с Wi-Fi, и устройство выведет SSID, PSK, а также парные и групповые ключи, полученные во время четырёхстороннего рукопожатия:
_wifi_easyjoin: ssid:<my SSID> bssid:00:00:00:00:00:00key = <my PSK>...WPA: TK <32 hex chars>...WPA: GTK <32 hex chars>
Любой, у кого есть доступ к UART на этом устройстве, получит имя домашней сети, пароль и активные ключи сеанса. Доступ к UART на дверном звонке тоже не представляет большой сложности: устройство по своей конструкции висит на фасаде дома, поэтому физический доступ сводится к отвёртке и нескольким минутам тишины. Это компромисс для всей сети из-за одного дверного звонка.
Как только устройство получит IP-адрес, оно выполнит свой первый HTTP-запрос. Это тот же запрос оповещения, что и на этапе 1, и прошивка выведет ответ непосредственно в коде. Значения из ключа конфигурации также отображаются здесь, причём две метки смещены:
[SOC_connectSockerDevice -237] Debug :CONNECT IP=<backend ip> PORT = 80...[cjson_api_for_device_config_pic_stun:1054] server port <port> server pwd <stun ip> server host <static device password> domain <redacted>.naxclow.com
Строка «server pwd» на самом деле является IP-адресом хоста ретранслятора. Строка «server host» — это статический пароль ретранслятора устройства. Значения верны; метки — нет. В любом случае все они передаются по сети.
Затем открывается протокол STUN. Устройство зеркалирует полный обмен данными TCP с ретранслятором в UART в формате JSON в обоих направлениях:
[start_stun_talk:78] CONNECT IP=<stun ip> PORT = <port>...[rtc_serviceRegisDevice -267] Debug :reg: {"code": 100, "uid" : "1e2023XXXXXX", "token": "<static password>", "domain": "<redacted>.naxclow.com"}[rtc_serviceRegisDevice -283] Debug :reg: {"code":101,"status":200}[rtc_serviceRegisDevice -290] The server success to register the device
Первый кадр — тот же самый, что я описывал на этапе 1: устройство аутентифицируется на ретрансляторе с помощью своего UID, статического пароля ретранслятора и селектора домена. Прошивка выводит его в UART без изменений при каждом пробуждении, без каких-либо изменений.
Сразу после подтверждения регистрации начинается этап вызова. На той же консоли отображается дескриптор узла, возвращаемый сервером, плюс результат «пробивания дыры»:
[rtc_rtthService:1018] recv = ({"code":11,"cliTarget":"<account id>","cliToken":"<account token>","cliIp":"<caller public ip>","cliPort":1948,...[obj_serverInforExchange -387] Debug : ({"code":12,"status":200,"devIp":"<our public ip>","devPort":1954,"devNatIp":"<our private ip>","devNatPort":10006,...})[obj_prePeneTest -419] cliip(<caller public ip>) cliport(1948) natip(<caller private ip>) natport(58849)[obj_prePeneTest -419] pierce through success
Идентификатор учётной записи, токен на стороне учётной записи, публичный IP-адрес, частный IP-адрес, порт, отображаемый NAT, — всё без изменений. Дверной звонок общается только со своим привязанным владельцем, поэтому приведённые выше значения принадлежат владельцу. Дверной звонок, подключённый через UART, предоставляет доступ к привязанной учётной записи вместе с собственным идентификатором устройства.
Конец вызова отображается на том же канале:
[rtc_rtthService:1018] recv = ({"code":53,"target":"<account id>","status":0})
Всё вышеизложенное получено путём пассивного наблюдения. В командной строке ничего не вводилось. Устройство передает версию прошивки, состояние OTA, учётные данные домашней сети, весь протокол STUN и все учётные данные, необходимые для работы этого протокола, любому пользователю с последовательным кабелем, подключённым к разъёму, с которым оно поставлялось.
Фаза 3: Запрос прошивки в оболочку
Я нажал Enter, и устройство ответило msh />. Это оболочка RT-Thread, полностью интерактивная. Команда help вывела список достаточно длинный, чтобы его можно было читать как меню:
msh />helpRT-Thread shell commands:...device_code - device_codewrite_device_code - write_device_codedont_sleep - dont_sleeppm_level - pm_level 1...printenv - Print all envrionment variables.setenv - Set an envrionment variable.saveenv - Save all envrionment variables to flash....ping - ping network hostls - List information about the FILEs.cat - Concatenate FILE(s)fal - FAL (Flash Abstraction Layer) operate.wifi - wifi command...
Некоторые из них выделяются как очевидные цели. device_code читается как геттер для идентификатора устройства, и вывод довольно обширный:
msh />device_code[get_my_mac_devicecode:40] 1e2023XXXXXX 1my device name 1e2023XXXXXX[get_my_mac_devicecode:40] 1e2023YYYYYY 1my device mac 1e2023YYYYYYsercret <hardcoded salt> batch 1e2023[Flash]EasyFlash V3.0.4 already initialize.doorbell m7 X9 dymic chip_five all local start wakeup:17 b 37 g 0 x 30326531 cam 808465202[Flash]EasyFlash V3.0.4 already initialize.confirm status 1
Три вещи, на которые стоит обратить внимание. «Имя устройства» — это UID в формате MAC, который я видел в сети на первом этапе, в формате 1e2023XXXXXX. «MAC-адрес устройства» выглядит как отдельный аппаратный MAC-адрес, использующий тот же префикс пакета 1e2023. А строка «sercret», со всеми опечатками, передает мне строку, которую прошивка обрабатывает как секрет.
Да, «sercret». С дополнительной «r». Ошибка в написании метки в отладочной команде, выводящей учётные данные в открытом виде, на серийном оборудовании. Никаких примечаний.
Что прошивка делает с этой строкой, пока неясно, но секретная информация с меткой, выводимая в открытом виде в отладочную команду, заслуживает внимания. Команда write_device_code, расположенная на одну строку выше device_code в меню справки, — это именно то, что подразумевает её название: путь во время выполнения для перезаписи идентификатора во флэш-памяти, аутентификация не требуется.
В оболочке есть printenv для чтения среды EasyFlash, а также cd, ls, cat для того, что прошивка монтирует в качестве своего раздела файловой системы, и setenv / saveenv для записи обратно во флэш-память. Ни один из этих способов не позволил мне напрямую извлечь прошивку. Единственный, который помог, — это fal, уровень абстракции флэш-памяти, с подкомандами для проверки, чтения, записи и стирания.
Команда fal probe без аргументов выводит таблицу разделов:
msh />fal probe[I/FAL] ==================== FAL partition table ====================[I/FAL] | name | flash_dev | offset | length |[I/FAL] -------------------------------------------------------------[I/FAL] | bootloader | beken_onchip_crc | 0x00000000 | 0x0000f000 |[I/FAL] | app | beken_onchip_crc | 0x00010000 | 0x00114000 |[I/FAL] | filesystem | beken_onchip | 0x00125000 | 0x000d1000 |[I/FAL] | param1 | beken_onchip | 0x001fd000 | 0x00002000 |[I/FAL] | netinfo | beken_onchip | 0x001ff000 | 0x00001000 |[I/FAL] =============================================================
Пять разделов на встроенной 2 МБ флэш-памяти: загрузчик, приложение, файловая система, param1, netinfo. Нет слота для загрузки, на что именно жаловалась rt_ota_init. 2 МБ памяти чипа используются целиком, без свободного места для размещения слота для загрузки без перепрошивки.
Выбор раздела осуществляется командой fal probe <name>. После этого команда fal read <offset> <size> выводит байты из выбранного раздела на консоль в виде помеченного шестнадцатеричного представления:
msh />fal probe appProbed a flash partition | app | flash_dev: beken_onchip_crc | offset: 65536 | len: 1130496 |.msh />fal read 0x0 0x40Read data success. Start from 0x00000000, size is 64. The data is:Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F[00000000] 0E 00 00 EA 14 F0 9F E5 14 F0 9F E5 14 F0 9F E5 ................[00000010] 14 F0 9F E5 14 F0 9F E5 14 F0 9F E5 14 F0 9F E5 ................[00000020] C0 06 01 00 40 07 01 00 E0 06 01 00 00 07 01 00 ....@...........[00000030] 20 07 01 00 60 07 01 00 80 07 01 00 EF BE AD DE ...`...........
Один ограниченный примитив чтения через UART, указывающий на каждый раздел, содержащий что-либо интересное: код приложения по адресу 0x10000 (1,13 МБ), загрузчик по адресу 0x00000 (60 КБ), файловая система между ними. Остальная часть дампа выполняется механически.
Я написал небольшой драйвер на Python в блокноте Jupyter. Он перебирает диапазон адресов выбранного раздела блоками по 16 КБ, вызывает fal read через UART для каждого блока, анализирует шестнадцатеричные дампы и записывает байты в бинарный файл. Это была простая часть.
Сложность заключалась в задержке (sleep). Даже при активном использовании устройство переходит в глубокий сон после короткого периода бездействия, а скорость чтения fal более 115200 бод слишком низка, чтобы её обогнать. Оболочка предоставляет функции dont_sleep и pm_level, обе из которых кажутся подходящими примитивами, но мне не удалось заставить ни одну из них поддерживать устройство в активном состоянии во время дампа ни в одной из опробованных комбинаций. Поэтому дампер обрабатывает сон, а не предотвращает его: определяет, когда устройство перешло в спящий режим во время чтения, делает паузу, запрашивает нажатие физической кнопки, ждёт повторного запроса, повторно проверяет раздел и возобновляет чтение с последнего успешного смещения.
Вот тут-то и срабатывает принцип «Показывай, а не рассказывай». Было 2 часа ночи. У меня крутился Jupyter-блокнот, на столе стоял дверной звонок, я периодически нажимал на кнопку звонка, чтобы поддерживать работу дампа прошивки между периодами сна, а кот смотрел на меня из другого конца комнаты, как бы спрашивая, считается ли это работой. Спустя несколько часов и несколько сотен нажатий кнопок у меня получился полный дамп всех важных для меня разделов.
Мне не пришлось глушить чип или снимать его с платы. Устройство передало мне свою прошивку через поставляемый с ним разъём, потому что оболочка позволяла мне это сделать. Блокнот с дампом остался со мной.
Ячейка проверки хеширует дампированный файл и сравнивает количество байтов с данными, полученными через fal probe. Оба значения совпадают, поэтому байты на диске — это байты, отправленные устройством.
Загрузчик не совсем бесполезен. Строки в загрузчике ссылаются на логику проверки образов, которая выполняется при загрузке, с сообщениями типа «Проверка приложения не удалась! Необходимо восстановить заводскую прошивку». Таким образом, он проверяет образы при загрузке, хотя проверка на криптографический тип или CRC требует отдельного анализа, который я не проводил.
Ошибка OTA из загрузочного баннера становится интереснее, когда вы читаете окружающие его строки. Ошибка выполнения: rt_ota_init: Initialize failed! Раздел загрузки (download) не найден. Строки в бинарном файле показывают, что модуль OTA статически присутствует, с форматерами типа OTA Write: [%s] %d%% и защитными сообщениями типа «Невозможно обновить раздел загрузчика!». Код может записать новый образ. Чего он не может сделать на этом устройстве, так это найти слот раздела с именем download для записи, поэтому путь OTA никогда не начинается.
«Версия прошивки», которую показывает приложение X Smart Home для устройства, — 1e2023. Это префикс MAC-адреса и тег пакета с самого устройства, а не реальная строка версии. В сочетании с отсутствующим разделом загрузки наиболее разумным объяснением является то, что на этой платформе вообще нет конвейера OTA. Сообщение приложения «вы используете последнюю версию прошивки» технически верно: обновление прошивки не достигнет этого оборудования в полевых условиях.
Далее меня ждала долгая сессия работы с Ghidra. Все бэкенд-конечные точки, которые я видел в сети, находились в бинарном файле в виде обычных строковых литералов, но для расшифровки окружающего их кода потребовалось время. Все последующие этапы были достигнуты в ходе этой сессии.
Фаза 4: Момент озарения, когда подпись перестала быть подписью
Каждый подписанный запрос проходит через одну и ту же процедуру. Он генерирует параметр токена из этапа 1. Форма с константами и именами параметров:
-
собрать параметры, поступающие в запрос (включая случайные, сгенерированные локально на устройстве);
-
добавить ещё одну запись: secret=<S>, где S — буквенно-цифровая строка, которую прошивка выводит рядом с «sercret» в выводе оболочки device_code (этап 3). Это одно и то же значение на каждом устройстве этой модели;
-
упорядочить записи по ключу в алфавитном порядке;
-
объединить их в строку key=val&key=val&…;
-
выполнить SHA1 этой строки;
-
усечь дайджест до фиксированной длины и отправить его по сети как token=<…>. Запись secret никогда не появляется в самом запросе. Сервер хранит ту же константу, добавляет её заново при получении и пересчитывает.
Но есть несколько ошибок.
«Secret» на самом деле не является секретом. Когда я впервые это увидел, я предположил, что значение будет специфичным для устройства, поскольку секретный ключ с меткой рядом с командой оболочки для каждого устройства — это разумное место для его хранения. В прошивке второго устройства была встроена точно такая же строка. Она поставляется в каждом образе прошивки, идентичная для всех устройств этой модели. Извлеките одно устройство, сделайте дамп флэш-памяти, восстановите значение один раз, и теперь я могу подписывать запросы для любого устройства на платформе. Или вообще пропустите дамп и вежливо запросите UART: команда оболочки device_code из Phase 3 уже выводит его рядом с «sercret». На стороне сервера нет ключа для каждого устройства, которого нет у клиента, и протокол не содержит запроса на ответ для устройства. Идентификация протокола передачи данных с V720 убедительно свидетельствует об использовании той же конструкции во всем семействе платформ.
Параметр random, который поставляется с каждым подписанным запросом, выглядит как nonce. Это не так. Устройство генерирует его локально (шестисимвольная буквенная строка в верхнем регистре) и помещает в параметры, которые хешируются. Сервер никогда его не отслеживает. Во время проверки сервер принимает все параметры, отправленные клиентом, включая значение, заявленное клиентом для случайного числа, добавляет секретный ключ = <S>, хеширует и сравнивает. Если хеш совпадает, принимает запрос. Сервер не хранит никаких записей о ранее увиденных значениях, поэтому в протоколе вообще нет защиты от повторного воспроизведения. Случайное число носит декоративный характер. Я могу зафиксировать его на константе, увеличить его или заполнить буквой A шесть раз, и сервер будет обрабатывать каждый вариант одинаково.
Соединим эти два момента: SHA1 со статическим суффиксом — это не аутентификация. На стороне сервера нет ключа, которого бы не было у клиента. Это замаскированное хеширование целостности. Оно предотвращает случайное чтение pcap-файла, и ничего больше.
После того, как у меня появилась эта функция, я создал небольшой клиент на Python, который её воспроизводит. Я не буду им делиться. С помощью соли и алгоритма я могу отправить запрос к любой подписанной конечной точке на платформе, заполнить любые нужные параметры, добавить правильно вычисленную подпись, и сервер его примет. С этого момента все атаки в этом описании осуществляются через этот клиент.
Это был первый поворотный момент. С этого момента «дверной звонок» и «любой, у кого есть мой Python-клиент» взаимозаменяемы для этой платформы.
Фаза 5: Токены никогда не меняются
Фаза 1 уже показала два интересных поля в настройке вызова: devToken (пароль ретранслятора устройства) и cliToken (пароль ретранслятора учётной записи). Оба передаются по сети в открытом виде при каждом вызове. Названия заставили меня предположить, что это кратковременные токены сессии.
Но это не так. Я проверил наличие смены токенов. Ничего подобного я не обнаружил:
-
сброс к заводским настройкам сохраняет devToken байт за байтом;
-
повторная привязка устройства к другой учётной записи сохраняет его снова;
-
прошивка не содержит фрагмента кода, который запрашивает у бэкенда новый пароль.
Токен хранится на стороне сервера, привязан к идентификатору устройства. Прошивка получает его через блок конфигурации ответа на оповещение на каждом цикле, поэтому сброс к заводским настройкам и повторная привязка никогда не достигают авторитетной копии. Что бы ни создавало запись на сервере изначально, это непрозрачно снаружи, но как только она там оказывается, никакие действия владельца не могут её изменить. Токен учётной записи ведет себя аналогично: он предоставляется один раз при регистрации и с тех пор привязан к учётной записи.
Для регистрации на ретрансляторе в качестве этого дверного звонка достаточно токена devToken. Для регистрации в качестве приложения этого пользователя достаточно токена cliToken. Ни одно из этих значений не привязано к сессии, не защищено от повторного воспроизведения и не является кратковременным.
Окно действия cliToken также шире, чем «количество звонков, на которые ответил владелец». Мобильное приложение регистрируется на ретрансляторе в момент получения push-уведомления, а не когда владелец нажимает «ответить». Таким образом, токен на стороне учётной записи передаётся по сети при каждом оповещении, которое получает приложение, независимо от того, было ли оно принято или проигнорировано. Любой, кто может заставить телефон цели зазвонить (этап 7), может вместе с этим управлять регистрацией его приложения.
Этап 9 показывает, как получить токен устройства, не находясь вообще в сети.
Этап 6: Устройства имеют предсказуемые идентификаторы
Идентификатор устройства представляет собой значение, похожее на MAC-адрес, в формате 1e2023XXXXXX:
-
1e — это локально администрируемый префикс MAC-адреса;
-
2023 — это тег пакета, жёстко закодированный в прошивке;
-
шестнадцатеричная последовательность из шести символов в конце.
Цифры представляют собой увеличивающийся счётчик. Вывод команды device_code в оболочке на этапе 3 напрямую выводит номер партии 1e2023, поэтому каждое устройство, работающее с этой прошивкой, рекламирует один и тот же префикс. Пространство, которое стоит перечислить, — это последние шесть шестнадцатеричных символов, и счётчик является последовательным. Идентификаторы не содержат соли и не привязаны к учётной записи каким-либо образом, который я мог бы заметить.
Также существует бэкенд-эндпоинт, задача которого — создавать новые коды устройств для новых производственных партий. Он принимает префикс партии в качестве параметра и возвращает новый последовательный идентификатор для этой партии. Он подписан (этап 4), при этом идентификатор учётной записи находится среди подписанных параметров, но сам механизм создания не привязывает новый идентификатор к этой учётной записи. Устройство не отображается в списке вызывающей стороны, пока она не выполнит отдельную цепочку подтверждения и привязки (этап 8). Один подписанный POST-запрос под любым указанным мной префиксом возвращает следующий свободный идентификатор для этой партии. Возвращаемое число — это текущая максимальная отметка счетчика, до того, как я перечислил хоть что-то.
Этап 7: Оповещения принимают только идентификатор устройства и ничего больше
Оповещение «нажата дверная кнопка» на телефон любого владельца принимает один подписанный POST-запрос и идентификатор целевого устройства. Конечная точка принимает любое подписанное тело запроса, без какой-либо аутентификации, кроме самой подписи. Этап 1 уже показал, что запрос можно воспроизвести и что прикреплённое изображение передаётся без изменений. Этап 4 сломал подпись. Этап 6 сломал идентификатор. Вместе они сводятся к одному примитиву: любой пользователь в открытом интернете может заставить любую дверную кнопку на платформе звонить на телефон владельца, используя любое прикреплённое мною изображение.
Я позволю вам догадаться, что было в теле JPG-файла, когда я впервые протестировал этот примитив на своем собственном телефоне. Вы правы. Приложение X Smart Home отобразило его под названием «Умная дверная кнопка X3» с весёлым наложением «входящий звонок». С точки зрения приложения, это был тот, кто был у моей двери.
Конечная точка никогда ничего не запрашивает у устройства. В бэкенде нет способа отличить настоящий дверной звонок от поддельного подписанного запроса с действительным идентификатором.
Прошивка предоставляет более одной точки доступа для оповещений. Они отличаются только способом передачи снимка состояния: multipart с JPEG, устаревший путь GET без изображения, base64 inline. Все они принимают одну и ту же конструкцию хеша. Исправление только одной из них просто переносит ошибку.
Фаза 8: Скрытый захват
Процесс подключения X Smart Home использует две HTTP-точки доступа последовательно. Первая переводит устройство в состояние «доступно для привязки» в бэкенде. Вторая привязывает устройство к определённой учётной записи. Обе являются обычными строковыми литералами в прошивке. Обе используют жёстко закодированную соль подписи из Фазы 4. На этапе привязки в качестве нового владельца принимается идентификатор учётной записи, поэтому злоумышленнику нужна свободная учётная запись на платформе, чтобы быть привязываемым устройством, и ничего больше.
Процесс подключения предполагает, что устройство обращается к этим точкам доступа самостоятельно, сразу после сброса к заводским настройкам, до того, как у него появится привязка. Бэкенд не обеспечивает соблюдение этого предположения. Подписанный POST-запрос к первой конечной точке сбрасывает состояние привязки для любого устройства на платформе. Подписанный POST-запрос ко второй конечной точке, с идентификатором учётной записи злоумышленника в качестве нового владельца, завершает привязку. Предыдущий владелец молча удаляется.
Поведение после привязки, наблюдаемое в реальном времени на двух моих собственных устройствах и двух тестовых учётных записях:
-
устройство исчезает из приложения первоначального владельца в момент возобновления привязки;
-
оно остается онлайн в той же сети Wi-Fi, без сброса до заводских настроек на его стороне;
-
устройство не подаёт никаких сигналов о том, что что-то произошло; для владельца оно выглядит нормально, за исключением того, что приложение больше не отображает его.
Цепочка выглядит следующим образом:
-
перечислить идентификаторы устройств в парке или создать один в известной группе (Фаза 6);
-
для каждого идентификатора запустить последовательность подтверждения-загрузки с идентификатором вашей учётной записи в качестве нового владельца;
-
получать каждое событие дверного звонка, каждый звонок, каждый снимок с входной двери незнакомца.
Восстановление — вот где начинаются проблемы. Для восстановления связи владелец выполняет сброс настроек до заводских, а затем повторно регистрируется через приложение. Это повторно привязывает устройство к нему. Но та же цепочка действий, которая привела к его удалению, продолжает работать и через минуту: злоумышленник снова запускает последовательность подтверждения и привязки и возвращает себе устройство. Сброс настроек до заводских не меняет ничего, что бэкенд использует для определения права собственности.
Фаза 9: Момент озарения, когда я стал дверным звонком
Второй поворотный момент. Фаза 8 позволяет злоумышленнику переместить устройство в свою учётную запись. Фаза 9 позволяет ему находиться внутри активного вызова на устройстве, к которому он никогда не прикасался.
Фаза 5 показала, что токены установления вызова никогда не меняются, но для их извлечения из сети всё ещё требуется находиться на пути или хранить журналы бэкенда. Прошивка указывает на гораздо более чистый примитив.
Фаза 1 уже продемонстрировала, что ответ на оповещение содержит конфигурацию сигнализации в своём блоке конфигурации: хост ретранслятора, порт ретранслятора, UID устройства и его постоянный пароль ретранслятора. Получение учётных данных таким способом работает, но отправка оповещения поступает на телефон владельца, что является избыточным способом передачи данных. Платформа также предоставляет вторую подписанную конечную точку, которая возвращает ту же конфигурацию без отправки какого-либо оповещения. Она принимает идентификатор устройства и подписанный токен, а затем передаёт его обратно ретранслятору.
Принимает идентификатор устройства и подписанный токен, возвращает хост ретранслятора, порт, UID и пароль для этого идентификатора, и никогда не затрагивает приложение владельца. Нет проверки, является ли вызывающий абонент устройством, текущим владельцем устройства или кем-либо конкретным.
Таким образом, с помощью поддельного подписанного запроса и любого идентификатора целевого устройства платформа напрямую возвращает постоянный пароль ретранслятора этого устройства. Один POST-запрос, один ответ. Приложение владельца никогда не активируется.
Как только у меня есть пароль, имитация происходит механически:
-
открыть соединение с ретранслятором, возвращённым поиском;
-
отправить заголовок регистрации платформы и JSON-данные регистрации, используя идентификатор целевого устройства в качестве UID и скомпрометированный пароль в качестве токена. С этого момента ретранслятор рассматривает мой сокет как реальное устройство;
-
отправить поддельное оповещение «нажат дверной звонок» на учётную запись целевого устройства. Звонит его телефон;
-
подождать, пока владелец нажмёт «ответить». Нажатие не приводит к вызову на сторону устройства. Это сообщает ретранслятору о готовности приложения и передаёт данные для подключения приложения. Ретранслятор передает их тому сокету, который зарегистрирован как устройство, то есть моему. Настоящий дверной звонок никогда не отправляется;
-
после этого мой клиент делает то же, что и настоящее устройство: набирает номер приложения владельца по UDP, выполняет обнаружение NAT и пробивание дыры, а затем передаёт обратно выбранное злоумышленником видео в формате MJPEG. Соединение между устройствами всегда происходит от устройства к приложению, никогда наоборот.
Шаг 3 имеет полезный побочный эффект для злоумышленника. Как отмечалось на этапе 5, приложение владельца регистрируется у ретранслятора в момент получения push-уведомления, а не когда владелец нажимает «ответить». Имитационный звонок, который владелец игнорирует (телефон звонит, но никто не отвечает), всё равно передает cliToken владельца по сети.
Существует вариант той же атаки, реализуемый только на аппаратном уровне, который меня очень радует. На третьем этапе в оболочке UART появилась команда write_device_code, которая перезаписывает идентификатор устройства во флэш-памяти, без необходимости аутентификации. Имея доступ к UART принадлежащего мне дверного звонка, я записываю идентификатор целевого устройства в свой собственный блок, нажимаю кнопку, и стандартная прошивка делает всё остальное. Она отправляет подписанное оповещение от имени цели, получает пароль цели обратно в блоке конфигурации и регистрируется на реле как цель. Злоумышленник ничего не записывает. Прошивка уже свободно владеет протоколом; она просто работает с заимствованным идентификатором. С точки зрения платформы моё оборудование теперь является их дверным звонком.
На телефоне владельца отображается обычный звонок с его обычной двери. Видео, которое он видит, — это то, что я отправляю: статичное изображение, записанный клип, зацикленный кадр, подготовленная подделка. Видеоканал работает в одностороннем порядке от устройства к приложению, поэтому ничто в телефоне не ограничивает то, что отправляет злоумышленник. Аудиопоток двусторонний: владелец говорит и слушает через свой телефон, как обычно; злоумышленник говорит и слушает через клиент, имитирующий действия злоумышленника. Ретранслятор передаёт аудиосигнал между двумя конечными точками, рассматривая клиент злоумышленника как настоящий дверной звонок. Настоящий дверной звонок ничего этого не видит. Ни на телефоне, ни на устройстве нет сигнала, указывающего на то, что камера на другом конце — это не та же самая камера.
Это существенно отличается от этапа 8. При перехвате управление устройство навсегда переназначается, и владелец перестает получать уведомления. При имитации устройства оно остается на том же месте. Владелец продолжает получать уведомления. Просто он больше не разговаривает со своей настоящей входной дверью, когда открывает её.
У меня есть работающий клиент, имитирующий действия злоумышленника. Я не буду публиковать его.
Стена позора
Собираем цепочку. Что может сделать злоумышленник, вооруженный HTTP-библиотекой, с указанием необходимых условий для каждого действия:
ЗАХВАТ: незаметно украсть любой дверной звонок на платформе
Два подписанных POST-запроса с любой учётной записи. Первый сбрасывает состояние привязки целевого устройства, второй назначает его злоумышленнику. Приложение первоначального владельца отключает устройство в момент восстановления привязки. Само устройство остаётся в сети и ничего не знает о произошедшем. В сочетании с последовательными идентификаторами и подделываемой подписью это может произойти в масштабах всего парка устройств.
КРАЖА WI-FI: получить учётные данные домашней сети через отладочный порт
Доступ к UART на дверном звонке выводит домашний SSID, PSK и парные и групповые ключи WPA в отладочную консоль во время загрузки. Дверной звонок установлен снаружи дома. Необходимы лишь отвёртка и несколько минут тишины. Одна точка доступа превращается во всю локальную сеть.
ПЕРЕХВАТ ВЫЗОВА: имитация дверного звонка в реальном времени во время чужого звонка
Один подписанный запрос возвращает постоянный пароль ретранслятора целевого устройства. Зарегистрируйтесь на ретрансляторе как этот дверной звонок, заставьте телефон цели зазвонить с поддельным оповещением и ответьте на звонок от имени устройства с помощью выбранного злоумышленником видео и двусторонней аудиосвязи в реальном времени. Настоящий дверной звонок остаётся в сети. Владелец никогда не узнает, что камера на другом конце линии — это не та же камера.
Результаты по степени серьёзности
Полный список в одном месте. Степень серьёзности — моя собственная оценка.
[Критическая] Скрытый захват через цепочку подтверждения и привязки. Два подписанных POST-запроса с любой учётной записи на платформе: один сбрасывает состояние привязки, второй назначает. Устройство подключается к учётной записи злоумышленника. Приложение первоначального владельца отключает устройство в момент восстановления соединения. Само устройство остаётся в сети и не выдаёт никаких сообщений. В сочетании с последовательными идентификаторами и подделываемой подписью это распространяется на весь парк устройств.
[Критическая] Однократная кража учётных данных через конечную точку signaling-config. Любой подписанный запрос с идентификатором целевого устройства возвращает постоянный пароль ретранслятора этого устройства непосредственно в теле ответа. Злоумышленнику не нужно находиться в сети или читать журналы бэкенда. Именно это делает 9-ю фазу одношаговой атакой, а не двухшаговой.
[Критическая] Имитация устройства в реальном времени. Имея в распоряжении утёкший пароль ретранслятора для каждого устройства и поддельное подписанное оповещение, я могу зарегистрироваться на ретрансляторе как дверной звонок другого человека, заставить его телефон зазвонить и ответить на звонок от его имени с помощью выбранного злоумышленником видео и двусторонней аудиосвязи в реальном времени. Оригинальное устройство остаётся в сети, а владелец продолжает получать уведомления. Он просто больше не видит свою настоящую входную дверь, когда поднимает трубку.
[Критическая] Постоянный пароль для каждого устройства никогда не меняется. Пароль хранится на стороне сервера и привязан к идентификатору устройства. Сброс к заводским настройкам, переподключение к другой учётной записи и даже просто время делают его идентичным по байтам. После того, как злоумышленник его получит (один подписанный запрос, см. выше), даже удаление оборудования помогает только после того, как сервер перестанет передавать пароль. Повторная регистрация переподключает устройство, но учётные данные, которыми владеет злоумышленник, продолжают работать.
[Высокий уровень опасности] Постоянные учётные данные в открытом виде при установлении вызова. Протокол установления вызова передаёт идентификатор учётной записи, публичные и частные IP-адреса, сопоставления NAT и постоянные пароли для каждого устройства и учётной записи между участниками и бэкендом при каждом вызове в незашифрованном виде. Это не токены сессии; это долгосрочные пароли, используемые повторно до тех пор, пока не будут заменены, и замена, по-видимому, не происходит. Мобильное приложение регистрируется у ретранслятора при получении push-уведомления, а не при ответе на звонок, поэтому пароль на стороне учётной записи передаётся по сети при каждом оповещении, полученном приложением, независимо от того, было ли оно принято или проигнорировано.
[Высокий уровень опасности] Незашифрованные медиаданные P2P-звонка. Видеопоток с дверного звонка в приложение и двусторонняя аудиосвязь между ними передаются по каналу связи в открытом виде. Любой, кто находится на пути между любой из сторон и ретранслятором или между двумя участниками после пробития соединения, может получить прямую трансляцию по сети.
[Высокий уровень опасности] Утечка учётных данных Wi-Fi через журнал UART. Во время установления соединения Wi-Fi прошивка выводит SSID домашней сети, PSK и производные парные и групповые ключи WPA в консоль отладки. Доступ к UART на дверном звонке, установленном снаружи дома, сводится к отвёртке и нескольким минутам тишины, что превращает доступ к одному устройству в компрометацию всей сети.
[Высокий уровень опасности] Последовательные идентификаторы устройств. Идентификатор устройства в 12-хешированном формате равен 1e2023 плюс увеличивающийся счётчик. Весь парк устройств поддаётся перечислению.
[Высокий уровень опасности] Неаутентифицированное создание новых кодов устройств. Подписанный запрос с известным префиксом пакета и идентификатором учётной записи возвращает новый последовательный идентификатор устройства в рамках этого пакета. Сам механизм создания не привязывает новый идентификатор к вызывающей учётной записи; для этого требуется отдельная цепочка подтверждения и привязки. Помимо очевидного злоупотребления, связанного с произвольным расширением пространства имен, каждый механизм создания раскрывает максимальную точку счётчика пакета тому, кто запросил его, что делает целевое перечисление тривиальным.
[Высокий уровень опасности] Неаутентифицированная доставка оповещений. Конечная точка оповещения принимает идентификатор устройства, изображение и подписанный токен. Это весь входной сигнал; сам запрос не содержит аутентификации, кроме подписи. Любой пользователь открытого Интернета, способный подписать запрос, может позвонить на телефон любого владельца с помощью предоставленного злоумышленником изображения.
[Средний уровень] Жёстко закодированная соль подписи. «Подпись» каждого запроса к плоскости управления представляет собой усечённый SHA1, полученный из параметров, упорядоченных по алфавиту, плюс фиксированную буквенно-цифровую строку, встроенную в прошивку. После восстановления запросы могут быть подделаны для любого устройства. Идентификация протокола передачи данных с родственным приложением V720 предполагает, что та же конструкция распространяется на все семейства платформ.
[Средний уровень] Несколько точек оповещения, одна схема аутентификации. Многокомпонентные запросы с JPEG, устаревший GET-запрос без изображения и base64-в-многокомпонентных запросах — все они принимают один и тот же формат хеша. Патчинг одного запроса без других ничего не меняет.
[Средний уровень] Плоскость управления на обычном HTTP на сервере, который уже обслуживает TLS. Тот же nginx на том же хосте принимает HTTPS на порту 443 с действительным сертификатом и обслуживает те же конечные точки API идентично. Прошивка была написана для использования обычного HTTP независимо от этого. Это выбор конфигурации, а не ограничение сервера.
[Средний уровень] Подробная отладочная оболочка UART на производственном оборудовании. Поставляемая прошивка выводит изображение на интерактивную консоль через обозначенный UART-разъём, где любому пользователю с последовательным адаптером доступны команды справки, состояния и памяти. В той же оболочке при каждом пробуждении отображается полный обмен данными по протоколу STUN, постоянный пароль ретранслятора устройства и идентификатор учётной записи владельца.
[Средний уровень] Произвольное чтение памяти с помощью отладочных команд. Достаточно для выгрузки всего образа прошивки без использования специализированных инструментов. Загрузка завершается постоянно.
Другие находки, связанные с прошивкой.
[Низкий уровень] OTA-обновление не работает в поставляемой прошивке. В этой сборке отсутствует раздел OTA, поэтому устройство не может получать обновления по воздуху. Любое исправление от производителя должно будет включать перепрошивку во время производства будущих устройств, без возможности обновления существующего оборудования.
[Низкий уровень/уведомление] Загрузчик выполняет проверку образа при загрузке. Строки в загрузчике ссылаются на логику проверки с сообщениями типа «Проверка приложения не удалась!». Криптографическая проверка или проверка CRC не были определены в этом проходе. Стоит знать, если будущий исследователь попытается внедрить модифицированную прошивку.
Влияние
Для одного владельца это плохо по двум причинам. Захват позволяет любому пользователю в открытом интернете незаметно перенести ваш дверной звонок на свой аккаунт. С этого момента каждое событие с вашей входной двери будет приходить на его телефон, а не на ваш. Выдача себя за другое лицо позволяет вмешиваться в любой ваш звонок, заменяя видеопоток с камеры на что угодно и ведя живой разговор от имени самозванца. Первый вариант — кража устройства. Второй способ — кража отдельных разговоров. Оба происходят без каких-либо индикаций на устройстве: устройство продолжает светиться, приложение постоянно показывает «онлайн», и вы узнаете об этом либо по тому, что дверной звонок затих (захват), либо по тому, что человек у двери на самом деле не у вас (выдача себя за другое лицо).
Восстановление сложнее, чем кажется. Сброс к заводским настройкам возвращает устройство к первоначальному владельцу, но пароль для каждого устройства хранится на сервере, привязан к идентификатору устройства и не меняется при сбросе или переподключении устройства. Злоумышленник, получивший пароль один раз, может зарегистрироваться на ретрансляторе как устройство до тех пор, пока эта запись существует на сервере. Единственное гарантированное решение на уровне устройства — выбросить оборудование, но даже это поможет только после того, как сервер перестанет выдавать пароль.
Если у злоумышленника есть физический доступ (что несложно для оборудования, установленного снаружи дома), возможности для кражи расширяются. Доступ через UART позволяет получить учётные данные домашней сети Wi-Fi в открытом виде во время загрузки, превращая точку доступа к домашней локальной сети через дверной звонок. Этот же доступ также позволяет реализовать вариант атаки с подменой личности, доступный только на аппаратном уровне: команда оболочки write_device_code перезаписывает идентификатор во флэш-памяти, а протокол обрабатывается стандартной прошивкой.
Для всего парка устройств ситуация ещё хуже. Идентификаторы перечисляемы и могут быть созданы, путь оповещения может быть подделан, учётные данные для каждого устройства доступны по одному подписанному запросу, а захват представляет собой цепочку из двух конечных точек, которую может контролировать любая зарегистрированная учётная запись. Мотивированный злоумышленник может просканировать адресное пространство и либо получить учётные данные и карты домашней сети сразу, либо выбирать цели по одной.
Кроме того, на стороне производителя нет простого способа исправить это. В этой версии прошивки отсутствует раздел OTA, поэтому даже если Naxclow выпустит исправленную сборку завтра, у неё не будет доступа к уже используемым устройствам.
Цепочка состоит из множества независимых звеньев: прошивка, точки регистрации, точка кражи учётных данных, предсказуемая схема идентификации. Каждое из них требует собственного исправления. Закрытие любого из них по отдельности просто переносит атаку на другую ступень.
Раскрытие информации
01.04.2026 Начал тестирование на собственных устройствах в изолированной лаборатории, работая неполный рабочий день.
29.04.2026 Отчёт о раскрытии информации отправлен по рабочим адресам электронной почты (история поиска контактов приведена во введении) и через форму обратной связи в приложении X Smart Home. Ответа нет.
06.05.2026 Публикация, конфиденциальная информация скрыта, это произошло через неделю после уведомления.
Naxclow управляется компанией Guangzhou Qiangui IoT Technology Co., Ltd., поэтому информация передана в надлежащую организацию независимо от того, под каким брендом было выпущено устройство.
Если они свяжутся со мной, я обновлю этот блог с их ответом и любыми исправлениями, которые будут выпущены. Я с удовольствием поделюсь полными результатами исследования, включая отредактированные части, через соответствующий канал связи.
Выводы
Если у вас есть одно из этих устройств
Используйте VLAN для вашего IoT. Я не могу повторить это ещё громче. Относитесь к дверному звонку как к враждебному сетевому хосту в вашей собственной локальной сети, разместите его в гостевом сегменте, который не может получить доступ ни к чему, что вам нужно, и примите тот факт, что производитель, вероятно, не сможет ничего из этого исправить, даже если бы захотел. В протестированной мною прошивке отсутствует возможность обновления по воздуху (OTA), поэтому даже попытка исправить проблему не приведёт к повреждению устройства, уже приклеенного к вашей стене.
Несколько практических сигналов, на которые стоит обратить внимание:
-
дверной звонок внезапно замолкает: теперь это стоит проверить;
-
ответ на звонок вызывает какие-либо проблемы: доверьтесь этому ощущению;
-
приложение X Smart Home показывает устройство как отключённое чаще, чем это объясняется сбоем в сети: та же история.
Если это вас беспокоит, выбросьте его. Следующий дверной звонок за $12 в том же списке Temu, вероятно, работает на той же прошивке и на том же бэкенде.
Если вы создаёте эти устройства
Дешёвый микроконтроллер не освобождает бэкенд от ответственности за идентификацию. «Подпись», созданная из строки, которая поставляется с каждым образом прошивки, не является аутентификацией. Конечные точки подключения, которые принимают новое соединение без проверки текущего права собственности, являются примитивами захвата управления в масштабах всего парка устройств.
Зеркальное отображение постоянных учётных данных между участниками при каждом установлении соединения превращает передачу данных в широковещательную рассылку, а пароль для каждого устройства, который никогда не меняется, делает эту рассылку постоянной. Если ваш nginx уже завершает TLS, направьте свою прошивку на него. И если вы являетесь владельцем платформы, пожалуйста, смените учётные данные после сброса до заводских настроек.
Если вы проводите подобную оценку
Дешёвые устройства на базе микроконтроллеров выглядят пугающе, потому что вас не ждёт оболочка Linux и нет очевидной опоры над кремниевым кристаллом. Это не так. Тот же UART, который упрощает разработку, находится прямо на производственной плате, и любая команда отладки, считывающая память, при достаточном терпении и достаточном количестве нажатий кнопок в 2 часа ночи, предоставит вам прошивку. Потратьте полдня на изучение последовательного адаптера, прежде чем брать что-то более навороченное.
Обновления
06.05.2026. Я открыл координационное обращение в VINCE CERT/CC по поводу результатов, указанных ниже. Назначение CVE будет осуществлено в рамках этого процесса.
07.05.2026. Naxclow связалась со мной через день после публикации этого поста, подтвердила получение сообщения и начала внутренний процесс проверки.
ссылка на оригинал статьи https://habr.com/ru/articles/1038296/