
Exploit para la vulnerabilidad de inyección de comandos encontrada en el binario uhttpd de la cámara IP TP-Link Tapo c200
CVE-2021-4045 es una vulnerabilidad de inyección de comandos que permite la ejecución remota de código en la cámara IP TP-Link Tapo c200. Afecta a todas las versiones de firmware anteriores a 1.1.16 Build 211209 Rel. 37726N debido a comprobaciones insuficientes de la entrada del usuario en uhttpd, uno de los binarios principales del dispositivo. El análisis completo se puede encontrar aquí.
Esta es una versión extendida del PoC realizada para mostrar la vulnerabilidad en la CryptoParty Madrid 2022. Tiene 2 modos:
shell: Abre una shell root en el objetivo

rtsp: Da acceso a la transmisión de video en vivo a través de RTSP

La función exec_and_read_json usa popen para ejecutar comandos:

exec_and_read_json es usada por 2 funciones anónimas, a las que llamé set_language y wifi_connect. Estas se encargan respectivamente de la configuración de idioma y wifi (obviamente). wifi_connect parece parsear las comillas simples ('); sin embargo, set_language no lo hace. Esto significa que si podemos controlar la entrada de la función set_language, podemos inyectar nuestros propios comandos con éxito.

La función set_language es usada por uh_slp_proto_request, la función que mencioné antes, la cual pasa como entrada algunos datos parseados recibidos del usuario.

Para parsear los datos del usuario, uh_slp_proto_request comprueba si se trata de un objeto JSON válido. Luego, obtiene un valor de cadena identificado por la clave "method" y un valor de diccionario identificado por "params" (al menos eso es lo que creo, ya que la llamada a la función no pudo resolverse con Ghidra, pero parecía funcionar de esta manera). Dependiendo del método seleccionado, uh_slp_proto_request selecciona la función que será llamada.
Por lo tanto, enviando {"method": "setLanguage", "params":{}} llamamos exitosamente a la función set_language y pasamos {} como parámetro language_json. Luego, dentro de set_language, el objeto language_json se convierte en cadena y se inserta directamente en "ubus call system_state_audio set_language \'%s\'" para ser ejecutado.
Al enviar {"method": "setLanguage", "params": {"payload": "'; touch poc;'"}}, se ejecutará ubus call system_state_audio set_language '{"payload": "'; touch poc;'"}', que en realidad contiene 3 comandos: ubus call system_state_audio set_language '{"payload": "', touch poc y '"}'. El segundo nos da ejecución completa de código.
Ahora, uh_slp_proto_request es usada por otra función anónima que gestiona todas las peticiones, a la que llamé main_server_function. Si una petición es válida (no supera la longitud máxima, usa http o https según la configuración del servidor, etc.), main_server_function comprueba si la URL contiene /cgi-bin/luci o /web-static. Si no es así, se llama a uh_slp_proto_request.

Adivinando y enviando un par de peticiones a la cámara, podemos comprobar que los datos usados por uh_slp_proto_request son datos POST normales. Entonces, si enviamos una petición POST a / con el payload anterior, uh_slp_proto_request procesará estos datos, llamará a set_language y nuestro payload será inyectado en el comando ejecutado por exec_and_get_result.
Como puedes ver, no mencioné nada sobre autenticación, ya que el método setLanguage puede ser llamado sin iniciar sesión. Esto permite que cualquier usuario tome el control total de la cámara con una sola petición no autenticada.