Skip to content

Contournement de Captcha : Math Captcha WordPress version 1.2.7

Aujourd’hui, nous allons étudier le plugin WordPress Math Captcha version 1.2.7 qui revendique 30 000 installations actives à la date d’écriture de cet article. D’après sa description, ce plugin est 100% efficace.

Rappelons à ce titre le rôle d’un captcha, d’après Wikipédia :

Description du Captcha de Wikipédia
Description du Captcha de Wikipédia

Un captcha est donc un test de Turing, qui vise à distinguer un ordinateur d’un humain. Dans le contexte d’un blog ou d’un site web, les captchas sont utilisés afin d’éviter que des robots ne remplissent les articles de commentaires faisant de la pub (entre autres). On test donc la robustesse d’un captcha en tentant de le faire résoudre par un programme informatique, si personne ne parvient à créer un script (ou robot) assez « intelligent » pour résoudre un captcha de façon autonome, alors le captcha peut être décrit comme efficace. En plus du captcha en lui même (le « problème » à résoudre), on peut également tester la logique de fonctionnement du plugin.

Bref, quelques informations sur notre cible du jour :

Étude du fonctionnement de la cible

On installe donc le module sur WordPress , celui-ci peut être configuré à partir du panneau d’administration suivant :

Panneau de configuration et d'administration de Math Captcha
Panneau de configuration et d’administration de Math Captcha

Pour donner un maximum de chance à notre cible, j’active toutes les options visant à augmenter la robustesse du captcha, notamment :

  • ajout des opérations de types multiplication et division en plus de l’addition et de la soustraction ;
  • le captcha pourra faire figurer des mots en plus des nombres (par exemple « 35 » ou « trente cinq »).

Je me rends ensuite sur un des articles de mon faux blog et constate la présence du plugin Math Captcha :

Exemple de captcha à résoudre
Exemple de captcha à résoudre

La première chose à faire est de constater la prévisibilité ou l’imprévisibilité du problème que notre robot va devoir résoudre. En rechargeant plusieurs fois la page, j’obtiens plusieurs types de problème :

Différents problèmes proposés par Math Captcha
Différents problèmes proposés par Math Captcha

On peut ici distinguer plusieurs éléments :

  • il s’agit d’une opération à trou avec une inconnue, constituée de deux opérandes ;
  • l’inconnue peut être située à n’importe quel opérande ;
  • l’opérateur est toujours connu (+, -, *, /) ;
  • les opérandes peuvent être en écrits en anglais ou en chiffre arabe (0,1,2,3,4,5,6,7,8,9) ;
  • après plusieurs dizaines de test, je m’aperçois que la réponse est quasi-systématiquement comprise entre 1 et 15, ce qui donne un éventail de possibilités très faible ;

A présent, jetons un œil à la façon dont notre réponse est envoyée au serveur :

Requête HTTP de soumission d'un commentaire avec présence de Math Captcha
Requête HTTP de soumission d’un commentaire avec présence de Math Captcha

On voit donc qu’un simple paramètre mc-value est présent, ainsi que plusieurs cookies nommés mc_session. Également, la réponse doit obligatoirement être envoyée en chiffre (0,1,2,3,…), en cas d’échec, l’utilisateur est redirigé vers une erreur 500 :

Exemple de message d'erreur lorsque l'utilisateur ne répond pas correctement au captcha
Exemple de message d’erreur lorsque l’utilisateur ne répond pas correctement au captcha

Nous avons suffisamment d’informations pour dessiner quelques vecteurs d’attaque à tester :

  • L’opération contient certains paramètres aléatoires (opérande à résoudre, chiffre écrit ou non, opérateur, etc.), mais rien d’insurmontable pour un script python de quelques dizaines de lignes.
  • L’éventail de réponses ne semble pas très élevé (entre 1 et 15), peut-on effectuer un brute force sur la réponse pour éviter d’avoir à résoudre l’opération ?
  • L’envoi des informations relatives au captcha est-il obligatoire ?

Je vous propose de nous pencher sur ces trois cas afin d’essayer de contourner ce captcha « 100% efficace ». Sans avoir encore essayé quoi que ce soit, on peut déjà deviner que le catpcha aura une robustesse très faible, mais la voyage est souvent plus intéressant que la destination finale.

Vecteur d’attaque 01 : le brute force

Pour tester rapidement et simplement ce cas de figure, je charge une page contenant un article et une section commentaire protégée par Math Captcha et je démarre BurpSuite (outil qui agira comme un Proxy entre mon navigateur et le serveur, afin d’intercepter/modifier/supprimer les requêtes et réponses HTTP).

Avant d’envoyer mon commentaire, j’active le mode « Intercept » de BurpSuite, celui-ci va alors intercepter chaque requête provenant de mon navigateur et me laisser de choix de laisser passer, modifier ou « droper » (annuler l’envoi) de chaque requête.

Interception de la requête de soumission d'un commentaire avec BurpSuite et son module Proxy
Interception de la requête de soumission d’un commentaire avec BurpSuite et son module Proxy

