SCOP d'ingénieurs experts du logiciel libre depuis 2004
+33 1 70 61 60 16

Les captchas sont pénibles

Les spammers le sont aussi, pénibles, clairement, tout comme les 0.1% de débiles qui cliquent sur les liens pourris justifiant leur existence.

Notre prochain webinar

Les spammers le sont aussi, pénibles, clairement, tout comme les 0.1% de débiles qui cliquent sur les liens pourris justifiant leur existence. D'ailleurs, je ne sais pas à quel point les spams dans les commentaires arrivent à gruger l'indexation de Google. Mais j'insiste, les captchas sont pénibles.

Parmi les alternatives proposées, il existe les captchas en mode texte, qui évitent de se casser les yeux.

C'est gentil, mais ça ne protège pas grand-chose. C'est même très pédagogique de proposer ce genre de challenge. J'en ai pris un que j'ai vu passer, qui propose des devinettes mathématiques, niveau fin de primaire. "Un + _ = 3", ce genre de challenge. Voilà, un four à micro-ondes a plus de CPU qu'il n'en a fallu pour envoyer un homme sur la Lune, et mon ordinateur n'arriverait pas à répondre à ce genre de question ?

Bon, comme chaque fois que l'on pense avoir découvert quelque chose sur Internet, quelqu'un l'a déjà fait avant, mieux, et plus joli. De toute façon, vu le temps que ça a pris pour résoudre le puzzle, le mérite est faible. Ce n'est pas avec ce genre de code que l'on peut postuler à la NSA.

Pour rester dans le pédagogique, le code est en Python, et s'appuie sur des bibliothèques tierces éprouvées, pour traiter les soucis d'intendances. Request pour gérer la discussion HTTP avec sa victime. Pyquery pour lire le HTML. On peut faire autrement et souffrir. De plus, ce sont deux paquets Debian.

#!/usr/bin/env python #encoding: utf8 import requests import re from pyquery import PyQuery as pq import sys 

Lire des nombres en toutes lettres est un peu laborieux, mais tout à fait faisable. De toute façon le captcha a un peu la flemme, il parle de "soixante-dix deux", plutôt que l'élégant soixante-douze.

SPACES = re.compile("\s+") NUMBERS = u"zéro un deux trois quatre cinq six sept huit neuf dix onze douze treize quatorze quinze seize dix-sept dix-huit dix-neuf vingt".split(u" ") TENS = u"dix vingt trente quarante cinquante soixante soixante-dix quatre-vingt quatre-vingt-dix cent".split(u" ") OPERATORS = u"+ − ×".split(u" ") ACTION = { u"+": "+", u"−": "-", u"×": "*"} INVERSE = { u"+": "-", u"−": "+", u"×": "/"} def maybe_int(stuff): if stuff.isdigit(): return int(stuff) if stuff in TENS: return (TENS.index(stuff) + 1) * 10 if stuff in NUMBERS: return NUMBERS.index(stuff) return stuff 

On teste 5 fois l'URL, passée comme argument, on vérifie que le serveur répond bien.

for i in range(5): r = requests.get(sys.argv[1]) assert r.status_code == 200 

Pyquery est un hommage à Jquery, et il va aller chercher le bloc dont la class est "cptch_block"

 d = pq(r.text) bloc = d('.cptch_block') 

Maintenant, un peu de NLP, natural language processing. Les phrases sont découpées en jetons (en mots, quoi), et on essaye d'en faire des nombres. Les opérateurs sont en UTF8, pour faire plus joli.

Comme j'ai eu la flemme de traiter la position du "input" HTML, je bataille pour gérer le trou dans le motif "A opérateur B = C".

 poz = 0 poz_operator = None poz_equal = None operator = None values = [] tokens = SPACES.split(bloc.text()) previous_is_int = False for token in tokens: token = maybe_int(token) if type(token) == int: if previous_is_int: values[-1] += token else: values.append(token) elif token in OPERATORS: poz_operator = poz operator = token elif token == u"=": poz_equal = poz else: raise Exception("Unmanaged token: %s" % token) previous_is_int = type(token) == int poz += 1 
Il faut maintenant faire un peu de math. Coup de bol, python sait traiter des opérations simples, et eval a ici un usage légitime. "eval is evil"™ L'autre personne ayant fait un code similaire résoud le problème mathématique en brut force, il essaye de remplacer le trou par un nombre entre 1 et 1000, jusqu'à ce que ça passe.
 answer = None if poz_equal == 3: # 4 * 2 = X answer = eval("%i %s %i" % (values[0], ACTION[operator], values[1])) elif poz_operator == 0: # x * 2 = 4 answer = eval("%i %s %i" % (values[1], INVERSE[operator], values[0])) elif poz_operator == 1: # 2 * x = 4 if operator == u"−": answer = eval("%i - %i" % (values[0], values[1])) else: answer = eval("%i %s %i" % (values[1], INVERSE[operator], values[0])) else: raise Exception() print u" ".join(tokens), " => ", answer 

Ensuite, il faut faire la partie qui remplit le formulaire et qui poste des messages à caractères publicitaires. Mais j'ai la flemme, et je n'ai pas grand-chose à spammer, en fait.

Le résultat va ressembler à ça :

 − deux = 4 => 6 + sept = 9 => 2 neuf + = 14 => 5 neuf − 6 = => 3 + huit = 12 => 4 

Mathieu Lecarme

Inscrivez-vous à notre newsletter

Mieux comprendre le monde du DevOps et de l'administration système.

Abonnez-vous à notre newsletter

Hébergement & Infogérance

  • ✓ Service Astreinte 24h/7j/365
  • ✓ Supervision, monitoring & Alertes
  • ✓ Mises à jour en continu
  • ✓ Certificat SSL letsencrypt
  • ✓ Hébergement dédié sécurisé en France
  • ✓ Backup vers datacenter distant
Découvrir notre offre

Expertise Technologique

Notre équipe possède une vaste expertise technologique.