Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2021-4045 — Exploit de injeção de comando para a câmera TP-Link Tapo C200 (CVE-2021-4045) fornecendo acesso ao shell root via UART e análise do binário uhttpd com engenharia reversa. | Kitploit
Ferramentas/GitHubGitHub/kaleth4/cve-2021-4045
Segurança de Sistemas EmbarcadosSegurança IoTAnálise de VulnerabilidadesExploraçãoEngenharia ReversaHacking de HardwareTestes de PenetraçãoComando e ControleAnálise de Firmware
GitHubkaleth4/cve-2021-4045

CVE-2021-4045

Exploit de injeção de comando para a câmera TP-Link Tapo C200 (CVE-2021-4045) fornecendo acesso ao shell root via UART e análise do binário uhttpd com engenharia reversa.

há 2 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
Ver Repositório

🔍 CVE-2021-4045: Vulnerabilidade de Injeção de Comandos no TP-Link Tapo C200

image

CVE-2021-4045


📌 Resumo

CVE-2021-4045 é uma vulnerabilidade de injeção de comandos descoberta na câmera TP-Link Tapo C200, que permite que um atacante assuma o controle total do dispositivo com privilégios de root. Esta vulnerabilidade afeta todas as versões de firmware anteriores à 1.1.16 Build 211209 Rel. 37726N.

🔗 Aviso oficial do INCIBE: https://www.incibe.es/incibe-cert/alerta-temprana/vulnerabilidades/cve-2021-4045 (Substituir pelo link real)

🔧 Solução recomendada: Atualizar o firmware para a versão 1.1.16 ou superior.



🛠 Reconhecimento Inicial

Configuração e características do dispositivo

  • Câmera IP econômica (~30€) com funções avançadas:
    • Gravação em cartão SD.
    • Rotação horizontal de 360° e vertical de 90°.
    • Reprodução de áudio em tempo real a partir do aplicativo móvel.

Escaneamento de portas```bash

$ nmap -sV -p- 192.168.1.81

root@kitploit:~
**Resultado**:```
PORT     STATE SERVICE
443/tcp  open  https
554/tcp  open  rtsp
2020/tcp open  xinupageserver
8800/tcp open  sunwebadmin
image Como podem ver, o dispositivo tem algumas portas abertas interessantes. A primeira coisa que testei foi a porta 443. Embora o nmap indique claramente que usa https, quando fiz a varredura inicial, passei por cima disso e perdi bastante tempo pensando que a porta 443 estava usando http. Por causa disso, só testei http://192.168.1.81:443 em vez de https://192.168.1.81:443, então só obtive respostas 400. Como disse na introdução, esse processo foi cheio de falhas. Quanto às outras portas, os serviços que rodavam nelas eram totalmente desconhecidos para mim e não encontrei nenhuma informação clara a respeito. Naquele momento, fiquei sem opções conhecidas, então era hora de pesquisar mais a fundo.

----[ Obter uma shell ]-------------------------------

Antes de comprar a câmera, pesquisei na internet investigações anteriores sobre o dispositivo e, por sorte, encontrei este repositório no GitHub onde as pessoas colaboravam para fazer engenharia reversa. Um dos problemas explicava como obter acesso ao console através da porta UART, algo que eu desconhecia completamente na época. Então aprendi o básico e comprei um conversor USB para TTL para me conectar. image Com a ajuda do problema mencionado, consegui abrir o dispositivo com uma faca e uma chave de fenda e localizar rapidamente o UART. Depois de algumas tentativas e muita paciência, finalmente consegui soldar alguns fios nas almofadas.

image

Depois, chegou a hora de verificar se a solda era boa o suficiente para a transmissão de dados. Conectei os fios ao adaptador USB, lembrando que Rx do UART vai para Tx do adaptador e vice-versa, e conectei o adaptador ao meu computador. Novamente, graças ao problema mencionado, eu sabia que a velocidade de transmissão para a conexão serial era 57600, então executei:

$ sudo screen /dev/tty.usbserial-0001 57600

Onde '/dev/tty.usbserial-0001' é a porta USB à qual o adaptador está conectado e que alimenta o dispositivo. Imediatamente comecei a receber dados, ótimo.