Je fait un clic droit « Send To Intruder » dans le corps du message pour tenter un brute force sur le paramètre mc-value. Dans BurpSuite, le module « Intruder » permet d’effectuer du fuzzing (tests à données aléatoires, en français) de l’énumération ou plus généralement du brute force sur une ou plusieurs requêtes. Il prend généralement en entrée une requête et une ou plusieurs charges utiles (payloads, sorte de liste contenant les éléments à tester). Pour une requête visée, on peut spécifier l’endroit ou insérer une données à brute forcer à l’aide des caractères « § » :

Paramétrage de la position de la charge utile dans le module Intruder de BurpSuite

On peut ensuite configurer la charge utile (liste de données à tester) dans l’onglet nommé « Payloads » :

Paramétrage de la charge utile dans le module Intruder de BurpSuite
Paramétrage de la charge utile dans le module Intruder de BurpSuite

Je choisi une charge utile (ou payload) de type « Number » et configure une suite de nombre à tester (de 1 à 20) Ici, l’Intruder va donc se charger de renvoyer exactement la même requête une vingtaine de fois, mais avec à chaque fois une données différente dans le paramètre mc-value, il s’agit donc d’un brute force du paramètre mc-value. On test un « grand » nombre de paramètres en espérant tomber sur le bon à un moment donné.

Une fois l’attaque terminée, on peut constater que l’une des données envoyées génère une réponse différente de la part du serveur :

Revue de l'attaque Brute Force et des détections d'une réponse différente
Revue de l’attaque Brute Force et des détections d’une réponse différente

Lors de la soumission de la valeur « 10 », le commentaire semble avoir été accepté, les autres tests ayant générés une erreur 500. Effectivement, côté panneau d’administration WordPress, on constate que le commentaire « TEST 02 » a bien été envoyé :

Affichage du commentaire soumis grâce au brute force
Affichage du commentaire soumis grâce au brute force

Notre premier vecteur d’attaque est donc concluant, il ne sert à rien de résoudre l’opération car il est plus rapide et facile de générer une vingtaine de requêtes pour tomber sur le bon résultat.

Pour le sport, nous allons tout de même tenter nos deux autres vecteurs d’attaque.

Vecteur d’attaque 02 : Le script Python « intelligent »

Le cas du script python est intéressant à mettre en place. Il permettra notamment d’avoir un cas de contournement toujours efficace même si la fourchette de possibilité de réponse passe de 0 à 1000 (elle est visiblement de 1 à 15 aujourd’hui). La conception d’un tel script est certainement la partie la plus intéressante à réaliser mais peut facilement prendre plusieurs heures (disons deux sessions d’une heure et demi sur deux soirées, en étant sur Twitter en même temps), un format difficilement présentable dans cet article, je vous montre directement la bête en action, qui ne fait que ~160 lignes en comptant les commentaires et les lignes vides :

