
Exploit pour une vulnérabilité d'injection de commandes trouvée dans le binaire uhttpd de la caméra IP TP-Link Tapo c200
CVE-2021-4045 est une vulnérabilité d'injection de commandes qui permet l'exécution de code à distance sur la caméra IP TP-Link Tapo c200. Elle affecte toutes les versions de firmware antérieures à 1.1.16 Build 211209 Rel. 37726N en raison de contrôles insuffisants sur l'entrée utilisateur dans uhttpd, qui est l'un des principaux binaires de l'appareil. L'article complet peut être trouvé ici.
Ceci est une version étendue du PoC réalisée pour montrer la vulnérabilité lors de la CryptoParty Madrid 2022. Elle dispose de 2 modes :
shell : Lance un shell root sur la cible

rtsp : Donne accès au flux vidéo en direct via RTSP

La fonction exec_and_read_json utilise popen pour exécuter des commandes :

exec_and_read_json est utilisée par 2 fonctions sans nom, que j'ai nommées set_language et wifi_connect. Elles traitent respectivement de la configuration de la langue et du wifi (évidemment). wifi_connect semble analyser les guillemets simples ('), mais pas set_language. Cela signifie que si nous pouvons contrôler l'entrée de la fonction set_language, nous pouvons injecter nos propres commandes avec succès.

La fonction set_language est utilisée par uh_slp_proto_request, la fonction que j'ai mentionnée précédemment, qui transmet en entrée des données analysées reçues de l'utilisateur.

Pour analyser les données utilisateur, uh_slp_proto_request vérifie s'il s'agit d'un objet JSON valide. Ensuite, il récupère une valeur de chaîne identifiée par la clé "method" et une valeur de dictionnaire identifiée par "params" (du moins c'est ce que je pense, car l'appel de fonction n'a pas pu être résolu par Ghidra mais semblait fonctionner ainsi). Selon la méthode sélectionnée, uh_slp_proto_request choisit la fonction qui sera appelée.
Ainsi, en envoyant {"method": "setLanguage", "params":{}} nous appelons avec succès la fonction set_language et passons {} comme paramètre language_json. Ensuite, à l'intérieur de set_language, l'objet language_json est converti en chaîne et inséré directement dans "ubus call system_state_audio set_language \'%s\'" pour être exécuté.
En soumettant {"method": "setLanguage", "params": {"payload": "'; touch poc;'"}}, la commande ubus call system_state_audio set_language '{"payload": "'; touch poc;'"}' sera exécutée, qui contient en réalité 3 commandes : ubus call system_state_audio set_language '{"payload": "', touch poc, et '"}'. La deuxième nous donne une exécution de code complète.
Maintenant, uh_slp_proto_request est utilisée par une autre fonction sans nom gérant toutes les requêtes, que j'ai nommée main_server_function. Si une requête est valide (ne dépasse pas la longueur maximale, utilise http ou https selon la configuration du serveur, etc.), main_server_function vérifie si l'URL contient /cgi-bin/luci ou /web-static. Si ce n'est pas le cas, uh_slp_proto_request est appelée.

En devinant et en envoyant quelques requêtes à la caméra, nous pouvons vérifier que les données utilisées par uh_slp_proto_request sont des données POST classiques. Ainsi, si nous envoyons une requête POST à / avec la charge utile précédente, uh_slp_proto_request traitera ces données, appellera set_language et notre charge utile sera injectée dans la commande exécutée par exec_and_get_result.
Comme vous pouvez le voir, je n'ai rien mentionné à propos de l'authentification, car la méthode setLanguage peut être appelée sans connexion. Cela permet à n'importe quel utilisateur de prendre le contrôle total de la caméra avec une seule requête non authentifiée.