No entanto, ainda não tinha acesso ao console. O que eu recebia era simplesmente a sequência de inicialização do dispositivo, que na verdade era o bootloader U-Boot. Tinha uma aparência semelhante a esta:

U-Boot 2014.01-v1.2 (Jul 16 2021 - 18:41:10)

Board: IPCAM RTS3903 CPU: 500M :rx5281 prid=0xdc02 force spi nor mode DRAM: 64 MiB @ 1066 MHz Skipping flash_init Flash: 0 Bytes flash status is 0, 0, 0 SF: Detected XM25QH64A with page size 256 Bytes, erase size 64 KiB, total 8 MiB Using default environment

Autobooting in 1 seconds copying flash to 0x81500000 flash status is 0, 0, 0 SF: Detected XM25QH64A with page size 256 Bytes, erase size 64 KiB, total 8 MiB SF: 8388608 bytes @ 0x0 Read: OK

[...]

Ao pressionar Enter, é solicitado que insiramos um nome de usuário e uma senha. Graças a esse problema do GitHub, conhecemos as credenciais, então podemos fazer login corretamente com o usuário 'root' e a senha 'slprealtek' e, finalmente, obter acesso ao console.

Assim que verifiquei que a conexão funcionava, precisei reforçar a solda, pois ela havia quebrado duas vezes durante o processo de montagem da carcaça. Apliquei silicone termofusível para segurar todos os fios e fechei o dispositivo, desconectando todos os motores. Agora, minha unidade de teste estava pronta.

image

----[ Explorando o dispositivo ]--------------------------

Agora que temos uma carcaça, vamos explorar o dispositivo:

root@SLP:~# uname -a Linux SLP 3.10.27 #1 PREEMPT Wed Nov 11 20:42:05 CST 2020 rlx GNU/Linux

root@SLP:~# cat /etc/openwrt_version 12.09-rc1

Como podemos ver, trata-se de uma máquina OpenWRT rodando Linux 3.10.27. Agora vamos verificar os processos ativos e as portas abertas:

root@SLP:~# ps PID USER VSZ STAT COMMAND 1 root 2328 S init 2 root 0 SW [kthreadd] 3 root 0 SW [ksoftirqd/0] 4 root 0 SW [kworker/0:0] 5 root 0 SW< [kworker/0:0H] 6 root 0 SW [kworker/u2:0] 7 root 0 SW [rcu_preempt] 8 root 0 SW [rcu_bh] 9 root 0 SW [rcu_sched] 10 root 0 SW< [khelper] 11 root 0 SW< [writeback] 12 root 0 SW< [bioset] 13 root 0 SW< [kblockd] 14 root 0 SW [khubd] 15 root 0 SW [kworker/0:1] 16 root 0 SW [kswapd0] 17 root 0 SW [fsnotify_mark] 18 root 0 SW< [crypto] 27 root 0 SW [kworker/u2:1] 46 root 0 SW< [deferwq] 47 root 0 SW< [kworker/0:1H] 247 root 2328 S -ash 262 root 0 SW [irq/27-gpio res] 273 root 0 SW< [cryptodev_queue] 282 root 860 S /sbin/hotplug2 --override --persistent --set-rules-f 304 root 888 S /sbin/ubusd 325 root 8152 S tp_manage 357 root 3416 S /usr/bin/ledd 361 root 3408 S /sbin/msglogd 367 root 3220 S /usr/sbin/netlinkd 370 root 5468 S < /usr/bin/system_state_audio 379 root 10180 S /usr/sbin/wlan-manager 491 root 1636 S /sbin/netifd 492 root 1520 S /usr/sbin/connModed 494 root 11488 S /usr/bin/dsd 496 root 1532 S /usr/sbin/connModed 502 root 7640 S /bin/cloud-service 520 root 4360 S /bin/cloud-brd -c /var/etc/cloud_brd_conf 653 root 15020 S /bin/cloud-client 830 root 2320 S /usr/sbin/telnetd -b 127.0.0.1 861 root 3852 S /usr/sbin/uhttpd -f -h /www -T 180 -A 0 -n 8 -R -r C 870 root 6048 S /usr/bin/relayd 872 root 5948 S /usr/bin/rtspd 879 root 4612 S /usr/bin/p2pd 884 root 11152 S /bin/dn_switch 889 root 4180 S /bin/storage_manager 920 root 40940 S /bin/cet 956 root 32336 S /bin/vda 960 root 3808 S /bin/wtd 970 root 11288 S /bin/nvid 1019 root 2332 S udhcpc -p /var/run/static-dhcpc.pid -s /lib/netifd/s 1037 root 0 SW [RTW_CMD_THREAD] 1059 root 1212 S wpa_supplicant -B -Dwext -iwlan0 -P/tmp/supplicant_p 1089 root 2332 S /usr/sbin/ntpd -n -p time.nist.gov -p 133.100.9.2 -p 1103 root 3840 S /usr/bin/motord 1447 root 2324 R ps

