samedi, janvier 4 2014

Statistics about blog comments spam

A little less than one year ago, I drew a few statistics on the spam I was getting on this blog. Since then, the number of spammy comments continued to increase as shown in the graph below:

image

The email regex rule documented in the previous blog post (called in this graph “dcFilterAlexAntispam) completes the other anti-spam measures and helps catching 100% (!) of all unwanted comments so far. This obviously raises additional questions, as it hints that one operator or software is behind all this spam.

Due to this single software in usage, can we easily identify its source? Let’s break down the collected IP address to their /8 mask and see if we find any obvious source:

image

A few networks come out but we are far from the Pareto rule where we could attempt to eliminate 80% of the spam by blacklisting 20% of the offending IP addresses.

But maybe we can correlate IP addresses to countries or providers? Let’s use for this the IP to AS service of Team Cymru. But first, let’s aggregate the IP addresses to avoid sending duplicates or multiple IP addresses within the same /24 range. Of the little less than 20’000 entries, 6844 unique /24 IP addresses were identified. Let’s save this list in a text document, insert keywords “begin” and “verbose” at the start of the document and insert “end” at the end of it before invoking the whois based conversion service:

$ netcat whois.cymru.com 43 < asm_requests.txt | sort -n > asm_responses.txt

During the import in your preferred spreadsheet, don’t forget to trim away the various whitespaces of the fields. The outcome is pretty interesting, as some countries emerge from the statistic:

image

Drilling down to the top 10 countries, we get the following representation:

image

While some countries are consistently spamming a lot over the year, other have peaks – e.g. Germany in the third trimester of 2013 or Sweden in first trimester of 2013.

There are of course some limitations with this evaluation. First of all, the link between IP address and AS ownership was established today, while some of the IP addresses were recorded over a year ago. This IP address might have been owned by a less reputable source than now and thus induce a bias. Furthermore, some AS might register themselves where their headquarters is, despite being located all over the world. This could help explain  the predominance of e.g. the US in this statistic.

lundi, février 18 2013

High increase in spammy blog comments

As already stated, having SPAMs in comments isn’t unfortunately uncommon, but the volume handled on this blog severely increased around Christmas 2012.

As shown in the graph below, the increase started on December 2012 with a little less than half of the comments being unrecognised by any DotClear spam filter (value in light blue – legend NULL):

image

Beginning of January, I attempted to put as many IP address blocks in the blacklist, as well as filter more aggressively on unwanted keywords, unfortunately with limited results. The situation increased dramatically once I implemented a custom spam filter based on the following observations:

  • IP address ranges were very distributed and while some reoccurrence could be seen, less than half of the spams were caught by this list
  • The text seems to be composed on highly adaptable templates, where you could not blacklist given words, e.g.
    “{Hello|Hi} there, {simply|just} {turned into|became|was|become|changed into} {aware of|alert to} your {blog|weblog} {thru|through|via} Google, {and found|and located} that {it is|it's} {really|truly} informative. {I'm|I am} {gonna|going to} {watch out|be careful} for brussels. {I will|I'll} {appreciate|be grateful} {if you|should you|when you|in the event you|in case you|for those who|if you happen to} {continue|proceed} this {in future}. […]”
  • The review of the Apache logs did not yield any further distinctive keyword (e.g. in the user-agent).
  • The only interesting field was the provided email, almost always following the following pattern: “Word 1 starting with capital letter” + “Word 2 starting with capital letter” + “number  between 10 and 9999” (at) “a small list of predefined major free email providers”, e.g. MailletQuijas95@yahoomail.com

This last point is exactly the logic which got implemented in dcCustomSpamFilter with the following regular expression and a great success rate:

    public $regexEmail = '([A-Z][a-z]+){2}([0-9]{2,4})@(123mail\.net|aol\.com|googlemail\.com|gnumail\.com|yahoomail\.com|hotmail\.com|mail\.com|gmail\.com|aim\.com)';

The whole code for this custom DotClear spam filter is below and was placed in a newly created folder [DotClearRoot]/plugins/custom_antispam/:

_define.php

<?php
if (!defined('DC_RC_PATH')) { return; }
 
$this->registerModule(
    /* Name */            "Custom_antispam",
    /* Description*/        "Custom Anti Spam Filter",
    /* Author */            "www.ness.ch/misc/",
    /* Version */            '0.1',
    /* Permissions */        'usage,contentadmin',
    /* Priority */            200
);
?>

_prepend.php

<?php
if (!defined('DC_RC_PATH')) { return; }
 
global $__autoload, $core;
$__autoload['dcCustomSpamFilter'] = dirname(__FILE__).'/class.dc.filter.custom.antispam.php';
$core->spamfilters[] = 'dcCustomSpamFilter';
?>

class.dc.filter.custom.antispam.php

<?php   
//Source: http://fr.dotclear.org/documentation/2.0/resources/plugins/antispam

class dcCustomSpamFilter extends dcSpamFilter
{
    public $name = Custom anti spam Filter';
    public $has_gui = false;
    public $regexEmail = '([A-Z][a-z]+){2}([0-9]{2,4})@(123mail\.net|aol\.com|googlemail\.com|gnumail\.com|yahoomail\.com|hotmail\.com|mail\.com|gmail\.com|aim\.com)';
 
    protected function setInfo()
    {
        $this->description = __('My custom anti spam filter');
    }

   
    /*
Cette méthode prend les paramètres suivants :

$type : le type de commentaire (comment ou trackback)
$author : le nom de l'auteur
$email : l'adresse email de l'auteur
$site : l'URL du site de l'auteur
$ip : l'adresse IP de l'auteur
$content : le contenu du commentaire
$post_id : l'ID du billet sur lequel le commentaire a été posté
La dernière variable $status doit bien être déclarée en référence (&$status) puisqu'elle permet de transmettre le statut du commentaire si celui-ci est marqué comme spam.

Cette méthode doit renvoyer true si le message est un spam et null si on ne sait pas.   
    */
   
    public function isSpam($type,$author,$email,$site,$ip, $content,$post_id,&$status)
    {
        if (preg_match('/'.$regexEmail.'/',$email)) {
            $status = 'Filtered';
            return true;
        }
    }
   
    public function getStatusMessage($status,$comment_id)
    {
        return sprintf(__('Filtered by %s. - generated email match'),$this->guiLink());
    }
}
?>

jeudi, août 18 2011

Comment exporter le contenu de son blog DotClear sur Blurb afin d'en faire un album photo

Le but de cet article est de détailler les étapes nécessaires à pouvoir importer un maximum de contenu provenant d’un blog DotClear dans Blurb afin d’en faire un livre. Les plus intéressés peuvent lire de plus amples informations dans les deux articles précédemment écrits.

Pré-requis

Configuration de DotClear

Afin de respecter les bonnes pratiques de sécurité, nous allons créer un compte utilisateur dans DotClear ayant juste les permissions requises pour l’export des billets. Après avoir crée le compte blurb, on lui attribue la permission “gérer tous les billets et commentaires” pour le blog à exporter.

image

Dans les paramètres du blog, assurez-vous tout en bas de la page que l’interface XML-RPC est bien activée. Notez également l’URL du serveur.

image

2. Mise en place sur le poste de travail

Je pars de l’idée que Blurb est installé sur l’ordinateur et que les paramètres de proxy de la JRE utilisée par Blurb ont été édités comme suit:

  • http.proxyHost=localhost
  • http.proxyPort=8080
  • http.proxyHost=localhost
  • http.proxyPort=8080

Afin d’auditer voire éditer les requêtes que Blurb va faire, nous allons installer Burp – un outil fréquemment utilisé pour l’audit de sécurité d’application web. La version gratuite est de loin suffisante pour les besoins que nous allons avoir.

image

Finalement, il est également nécessaire de noter l’adresse IP du site Internet hébergeant notre blog – dans cet exemple ce sera 93.88.254.17.

3. Modification de code dans DotClear

DotClear ne sachant pas nativement faire l’export des données comme souhaité, nous devons y ajouter un module d’export “WXR”.

La première étape est de sauver le fichier suivant sous le nom de wxr.xml dans le sous-répertoire DotClear inc/public/default-templates/:
<?xml version="1.0" encoding="UTF-8"?>

<!-- generator="DotClearHack" -->
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:wp="http://wordpress.org/export/1.0/"
>

<channel>
  <title><![CDATA[{{tpl:BlogName encode_xml="1"}}{{tpl:SysFeedSubtitle encode_xml="1"}}]]></title>
  <link>{{tpl:BlogURL}}</link>
  <description>{{tpl:BlogDescription encode_xml="1"}}</description>
  <language>{{tpl:BlogLanguage}}</language>
  <pubDate>{{tpl:BlogUpdateDate rfc822="1"}}</pubDate>
  <copyright>{{tpl:BlogCopyrightNotice encode_xml="1"}}</copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
 
  <tpl:Entries lastn="1000">
 
  <item>
    <title>{{tpl:EntryTitle encode_xml="1"}}</title>
    <link>{{tpl:EntryURL}}</link>
    <guid isPermaLink="false">{{tpl:EntryFeedID}}</guid>
    <pubDate>{{tpl:EntryDate rfc822="1"}}</pubDate>
    <dc:creator>{{tpl:EntryAuthorCommonName encode_xml="1"}}</dc:creator>
    <tpl:EntryIf has_category="1">
    <category>{{tpl:EntryCategory encode_html="1"}}</category>
    </tpl:EntryIf>
   
    <description></description>
    <!-- WP specific stuff -->
    <content:encoded>{{tpl:EntryExcerpt absolute_urls="1" encode_xml="1"}} {{tpl:EntryContent absolute_urls="1" encode_html="1"}}</content:encoded>
    <wp:post_id>{{tpl:EntryID}}</wp:post_id>
    <wp:post_date>{{tpl:EntryDate format="%Y-%m-%d %T"}}</wp:post_date>
    <wp:post_date_gmt>{{tpl:EntryDate format="%Y-%m-%d %T"}}</wp:post_date_gmt>
    <wp:comment_status>open</wp:comment_status>
    <wp:ping_status>open</wp:ping_status>
    <wp:post_name>post-name-value</wp:post_name>
    <wp:status>publish</wp:status>
    <wp:post_parent>0</wp:post_parent>
    <wp:menu_order>0</wp:menu_order>
    <wp:post_type>post</wp:post_type>
    <wp:post_password></wp:post_password>
  </item>
 
  </tpl:Entries>
 
</channel>
</rss>

Finalement, il a encore fallu ajouter le code suivant en rouge dans la fonction feed de /inc/public/lib.urlhandlers.php pour que l’URL soit reconnue et mappée vers le bon fichier de ressource que nous venons de créer:

if (preg_match('#^rss2/xslt$#',$args,$m))
{
# RSS XSLT stylesheet
self::serveDocument('rss2.xsl','text/xml');
return;
}
if (preg_match('#^wxr$#',$args,$m))
{
# RSS XSLT stylesheet
self::serveDocument('wxr.xml','text/xml');
return;
}
4. Re-routage des requêtes vers notre blog

Burp va nous permettre de mettre un certain nombre de règles afin d’envoyer cette requête non pas chez WordPress.com mais directement sur notre blog. Pour ce faire, nous devons:

  • faire en sorte que la requête parte sur notre blog au lieu de wordpress.com
  • réécrire les URLs afin qu’elles soient adaptées pour DotClear
4.1 Correction de la résolution hostname - IP

Comme toutes les requêtes sont passées via notre proxy Burp, nous pouvons y changer l’assignation hostname – adresse IP. Cela se fait facilement dans l’onglet Options de Burp:

image

4.2 Redirection appropriée des URLs

Il est également nécessaire de corriger les URLs ainsi que le champ Host: de l’entête HTTP. Les options nécessaires à cela sont disponibles dans l’onglet Proxy – sous-onglet options – section match and replace.

Il est nécessaire d’ajouter les règles suivantes:

Type Match Replace
request header POST /xmlrpc.php POST /[Votre URL]/index.php\?xmlrpc/default
request header Host: wordpress.com Host: [VotreSite]
request header

POST /wp-admin/export.php\?download=true&author=all

POST /[Votre URL]/index.php\?feed/wxr

 

image

4.3 Test de connexion

Le grand moment est là – vérifions si nous arrivons à récupérer la liste des blogs dans Blurb:

image

Bingo, la liste des blogs nous est retournée!

image

image

5. Pas toutes mes photos sont importées dans Blurb! Pourquoi?

Ce problème semble être du au type de lien utilisé par DotClear pour les photos. Certaines anciennes versions de DotClear ne semblait pas encoder les liens avec les caractères approprié (%20 pour l’espace par exemple).

La solution – contraignante – consiste à aller mettre à jour cet encodage dans la base de données. Il faut d’abord identifier tous les répertoires d’images contenant des espaces ou des caractères spéciaux.

La requête SQL suivante permet ensuite de voir si des articles possèdent le problème d’encodage mentionné:

SELECT post_id from dc_post where  INSTR(post_content_xhtml, '<a href="/[Votre URL]/public/repertoire photo avec espace/') > 0

Si un ou plusieurs documents sont retournés, il faut faire la mise à jour à l’aide de la commande SQL suivante – attention, il est vivement conseillé de faire une sauvegarde de la base de données avant d’exécuter de telles commandes!
UPDATE dc_post
set post_content =
   REPLACE(post_content,
   '|/[Votre URL]/public/repertoire photo avec espace/',
   '|/[Votre URL]/public/repertoire%20photo%20avec%20espace/'),
post_content_xhtml = REPLACE(post_content_xhtml,
   '<a href="/[Votre URL]/public/repertoire photo avec espace/',
   '<a href="/[Votre URL]/public/repertoire%20photo%20avec%20espace/')

Afin de détecter d’autres problèmes, il peut être intéressant de jeter un oeil au répertoire de travail de Blurb, situé dans Documents\BookSmartData\<nomDuProjet>\library\. Il s’y trouve notamment un fichier .bml qui est la transcription XML en plus haut niveau de ce qui a été récupéré via WXR.

- page 1 de 3