L'anti-spam fastoche

On veut être web2.0, on veut de l'interaction, du commentaire, de la réponse et paf on se ramasse 150 messages vantant les mérites du Viagra ou je ne sais quel autre médicament faisant la fortune de laboratoires américains. Pourtant on ne veut pas utiliser de CAPTCHAs parce que ce n'est pas terrible niveau accessibilité, les formules mathématiques semblent séduisantes mais pas merveilleuses, et les webservices comme Akismet trop lourds… Alors quoi ?

Après avoir lu un article fort intéressant sur le blog du Snook j'ai écris une fonction assez simple et qui se révèle particulièrement efficace. Comme je suis drôlement sympa, je la partage avec vous.

Permis à point

Cette fonction utilise un système à points comme décrit dans l'article de Snook. Pour tout ce qui parait bien, des points sont accordés, et pour tout ce qui est moins bien des points sont retranchés. Plus c'est suspicieux et plus les points s'envolent. La meilleure note étant 4, ça tombe très vite :

  • le score est de 1 ou plus, on peut considérer le message comme valide.
  • le score est à zéro on peut suspecter un spam, mais rien d'alarmant, on le classera dans les messages à modérer.
  • le score est inférieur à zéro, il s'agit certainement d'un spam
  • le score est inférieur à 10, ce n'est même pas la peine de s'embêter à enregistrer ce message, il pue le spam.

Homme ou machine (nul n'imagine quel est son secret)

La fonction vise surtout les spams automatiques, elle ne pourra pas grand chose contre les spams écrits par de vilaines gens. Mais enfin, le spam humain ne représente pas grand chose vu le cout de la main d'œuvre, alors dormons -presque- tranquille.

<?php

function wd_spamScore($body$url$author$words=NULL$starters=NULL)
{
    #
    # score >= 1 - The message doesn't look like spam
    # score == 0 - The message should be put to moderation
    # score < -10 - The message is most certainly spam
    #
    
    $score = 0;
    
    #
    # put our body in lower case for checking
    #
    
    $body = strtolower($body);
    
    #
    # how many links are in the body ?
    #
    
    $n = max
    (
        array
        (
            substr_count($body'http://'),
            substr_count($body'href'),
            substr_count($body'ftp')
        )
    );

    if ($n > 2)
    {
        #
        # more than 2 : -1 point per link
        #
    
        $score -= $n;
    }
    else
    {
        #
        # less than 3 : +2 points
        #
        
        $score += 2;
    }

    #
    # now remove links
    #

    # html style: <a> <a/>

    $body = preg_replace('#\<a\s.+\<\/a\>#'NULL$body);

    # bb style: [url] [/url]

    $body = preg_replace('#\[url.+\/url\]#'NULL$body);

    # remaining addresses: http://

    $body = preg_replace('#http://[^\s]+#'NULL$body);

    #
    # how long is the body ?
    #
    
    $l = strlen($body);
    
    if ($l > 20 && $n = 0)
    {
        #
        # More than 20 characters and there's no links : +2 points
        #
        
        $score += 2;
    }
    else if ($l < 20)
    {
        #
        # Less than 20 characters : -1 point
        #
        
        $score--;
    }
    
    #
    # Keyword search
    #
    
    if (empty($words))
    {
        $words = array();
    }
    
    $words += array
    (
        'levitra''viagra''casino''free sex''porn'
    );
    
    foreach ($words as $word)
    {
        $n = substr_count($body$word);
        
        if (!$n)
        {
            continue;
        }
        
        $score -= $n;
    }
    
    #
    # URL length
    #
    
    if (strlen($url) > 32)
    {
        $score--;
    }
    
    #
    # Body starts with...
    #
    
    if (empty($starters))
    {
        $starters = array();
    }
    
    $starters += array
    (
        'interesting''sorry''nice''cool''hi'
    );
    
    foreach ($starters as $word)
    {
        $pos = strpos($body$word . ' ');
        
        if ($pos === false)
        {
            continue;
        }
        
        if ($pos > 10)
        {
            continue;
        }
    
        $score -= 10;
            
        break;
    }
    
    #
    # Author's name has 'http://' in it
    #
    
    if (strpos($author'http://'))
    {
        $score -= 2;
    }
    
    #
    # How many different words are used ?
    #
    
    $count = str_word_count($body);
    
    if ($count < 10)
    {
        $score -= 5;
    }
        
    return $score;
}

Comment je fais ? (en réponse à Hureau)

Avant de sauver un message dans notre base de données, on appelle la fonction et, en se référant au système à points, on décide de son sort. C'est tout simple, vraiment. Imaginons un formulaire avec les champs author, url et message:

<?php

$score = wd_spamScore($_POST['message']$_POST['url']$_POST['author']);

if ($score > 0)
{
    #
    # on sauve le message
    #
}
else if ($score == 0)
{
    #
    # on sauve le message avec un drapeau de modération
    #
}
else
{
    #
    # je ne m'ennuie même pas avec les scores négatifs,
    # je mets le message à la poubelle
    #
}

Aller plus loin

Cette fonction est très efficace, mais c'est pas le délire non plus. Le développeur sexy et ambitieux aura vite fait d'étendre la vérification avec une jolie base de donnée pour vérifier si les messages précédents de l'auteur ont été validés ou non, ou si le corps du message est déjà utilisé ailleurs… Mais enfin, si comme moi vous ne voyez plus l'ombre d'un vilain spam sur votre site pas la peine de se prendre la tête :-D

Laisser un commentaire

6 commentaires

Hureau
Hureau

Très intéressé étant régulièrement spammé. Mais étant newbie en php, je le mets où ce code par rapport à mon formulaire ??? D'avance merci Xavier Hureau

Olivier
Olivier

J'ai ajouté une section « Comment je fais ? ». J'espère qu'elle répond à ta question.

007
007

Encore un article intéressant par sa simplicité et son 'work around', j'adopte cette méthode très prochainement. Merci !

alphanono
alphanono

Hello … y'en a qui aiment se triturer les méninges !! Bon, ça semble intéressant. Reste à voir si l'attribution des points est bonne. Pour ma part, dans ce genre de formulaire je crée un champ texte que je masque avec du css. Les robots (ces cons !) remplissent immanquablement ce champ alors qu'un humain ne le voit même pas. Résultat, le formulaire n'est pas pollué par des CAPCHA avec tout pleins de calculs à faire ou des lettres à lire (pfiou … trop dur) et il suffit de vérifier si le champ spécial a été rempli ou non.

Martin
Martin

Cela suppose que le blog ne traite pas lui-même de sujets faisant partie des mots-clés bannis, ce qui pourrait déclencher de nombreux faux positifs qu'il faudrait savoir identifier et corriger. Bien trop de solutions anti-spam, notamment celles basées sur du JavaScript, déclenchent des faux positifs sans se soucier de les récupérer, ni même de les comptabiliser.

En ce qui me concerne, en matière d'anti-spam ultra-basique appliqué à mes blogs WordPress, je vire tous les commentaires contenant des liens au format bbcode, car de nombreux spammeurs spamment tout ce qui bouge avec des messages contenant toutes formes de liens et en bombardant avec le même message forums, blogs, digg, etc., ce qui les rend faciles à repérer.

Depuis 2004 que je blogue, aucun des commentaires « humains » que j'ai pu recevoir sur mes articles ne contenait un lien bbcode.

Olivier
Olivier

Les « spam » sont de plus en plus difficiles à déceler. Je ne trouve quasiment plus de messages avec trois millier de liens. Aujourd'hui les messages semblent tout ce qu'il y a de plus normaux, alors il faut bannir selon l'URL associée au message, et malheureusement cela demande plus de travail.