<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Artisan Numérique</title>
  <link rel="alternate" type="text/html" href="http://artisan.karma-lab.net/node/1250"/>
  <link rel="self" type="application/atom+xml" href="http://artisan.karma-lab.net/node/1250/atom/feed"/>
  <id>http://artisan.karma-lab.net/node/1250/atom/feed</id>
  <updated>2008-11-14T08:30:29+01:00</updated>
  <entry>
    <title> Débarrasser drupal du SPAM sans captchas (Suite)</title>
    <link rel="alternate" type="text/html" href="http://artisan.karma-lab.net/node/1250" />
    <id>http://artisan.karma-lab.net/node/1250</id>
    <published>2007-10-28T18:27:52+01:00</published>
    <updated>2008-11-14T08:30:29+01:00</updated>
    <author>
      <name>Ulhume</name>
    </author>
    <category term="Drupal" />
    <category term="drupalfr.org" />
    <category term="OK" />
    <category term="Planet Libre" />
    <category term="Tutoriel" />
    <summary type="html"><![CDATA[<p>
  Dans un billet précédent, j'abordais une méthode assez simple pour se passer des Captchas basée sur une évaluation du temps de réponse à un formulaire. Totalement orientée "serveur", elle avait le mérite de ne rien demander au client. En revanche cette méthode présente deux défauts. Tout d'abord elle nous oblige à désactiver le cache pour les pages à protéger ce qui est très contraignant en terme de charge serveur. A noter que c'est aussi le cas pour tous les modules captchas disponible à l'heure actuelle. Ensuite, certain spammeurs sont assez malins, ou assez lents, pour mettre plus des 10 secondes fatidiques à répondre au formulaire, et ainsi passer le filtre (environ 10%). 
</p>
<p>
  Pour améliorer le système, une nouvelle approche consiste à exploiter le même concept de champ caché, mais en utilisant cette fois un peu de javascript côté client pour lui donner sa "bonne" valeur. Aucune méthode n'est fiable à 100% mais celle-ci semble s'en approcher. 
</p>
    ]]></summary>
    <content type="html"><![CDATA[<p>
  Dans un billet précédent, j'abordais une méthode assez simple pour se passer des Captchas basée sur une évaluation du temps de réponse à un formulaire. Totalement orientée "serveur", elle avait le mérite de ne rien demander au client. En revanche cette méthode présente deux défauts. Tout d'abord elle nous oblige à désactiver le cache pour les pages à protéger ce qui est très contraignant en terme de charge serveur. A noter que c'est aussi le cas pour tous les modules captchas disponible à l'heure actuelle. Ensuite, certain spammeurs sont assez malins, ou assez lents, pour mettre plus des 10 secondes fatidiques à répondre au formulaire, et ainsi passer le filtre (environ 10%). 
</p>
<p>
  Pour améliorer le système, une nouvelle approche consiste à exploiter le même concept de champ caché, mais en utilisant cette fois un peu de javascript côté client pour lui donner sa "bonne" valeur. Aucune méthode n'est fiable à 100% mais celle-ci semble s'en approcher. 
</p>
<!--break-->

	<a name='chapter_1'></a>
  <h2>Bilan et analyse</h2>
	
<p>
  Tout d'abord un petit topo de ce que j'ai pu apprendre des spammers pendant cette semaine d'expérimentation :
<ul>
<li>
Il semble y avoir deux familles de spammeurs (regroupés dans une plus large famille dite de "chieurs"). Ceux qui savent quel CMS vous utilisez (Drupal, Wordpress, DotClear, etc.) et qui forgent des réponses spécifiques. Ceux là sont vite déroutés si l'on insère un nouveau champ non prévu en standard ou si l'on change les urls d'accès. La deuxième famille est plus complexe à gérer car elle se moque du type de CMS et analyse la page pour forger le "bon" formulaire.</li>
<li>Contrairement à la légende en circulation, de nombreux spammeurs connaissent javascript et exécute très bien tous les scripts d'une page. Encoder un formulaire avec des <kbd>document.write</kbd>, même avec cryptage ne sert absolument à rien. </li>
<li>Ces mêmes logiciels spammeurs savent suivre les scripts même s'ils sont dans des fichiers externes. Donc fragmenter un formulaire en plusieurs fichiers .js n'est d'aucune aide.</li>
</ul>
</p>
<p>
En revanche ce que ne semble pas savoir faire cette dernière famille à problèmes, c'est déclencher des événements javascript (onfocus, onkeyup,etc..). En effet, si l'on place par exemple un événement <kbd>onfocus</kbd> sur un champ et un autre sur un autre champ, le malheureux n'a aucun moyen de savoir lequel serait "piégé" et lequel rendrait le formulaire valide.
</p>
<p>
L'idée ici est donc de reprendre cet concept (merci Advaita <img src="http://artisan.karma-lab.net/sites/all/modules/contrib/smileys/packs/crystal/wink2.gif" title="Wink" alt="Wink" class="smiley-content"/> pour modifier le champ caché de notre précédent article et lui donner une valeur "magique" à ce champ sur déclenchement d'un événement utilisateur <kbd>onkeyup</kbd>, rendant ainsi valide le formulaire. 
</p>

	<a name='chapter_2'></a>
  <h2>Mise en œuvre</h2>
	
<p>
L'autre avantage de cette méthode est de ne plus avoir à désactiver le cache de page. En effet, il suffit de mettre dans le formulaire une valeur par défaut "fausse" et dans l'événement javascript la "bonne" pour le système fonctionne en cache ou pas. Dans le précédent exemple le time stamp devait être le bon à chaque visualisation et ne pouvait donc fonctionner avec un formulaire en cache. 
</p>
<p>
De même la méthode est suffisamment transparent pour ne plus l'appliquer à seulement certains formulaires. Plus la peine donc de prévoir une page de "settings" pour définir qui est protégé de qui ne l'est pas. Par défaut tout formulaire accédé anonymement est protégé. 
</p>
<p>
Comme pour la précédent méthode, la première chose à faire est d'ajouter un champ caché qui cette fois aura pour nom une valeur définie par une <kbd>define php</kbd>. Je vous conseille de choisir votre propre non de sorte à rendre le système le plus personnalisé possible. </p>
<p>
Pour commencer ce nouveau module, reprenez l'ancien, enlevez la fonction de hook sur <kbd>vilains_spammeurs_settings</kbd> et modifiez la fonction hook <kbd>vilains_spammeurs_form_alter</kbd> comme suit :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <a target="blank" href="http://www.php.net/define"><span class="kw3">define</span></a><span class="br0">&#40;</span><span class="st0">'COOKIE_NAME'</span><span class="sy0">,</span> <span class="st0">'biscuit'</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
<a target="blank" href="http://www.php.net/define"><span class="kw3">define</span></a><span class="br0">&#40;</span><span class="st0">'COOKIE_VALUE'</span><span class="sy0">,</span> <span class="st0">'magique'</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
<a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> vilains_spammeurs_form_alter<span class="br0">&#40;</span><span class="re0">$form_id</span><span class="sy0">,</span> <span class="sy0">&amp;</span> <span class="re0">$form</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/global"><span class="kw3">global</span></a> <span class="re0">$user</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$user</span><span class="sy0">-&gt;</span><span class="me1">uid</span> <span class="sy0">==</span> <span class="nu0">0</span><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re0">$timeStamp</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/time"><span class="kw3">time</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/error_log"><span class="kw3">error_log</span></a><span class="br0">&#40;</span><span class="st0">'SpamKiller - ALTER - '</span> <span class="sy0">.</span> <span class="re0">$_GET</span><span class="br0">&#91;</span><span class="st0">'q'</span><span class="br0">&#93;</span> <span class="sy0">.</span> <span class="st0">'/'</span> <span class="sy0">.</span> <span class="re0">$form_id</span> <span class="sy0">.</span> <span class="st0">' &nbsp;= '</span> <span class="sy0">.</span> COOKIE_NAME <span class="sy0">.</span> <span class="st0">'='</span> <span class="sy0">.</span> <span class="re0">$timeStamp</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <span class="re0">$form</span><span class="br0">&#91;</span>COOKIE_NAME<span class="br0">&#93;</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#type'</span> <span class="sy0">=&gt;</span> <span class="st0">'hidden'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#default_value'</span> <span class="sy0">=&gt;</span> <span class="re0">$timeStamp</span><br />
&nbsp; &nbsp; <span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <span class="re0">$form</span><span class="br0">&#91;</span><span class="st0">'#submit'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/array_merge"><span class="kw3">array_merge</span></a><span class="br0">&#40;</span><a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'vilains_spammeurs_validator'</span> <span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; <span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="st0">'#submit'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a><span class="sy0">;</span><br />
&nbsp; <span class="br0">&#125;</span><br />
<span class="br0">&#125;</span>
  </div>
  
  </div>
</p>
<p>
Comme vous le voyez, il n'y a pas grande différence avec la précédent procédure mise à par le nom du champ et la suppression de la ligne désactivant la mise en cache.
</p>
<p>
Ensuite, nous allons introduire deux nouvelles fonctions pour modifier à la volée les champs <kbd>textarea</kbd> et <kbd>textfield</kbd> et y insérer un événement javascript :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> phptemplate_textarea<span class="br0">&#40;</span><span class="re0">$element</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$result</span> <span class="sy0">=</span> theme_textarea<span class="br0">&#40;</span><span class="re0">$element</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <a target="blank" href="http://www.php.net/global"><span class="kw3">global</span></a> <span class="re0">$user</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$user</span><span class="sy0">-&gt;</span><span class="me1">uid</span><span class="sy0">==</span><span class="nu0">0</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; <span class="re0">$result</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/str_replace"><span class="kw3">str_replace</span></a><span class="br0">&#40;</span><span class="st0">&quot;&lt;textarea&quot;</span><span class="sy0">,</span> <span class="st0">'&lt;textarea onkeyup=&quot;this.form.'</span> <span class="sy0">.</span> COOKIE_NAME <span class="sy0">.</span> <span class="st0">'.value=<span class="es0">\'</span>'</span> <span class="sy0">.</span> COOKIE_VALUE <span class="sy0">.</span> <span class="st0">'<span class="es0">\'</span>;&quot; '</span><span class="sy0">,</span><span class="re0">$result</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a> <span class="re0">$result</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span><br />
<br />
<a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> phptemplate_textarea<span class="br0">&#40;</span><span class="re0">$element</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$result</span> <span class="sy0">=</span> theme_textarea<span class="br0">&#40;</span><span class="re0">$element</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <a target="blank" href="http://www.php.net/global"><span class="kw3">global</span></a> <span class="re0">$user</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$user</span><span class="sy0">-&gt;</span><span class="me1">uid</span><span class="sy0">==</span><span class="nu0">0</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; <span class="re0">$result</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/str_replace"><span class="kw3">str_replace</span></a><span class="br0">&#40;</span><span class="st0">&quot;&lt;input&quot;</span><span class="sy0">,</span> <span class="st0">'&lt;input onkeyup=&quot;this.form.'</span> <span class="sy0">.</span> COOKIE_NAME <span class="sy0">.</span> <span class="st0">'.value=<span class="es0">\'</span>'</span> <span class="sy0">.</span> COOKIE_VALUE <span class="sy0">.</span> <span class="st0">'<span class="es0">\'</span>;&quot; '</span><span class="sy0">,</span><span class="re0">$result</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a> <span class="re0">$result</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span>
  </div>
  
  </div>
</p>
<p>
La seule astuce ici consiste à utiliser les hooks assez peu connus phptemplate_xxx qui sont appelés pour skinner les éléments textarea et textfield. Heureusement pour nous le moteur de thème phptemplate ne les utilise pas. Leur rôle est d'effectuer la transformation standard (ne pas ré-inventer la roue) puis, si mode "anonyme", de rajouter un événement "onkeyup" qui va, lorsque déclenché, donner à notre champ caché sa vraie valeur. Ainsi l'appui d'une touche dans n'importe quel champ texte (commentaire, login, etc..) va valider. 
</p>
<p>
Il ne nous reste plus maintenant qu'à vérifier cette valeur en retour de formulaire comme dans le billet précédent en modifiant la procédure de validation comme suit :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> vilains_spammeurs_validator<span class="br0">&#40;</span><span class="re0">$form_id</span><span class="sy0">,</span> <span class="re0">$form_values</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$cookieValue</span> <span class="sy0">=</span> <span class="re0">$form_values</span><span class="br0">&#91;</span>COOKIE_NAME<span class="br0">&#93;</span><span class="sy0">;</span><br />
&nbsp; <span class="re0">$failed</span> <span class="sy0">=</span> <span class="re0">$cookieValue</span> <span class="sy0">!=</span> COOKIE_VALUE<span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/error_log"><span class="kw3">error_log</span></a><span class="br0">&#40;</span><span class="st0">'SpamKiller - SUBMIT - '</span> <span class="sy0">.</span> <span class="br0">&#40;</span><span class="re0">$failed</span> ? <span class="st0">'SPAM'</span> <span class="sy0">:</span> <span class="st0">'OK'</span><span class="br0">&#41;</span> <span class="sy0">.</span> <span class="st0">' - '</span> <span class="sy0">.</span> <span class="re0">$_GET</span><span class="br0">&#91;</span><span class="st0">'q'</span><span class="br0">&#93;</span> <span class="sy0">.</span> <span class="st0">'/'</span> <span class="sy0">.</span> <span class="re0">$form_id</span> <span class="sy0">.</span> <span class="st0">' : '</span> <span class="sy0">.</span> COOKIE_NAME <span class="sy0">.</span> <span class="st0">' = '</span> <span class="sy0">.</span> <span class="re0">$cookieValue</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$failed</span><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/error_log"><span class="kw3">error_log</span></a><span class="br0">&#40;</span><span class="st0">'Spam Content : '</span> <span class="sy0">.</span> <span class="re0">$form_values</span><span class="br0">&#91;</span><span class="st0">'comment'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; drupal_set_header<span class="br0">&#40;</span><span class="st0">'HTTP/1.1 666 Forbiden to spammers.'</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/print"><span class="kw3">print</span></a> <span class="st0">&quot;Ceci est sûrement un spam. Si c'est un erreur, veuillez contacter l'administrateur&quot;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/exit"><span class="kw3">exit</span></a> <span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <span class="br0">&#125;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a><span class="sy0">;</span><br />
<span class="br0">&#125;</span>
  </div>
  
  </div>
</p>
<p>
Voilà, c'est tout. Plus de spam, jusqu'à ce qu'ils trouvent une parade, bien évidement <img src="http://artisan.karma-lab.net/sites/all/modules/contrib/smileys/packs/crystal/wink2.gif" title="Wink" alt="Wink" class="smiley-content"/> Mais il est à noter que les spammeurs ont un net handicape par rapport à nous, s'ils jouent sur une masse, nous nous pouvons créer des cas particuliers... 
</p>
<p>
Pour ceux que cela intéresse, les sources sont disponible sur le dépôt subversion :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  svn <a target="blank" href="http://pwet.fr/man/linux/commandes/co"><span class="kw2">co</span></a> http:<span class="sy0">//</span>artisan.karma-lab.net<span class="sy0">/</span>software<span class="sy0">/</span>subversion<span class="sy0">/</span>spam_killer<span class="sy0">/</span>tags<span class="sy0">/</span>v0<span class="nu0">.2</span><span class="sy0">/</span>spam_killer
  </div>
  
  </div>
</p>
</p>    ]]></content>
  </entry>
</feed>