root@SLP:~# netstat -natpu Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:8800 0.0.0.0:* LISTEN 920/cet tcp 0 0 127.0.0.1:929 0.0.0.0:* LISTEN 875/p2pd tcp 0 0 0.0.0.0:20002 0.0.0.0:* LISTEN 325/tp_manage tcp 0 0 0.0.0.0:2020 0.0.0.0:* LISTEN 969/nvid tcp 0 0 0.0.0.0:554 0.0.0.0:* LISTEN 920/cet tcp 0 0 127.0.0.1:23 0.0.0.0:* LISTEN 832/telnetd tcp 0 0 127.0.0.1:921 0.0.0.0:* LISTEN 878/relayd tcp 0 0 127.0.0.1:922 0.0.0.0:* LISTEN 877/rtspd tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 863/uhttpd tcp 0 0 192.168.1.80:37380 52.19.66.90:443 ESTABLISHED 507/cloud-brd udp 0 0 0.0.0.0:20002 0.0.0.0:* 325/tp_manage udp 0 0 0.0.0.0:38000 0.0.0.0:* 1087/ntpd udp 0 0 0.0.0.0:3702 0.0.0.0:* 969/nvid

Podemos ver os processos por trás dessas portas abertas que aparecem na varredura do nmap, como uhttpd ou cet. Concentrei-me especificamente no processo uhttpd, pois é ele que está por trás do servidor https (que na época eu ainda acreditava ser http) e já estava muito familiarizado com os protocolos http.

uhttpd é um servidor web criado pela OpenWRT para uso em dispositivos embarcados que rodam essa distribuição. Nesse ponto, queria saber se podia obter mais informações sobre ele, como o código-fonte ou, pelo menos, as rotas. Visitei a wiki da OpenWRT e aprendi sobre uhttpd e OpenWRT em geral. Em máquinas OpenWRT, existe um sistema chamado Interface de Configuração Unificada (UCI), que é usado basicamente para configurar facilmente os serviços do sistema. Usando isso, podemos obter a configuração do uhttpd:

root@SLP:~# uci show | grep uhttpd ucitrack.@uhttpd[0]=uhttpd ucitrack.@uhttpd[0].init=uhttpd uhttpd.main=uhttpd uhttpd.main.listen_https=443 uhttpd.main.home=/ww uhttpd.main.rfc1918_filter=1 uhttpd.main.max_requests=8 uhttpd.main.cert=/tmp/uhttpd.crt uhttpd.main.key=/tmp/uhttpd.key uhttpd.main.cgi_prefix=/cgi-bin uhttpd.main.lua_prefix=/luci uhttpd.main.lua_handler=/usr/lib/lua/luci/sgi/uhttpd.lua uhttpd.main.script_timeout=180 uhttpd.main.network_timeout=180 uhttpd.main.tcp_keepalive=0 uhttpd.px5g=cert uhttpd.px5g.days=3600 uhttpd.px5g.bits=1024 uhttpd.px5g.country=CN uhttpd.px5g.state=China uhttpd.px5g.location=China uhttpd.px5g.commonname=TP-Link upnpc.uhttpd=entry upnpc.uhttpd.proto=TCP upnpc.uhttpd.ext_port=80 upnpc.uhttpd.desc=uhttpd