Sans mettre le code source du script en ligne, voici les fonctions créées pour qu’il puisse résoudre les challenges de façon autonome comme exposé dans la vidéo :

  • getData(url): Récupère simplement le code source de la page ciblée en paramètre. Cette fonction utilise la librairie python requests.
  • htmlEntToClear(page): Le plugin ajoute une difficulté supplémentaire en écrivant des caractères en HTML entities (https://www.w3schools.com/html/html_entities.asp) et parfois en texte clair. Il faut donc convertir tous les caractères HTML entities de la zone ciblée en texte « clair », ce qui est le rôle de cette fonction qui utilise des expressions régulières (librairie python re) ainsi que la librairie python HTMLParser.
  • textToNum(text): Traduit tous les nombres écrits de façon textuelle en chiffre arabe (1,2,3,4,5,6,7,8,9,0) afin de faciliter la suite des opérations. L’uniformisation des caractères permet de traiter des données mathématiques uniquement, ce qui est beaucoup plus simple.
  • getOperande(html): permet de récupérer les opérandes, opérateur et résultat de l’opération soumise en challenge par le captcha, la valeur à remplir (l’inconnue) est indiquée par un « X ».
  • SolveOperation(opUn,op,opDeux,R): résout l’équation à une inconnue en utilisant les données passées en paramètre grâce à la librairie sympy et ses fonctions Symbol, Eq et solve.
  • sendRequest(D) : Fonction qui envoie la requête POST visant à poster un commentaire, D est ici le résultat calculé du captcha qui ira dans le paramètre mc-value.

Le code de la fonction main est ensuite le suivant :

while True :
 global s
 s = requests.session()
 challenge = getData("http://192.168.1.35/2018/02/16/hello-world/")
 challenge = htmlEntToClear(challenge)
 opUn, opDeux, op, R = getOperande(challenge)
 print(str(opUn)+str(op)+str(opDeux)+" = "+str(R))
 resultat = SolveOperation(opUn,op,opDeux,R)
 print ("Solution = "+str(resultat)+"\n" )
 sendRequest(resultat)

Il n’y a plus qu’à laisser tourner le tout dans un boucle infinie (une boucle infinie est une boucle qui ne connait pas de condition d’arrêt en programmation) afin de poster un nombre infinie de commentaire tout en résolvant chaque captcha.

Vecteur d’attaque 03 : Test du caractère obligatoire des paramètres du captcha

Un des tests les plus basiques à faire est de tester le caractère obligatoire du captcha lors de l’envoi d’une requête. Nous resterons ici dans le cadre d’une requête de soumission d’un commentaire. Il est en effet (très) fréquent que les mécanismes de sécurité (notamment web) puissent être contournés si l’on omet purement et simplement les paramètres les concernant de la requête POST.

Exemple : Pour soumettre une commentaire avec un captcha dûment rempli, la requête suivante est envoyée :

POST /wp-comments-post.php HTTP/1.1
 Host: 192.168.1.35
 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
 Accept-Language: en-US,en;q=0.5
 Accept-Encoding: gzip, deflate
 Referer: http://192.168.1.35/2018/02/16/hello-world/
 Cookie: mc_session_ids[default]=89d14e2e7ff0bdb4fddb065b174845992d84b37b; mc_session_ids[multi][0]=245df5e427bcda0977b2f46e5e4a93f8e08c1ad3; mc_session_ids[multi][1]=9f4a7b85b45b1fc1bd0069b6499a1bf256135c23; mc_session_ids[multi][2]=2b1552a4c3172ad66fabc57907ab22aafdac00f4; mc_session_ids[multi][3]=8e05029f4eedd72aaf3ba9e881f602e1c6ed17c8; mc_session_ids[multi][4]=2de1aba834f3f290049b0611febc5b8fd8dd8800; comment_author_f5e39257da510946066f41b32951c8da=AAA; comment_author_email_f5e39257da510946066f41b32951c8da=alfred01010202%40laposte.net
 Connection: close
 Upgrade-Insecure-Requests: 1
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 137
comment=TEST+BYPASS02&author=AAA&email=testtest%40laposte.net&url=&mc-value=8&submit=Post+Comment&comment_post_ID=1&comment_parent=0

On constate ici plusieurs éléments relatifs à la protection apportée par Math Captcha :

  • des cookies mc_session contenant des hashs ;
  • un paramètre mc-value envoyé en donnée POST contenant notre réponse

Si l’on oublie de répondre au captcha et que le paramètre mc-value contient une valeur vide, on constate l’affichage d’une erreur côté serveur qui refuse notre commentaire :

Refus de la soumission du commentaire lors de l'absence des cookies Math Captcha
Refus de la soumission du commentaire lors de l’absence des cookies Math Captcha

Logique me direz vous, quel intérêt de mettre en place un captcha si l’utilisateur (ou robot) qui oublie de le remplir se voit accepter sa requête. J’observe d’ailleurs le même résultat si je supprime de ma requête le différents cookies concernant math captcha, même si ma réponse est la bonne ou encore si je supprime le paramètre mc-value des paramètres envoyés en gardant les cookies mc-session.

Cependant, si je supprime de ma requête les cookies mc-captcha et le paramètre mc-value, celle-ci est acceptée par le serveur et mon commentaire est bien posté, démonstration de l’ensemble de la démarche en vidéo :

La logique du captcha est donc de tester la validité des différents paramètres (POST et cookie), mais pas leur absence commune. Un robot qui ignore complètement l’existence du captcha peut poster des commentaires librement.

Conclusion

Le caractère 100% efficace décrit dans la présentation peut clairement être remis en cause. Voici quelques leçons à tirer de cet article  :
Côté captcha/développeur :

  • Pensez à tester les plugins que vous insérez dans vos blogs, ou au moins vérifier les éléments les plus basiques de sécurité, une belle présentation et des retours/notes (potentiellement fausses) ne suffisent pas à garantir la sécurité d’un plugin.
  • La conception d’un captcha n’est pas chose facile, utiliser les mécanismes robustes et éprouvés demeurent une meilleure solution que de ré-inventer la roue (la plupart du temps), la solution Re-captcha de Google reste la plus efficace.
  • Un captcha est censé pauser une colle à un robot, c’est à dire à un mécanisme qui possède une intelligence limitée, incapable de s’adapter. Résoudre des opérations mathématiques étant le passe-temps favori d’un ordinateur, il ne semble pas logique de proposer un tel challenge pour distinguer humains et robots. Les éléments visuels sont en général beaucoup plus complexes à appréhender pour les robots que pour les humains, la reconnaissance d’image reste donc le captcha le plus efficace (pour le moment).

Côté attaquant :

  • Lorsqu’un formulaire est protégé par un captcha et que celui-ci n’est pas le captcha Google, il est généralement toujours intéressent d’y jeter un œil.
  • Analyser toutes les possibilités et types de problème à résoudre proposés par un captcha aide à comprendre sa difficulté de résolution et ainsi à envisager des scénarios peu couteux permettant de les contourner ou résoudre automatiquement.
  • Outre les captchas, je vous recommande de souvent tester la suppression des paramètres dans un requête, notamment pour les token anti-CSRF, vous allez être surpris du résultat
Partager :
Published inAdvisory et ContributionsAttaques

Be First to Comment

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *