Bonjour, probablement beaucoup ont entendu parler de la récente vulnérabilité des routeurs Mikrotik, qui vous permet d'extraire les mots de passe de tous les utilisateurs. Dans cet article, je voudrais montrer et analyser en détail l'essence de cette vulnérabilité.
Tout le matériel est fourni à titre informatif uniquement, donc le code qui exploite la vulnérabilité ne sera pas ici. Si vous n'êtes pas intéressé à en savoir plus sur les causes et la structure interne d'une vulnérabilité particulière, vous pouvez continuer à lire.
Commençons
La première chose à commencer est l'analyse du trafic entre le client Winbox et l'appareil
Winbox est une application pour le système d'exploitation WIndows, qui répète exactement l'interface Web et est conçue pour administrer et configurer l'appareil avec le système d'exploitation du routeur à bord. 2 modes de fonctionnement pris en charge, TCP et UDP
Avant de commencer, vous devez désactiver le chiffrement du trafic dans Winbox. Cela se fait comme suit: vous devez activer la case à cocher
Outils ->
Mode avancé . Après cela, l'interface changera comme suit:
Décochez le
mode sécurisé . Lancez Wireshark et essayez de vous connecter à l'appareil:
Comme vous pouvez le voir ci-dessous, après autorisation, un fichier de
liste est demandé puis son contenu nous est complètement transféré, il peut sembler que tout va bien, mais regardons le tout début de cette session:
Au tout début, Winbox envoie exactement le même package en demandant le fichier de
liste :
Considérez sa structure:
- 37010035 - taille de l'emballage
- M2 est une constante indiquant le début d'un paquet
- 0500ff01 - variable 0xff0005 dans la valeur True
- 0600ff09 01 - variable 0xff0006 dans la valeur 1 (nombre de paquets transmis)
- 0700ff09 07 - variable 0xff0007 à la valeur 7 (ouvrir le fichier en mode lecture)
- 01000021 04 6967374 - liste de chaînes de la variable 0x01000001 de 4 octets (généralement cette variable est responsable du nom du fichier)
- 0200ff88 02 ... 00 - un tableau de 0xff0002 avec une taille de 2 éléments
- 0100ff88 02 ... 00 - un tableau de 0xff0001 avec une taille de 2 éléments
Du fait de l'inversion du protocole et des fichiers binaires correspondants côté client et serveur, il a été possible de restaurer et de mieux comprendre la structure du protocole par lequel Winbox communique avec le périphérique.
Description du protocole NvMessageTypes de champs (nom: désignation numérique)
- u32: 0x08000000
- u32_array: 0x88000000
- chaîne: 0x20000000
- tableau_chaînes: 0xA0000000
- addr6: 0x18000000
- addr6_array: 0x98000000
- u64: 0x10000000
- u64_array: 0x90000000
- vrai: 0x00000000
- false: 0x01000000
- bool_array: 0x80000000
- message: 0x28000000
- tableau_messages: 0xA8000000
- brut: 0x30000000
- raw_array: 0xB0000000
- u8: 0x09000000
- be32_array: 0x88000000
Types d'erreurs (nom: désignation numérique)
- SYS_TO: 0xFF0001
- STD_UNDOID: 0xFE0006
- STD_DESCR: 0xFE0009
- STD_FINISHED: 0xFE000B
- STD_DYNAMIC: 0xFE0007
- STD_INACTIVE: 0xFE0008
- STD_GETALLID: 0xFE0003
- STD_GETALLNO: 0xFE0004
- STD_NEXTID: 0xFE0005
- STD_ID: 0xFE0001
- STD_OBJS: 0xFE0002
- SYS_ERRNO: 0xFF0008
- SYS_POLICY: 0xFF000B
- SYS_CTRL_ARG: 0xFF000F
- SYS_RADDR6: 0xFF0013
- SYS_CTRL: 0xFF000D
- SYS_ERRSTR: 0xFF0009
- SYS_USER: 0xFF000A
- SYS_STATUS: 0xFF0004
- SYS_FROM: 0xFF0002
- SYS_TYPE: 0xFF0003
- SYS_REQID: 0xFF0006
Valeurs d'erreur (nom: désignation numérique)
- ERROR_FAILED: 0xFE0006
- ERROR_TOOBIG: 0xFE000A
- ERROR_EXISTS: 0xFE0007
- ERROR_NOTALLOWED: 0xFE0009
- ERROR_BUSY: 0xFE000C
- ERROR_UNknown: 0xFE0001
- ERROR_BRKPATH: 0xFE0002
- ERROR_UNknownID: 0xFE0004
- ERROR_UNKNOWNNEXTID: 0xFE000B
- ERROR_TIMEOUT: 0xFE000D
- ERROR_TOOMUCH: 0xFE000E
- ERROR_NOTIMP: 0xFE0003
- ERROR_MISSING: 0xFE0005
- STATUS_OK: 0x01
- STATUS_ERROR: 0x02
Structure des champs dans un package
Au début de n'importe quel champ se trouve son type - 4 octets (3 octets - le but de la variable, plus sur plus tard, 1 octet - directement le type de cette variable), puis la longueur est de 1 à 2 octets et la valeur elle-même.
Tableaux
Au figuré, le tableau peut être décrit par la structure suivante:
struct Array { uint32 type; uint8 count; uint32 item1; uint32 item2; ... uint8 zero; }
Type (4 octets) / Nombre d'éléments (1 octet) / Éléments (4 octets) / À la fin de l'octet \ x00
Lignes
Les chaînes ne sont pas terminées par null, mais ont une longueur clairement définie:
struct String { uint32 type; uint8 length; char text[length]; }
Les chiffres
Le type le plus simple du package, il peut être représenté comme un type de valeur:
struct u* { uint32 type; uint8/32/64 value; }
Selon le type, la valeur a une dimension de bit correspondante.
Type booléen
La taille du champ est de 4 octets, l'octet haut est responsable de la valeur (True \ False), les 3 octets inférieurs sont pour affecter la variable
De plus, chaque package contient:
- marqueurs spéciaux pour indiquer le début du colis
- taille de l'emballage
- grands marchés de contrôle des colis
Constantes trouvées
- 0xfe0001 - Contient l'identifiant de session (1 octet)
- 0xff0006 - Numéro du paquet envoyé (1 octet)
- 0xff0007 - Mode d'accès aux fichiers (1 octet)
Modes d'accès aux fichiers
- 7 - ouvert à la lecture
- 1 - ouvert pour l'enregistrement
- 6 - créer un répertoire
- 4 - lire
- 5 - supprimer
Maintenant, sachant comment fonctionne le protocole, nous pouvons générer de manière aléatoire les packages dont nous avons besoin et regarder comment le périphérique y répond.
Côté périphérique, l'exécutable
/ nova / bin / mproxy est responsable du traitement des paquets. Étant donné que les noms des fonctions n'ont pas été enregistrés, j'ai appelé une fonction qui traite le package et prend des décisions sur ce qu'il faut faire avec le fichier
file_handler () . Jetez un œil à la fonction elle-même:
PS Le code qui nous intéressera est marqué de flèches.
Étape 1
Lors de la réception d'un package pour ouvrir un fichier en lecture, il démarre le traitement à partir de ce bloc:
Au tout début, le nom du fichier est extrait du package en utilisant la fonction
nv :: message :: get <nv :: string_id> () .
Ensuite, la fonction
tokenize () divise la chaîne résultante en parties distinctes, en utilisant le caractère "
/ " comme délimiteur.
Le tableau de chaînes résultant est transmis à la fonction
path_filter () , qui vérifie la présence de "
.. " dans le tableau de chaînes reçu et, en cas d'erreur, renvoie une erreur
ERROR_NOTALLOWED (0xFE0009)PS ERROR_NOTALLOWED sera également reçu dans la réponse s'il n'y a aucune autorisation de fichier
Si tout va bien, le chemin vers le
répertoire webfig ou
pckg est concaténé au début du nom de fichier
Étape 2
Si tout s'est bien passé, le fichier s'ouvre et sa poignée est enregistrée dans l'objet global.
Si le fichier n'a pas pu être ouvert, alors dans la réponse nous obtenons une erreur:
impossible d'ouvrir le fichier source .
Ainsi, pour recevoir le contenu d'un fichier, 3 conditions doivent être remplies:
- Le chemin du fichier ne contient pas " .. ";
- Il existe des droits d'accès au fichier;
- Le fichier existe et peut être ouvert avec succès.
Essayons maintenant d'envoyer des packages pour tester la fonctionnalité de cette fonction:
$ ./untitled.py -t 192.168.88.1 -f /etc/passwd Error: SYS_ERRNO => ERROR_FAILED Error: SYS_ERRSTR => cannot open source file $ ./untitled.py -t 192.168.88.1 -f /../../../etc/passwd Error: SYS_ERRNO => ERROR_NOTALLOWED $ ./untitled.py -t 192.168.88.1 -f //./././././../etc/passwd Error: SYS_ERRNO => ERROR_FAILED Error: SYS_ERRSTR => cannot open source file
Alors! Mais c'est déjà étrange ... Nous nous souvenons que
ERROR_NOTALLOWED apparaît si la vérification dans
path_filter () n'a pas réussi, sinon nous
recevrions toujours un message sur le manque de droits d'accès, mais dans ce dernier cas, il s'avère que le fichier a été recherché dans le répertoire de niveau supérieur?
Essayons de cette façon:
$ ./untitled.py -t 192.168.88.1 -f //./.././.././../etc/passwd xvM2 1Enobody:*:99:99:nobody:/tmp:/bin/sh root::0:0:root:/home/root:/bin/sh
Et ça a marché. Mais pourquoi? Jetons un œil au code de la fonction
path_filter () :
Le code montre clairement que la recherche de l'occurrence de "
.. " dans le tableau de chaînes résultant se produit réellement. Mais alors la partie la plus intéressante, j'ai mis en évidence ce fragment en rouge.
L'essence de ce code est la suivante:
si l'élément précédent est également " .. ", la vérification est considérée comme ayant échoué. Sinon, considérez que tout va bien.
C'est-à-dire pour que tout fonctionne, il vous suffit d'alterner "
/./ " et "
/../ " pour naviguer avec succès dans tous les répertoires et descendre à n'importe quel niveau du FS.
Voyons comment les développeurs de Mikrotik l'ont corrigé:
Comparaison de codes pseudo-C Maintenant, la sortie du cycle de vérification se produit lors de la première détection de "
.. ". Certes, il n'est pas tout à fait clair pour moi pourquoi ils ont ajouté un chèque pour l'entrée d'un point. Et en raison d'un changement dans le mécanisme d'activation de l'utilisateur
devel , malheureusement, il n'y a aucun moyen de voir cela dans la dynamique.
Pour résumer
- Le système d'exploitation du routeur traite les paquets entrants sans problème avant même l'autorisation de l'utilisateur
- En raison d'un filtre incorrect, nous avons accès à n'importe quel fichier
Compte tenu des paragraphes précédents, nous pouvons facilement: créer, supprimer, lire et écrire des fichiers, ainsi que créer des répertoires arbitraires
Il n'est donc pas surprenant que d'avoir accès à tous les fichiers sans autorisation, la première chose qui a été faite a été de lire le fichier avec des mots de passe utilisateur. Heureusement, le réseau dispose de nombreuses informations sur son emplacement et sur la manière d'en extraire des données.
De plus, cette vulnérabilité peut être un excellent remplacement pour la possibilité précédemment connue d'activer le mode développeur, car vous n'avez pas besoin de redémarrer l'appareil,
sauvegardez /
restaurez le fichier de configuration maintenant.