Aqui há alguns parâmetros interessantes. Primeiro, 'uhttpd.main.home' aponta para o diretório raiz do servidor, então poderíamos encontrar alguns arquivos do servidor web. Depois, 'uhttpd.main.lua_handler' aponta para o script do manipulador Lua que é usado para inicializar o ambiente de execução do Lua ao iniciar o servidor, já que uhttpd suporta scripts Lua, então poderia haver mais arquivos interessantes ali. No entanto, o diretório '/www' está vazio e não há nenhum diretório 'sgi' em '/usr/lib/lua/luci' nem o arquivo 'uhttpd.lua' no sistema. Tentei encontrar informações sobre como essa instância do uhttpd funciona, mas não encontrei nada, apenas parâmetros de configuração que não apontam para lugar nenhum.

Nesse ponto, soube que a solução era analisar diretamente o binário 'uhttpd' e aplicar engenharia reversa, mas antes queria criar um ambiente de teste para saber o que acontecia dentro do servidor web ao realizar as requisições, já que, pela forma como o processo foi criado, não havia nenhuma saída em lugar nenhum.

Tentei executar o comando que aparece na saída do comando ps para o processo 861:

$ /usr/sbin/uhttpd -f -h /www -T 180 -A 0 -n 8 -R -r C

No entanto, obtive muitos erros e não consegui fazê-lo funcionar. Como não consegui criar o mesmo processo uhttpd em uma porta diferente, tentei buscar a saída faltante revisando a entrada '/proc' do processo, para tentar lê-las se existissem (como explicado neste vídeo do PwnFunction). Mas havia um grande problema:

root@SLP:~# sudo ls -l /proc/864/fd/ lrwx------ 1 root root 64 Nov 10 22:44 0 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 1 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 2 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 3 -> anon_inode:[eventpoll] lrwx------ 1 root root 64 Nov 10 22:44 4 -> socket:[1830]

Todos os descritores de arquivo para 'stdin', 'stdout' e 'stderr' foram redirecionados para '/dev/null', o que basicamente os redireciona para um buraco negro onde não podem ser encontrados. Eu estava preso e não sabia o que fazer. Como já estava na entrada '/proc', comecei a investigar, pois não lembrava que as entradas '/proc' tinham tanta informação sobre um processo e fiquei curioso. Graças a essa curiosidade fortuita, me deparei com a entrada 'environ', que contém todas as variáveis de ambiente para aquele processo. Uma dessas variáveis de ambiente era:

UHTTPD_ARGS=-h /www -T 180 -A 0 -n 8 -R -r C200 -C /tmp/uhttpd.crt -K /tmp/uhttpd.key -s 443

Imediatamente percebi que o comando mostrado pelo ps estava incorreto e, posteriormente, descobri que isso se devia ao fato de a interface UART não ter largura suficiente para exibir todos os caracteres. Mais uma falha que me ensinou lições importantes: nunca confiar na saída de uma porta UART.

Agora finalmente consegui criar outra instância do uhttpd com os mesmos parâmetros e sem pipes para '/dev/null' para testar o binário enquanto fazia engenharia reversa.

----[ Engenharia reversa do uhttpd com Ghidra ]--------

Esta foi a primeira vez que usei o Ghidra. Tinha visto alguns vídeos e lido alguns artigos sobre ele (graças ao stacksmashing e ao liveoverflow pelo conteúdo incrível e fácil de entender), mas nunca o tinha usado de verdade, então esta foi uma ótima oportunidade para aprender.

Abri o binário uhttpd e, após várias tentativas, descobri que a linguagem era MIPS32, little endian, com mips16e. Alguns nomes de funções vinham por padrão com o binário, mas outros não. Também dediquei algum tempo para renomear funções, pois, aparentemente, o Ghidra costuma se confundir com funções externas e obtemos wrappers estranhos para elas, como:

image Analisei a função `main()` e outras importantes para compreender a lógica do binário e sua estrutura. Encontrei algumas interessantes, já identificadas, entre as quais estavam `do_login()` e `uh_slp_proto_request()`. Falarei mais sobre esta última mais adiante.

Após este primeiro contato, comecei a procurar erros. Como sou um completo novato em vulnerabilidades de estouro, a primeira coisa que fiz foi procurar chamadas para system(), exec() e popen() para verificar se existia alguma vulnerabilidade de injeção de comandos que pudesse explorar facilmente. E tive muita sorte.

A função 'exec_and_read_json()' usa 'popen()' para executar comandos:

executar_y_leer_json

A função 'exec_and_read_json()' é usada por duas funções sem nome, que denominei 'set_language()' e 'wifi_connect()'. Essas funções são responsáveis respectivamente pela configuração do idioma e pela conexão Wi-Fi (obviamente). 'wifi_connect()' parece analisar as aspas simples ('), enquanto 'set_language()' não. Isso significa que, se pudermos controlar a entrada da função 'set_language()', podemos injetar nossos próprios comandos com sucesso:

conexão wifi definir_idioma

A função 'set_language()' é usada por 'uh_slp_proto_request()', a função que mencionei anteriormente, que passa como entrada alguns dados analisados recebidos do usuário.

função_principal_1 função_principal_2

Para analisar os dados do usuário, uh_slp_proto_request() verifica se trata-se de um objeto JSON válido. Em seguida, obtém um valor de string identificado pela chave method e um valor de dicionário identificado por params (pelo menos é o que acho, pois o Ghidra não conseguiu resolver a chamada da função, mas parecia funcionar dessa forma). Dependendo do método selecionado, uh_slp_proto_request() seleciona a função que será executada.

Então, ao enviar a seguinte carga útil:{"method": "setLanguage", "params":{}}

Chamamos corretamente a função 'set_language()' e passamos '{}' como parâmetro 'language_json'. Em seguida, dentro de 'set_language()', o objeto 'language_json' é convertido em uma string e inserido diretamente em "ubus call system_state_audio set_language '%s'" para sua execução.

Ao enviar esta carga útil:{"method": "setLanguage", "params": {"payload": "'; touch poc;'"}}

Será executado o seguinte.

ubus call system_state_audio set_language '{"payload": "'; touch poc;'"}'

Que na verdade contém 3 comandos:

ubus call system_state_audio set_language '{"payload": "' touch poc '"}'

A segunda nos permite a execução completa do código.

Agora, a função 'uh_slp_proto_request()' é utilizada por outra função sem nome que gerencia todas as solicitações, a qual denominei 'main_server_function()'. Se uma solicitação for válida (não excede o comprimento máximo, usa 'http' ou 'https' conforme a configuração do servidor, etc.), 'main_server_function()' verifica se a URL contém '/cgi-bin/luci' ou '/web-static'. Se não, 'uh_slp_proto_request()' é chamada.

uh_slp_proto_request_entrypoint

Ao realizar um teste e enviar algumas solicitações para a câmera, podemos verificar que os dados utilizados por 'uh_slp_proto_request()' são dados POST padrão. Portanto, se enviarmos uma solicitação POST para '/' com a carga útil anterior, 'uh_slp_proto_request()' processará esses dados, chamará 'set_language()' e nossa carga útil será injetada no comando executado por 'exec_and_get_result()'.

Como podem ver, não mencionei nada sobre autenticação, pois a função 'setLanguage()' pode ser chamada sem fazer login. Isso permite que qualquer usuário assuma o controle total da câmera com uma única solicitação sem autenticação.

----[ Exploração ]----------------------------------

Agora, é hora de escrever o exploit. Dediquei um tempo para descobrir como obter uma shell reversa com netcat. Parecia simples, mas não conseguia fazê-la funcionar. Descobri que a versão do netcat instalada no BusyBox é bastante limitada em funcionalidade, então as shells reversas convencionais não eram válidas. No entanto, encontrei o que procurava no repositório PayloadsAllTheThings (como sempre) e obtive a shell reversa perfeita. Como o uhttpd é executado como root (graças à TP-Link), obtemos uma shell com privilégios máximos simplesmente enviando uma solicitação POST maliciosa. O exploit está disponível na página do GitHub: https://github.com/hacefresko/CVE-2021-4045/blob/master/pwntapo.py image

🚨 Lição aprendida:

  • Confundi https com http na porta 443, perdendo tempo até descobrir o erro.
  • Os serviços nas portas 2020, 554 e 8800 eram desconhecidos, o que exigiu investigação adicional.

🔓 Obtendo um Shell

Acesso ao console via UART

  1. Investigação prévia:
    • Encontrei um repositório do GitHub com informações sobre engenharia reversa do dispositivo.
    • Aprendi a usar um conversor USB para TTL para acessar a porta UART.
Baixar ferramenta