Depuis le début de mon blog (
http://www.guiguishow.info ), j'utilise le plugin Spam Karma 2 pour filtrer automatiquement le spam dans les commentaires. Le fonctionnement de Spam Karma 2 est de faire du scoring comme le font les antispams mail : plusieurs critères (délai entre l'ouverture de la page et envoi du commentaire, nombre de liens insérés dans le contenu du commentaire, payload javascript, détection de balises HTML,...), chaque critère fait perdre des points. Si le score final est en dessous d'un certain seuil, on laisse une autre chance en proposant un captcha. En dessous d'un deuxième seuil, on ne propose même pas une deuxième chance. Spam Karma est modulaire : plusieurs plugins participent à établir le score final.
Évidemment, je n'utilise pas le check RBL qui consiste à faire une requête sur un site web externe pour vérifier la réputation de l'IP du visiteur et les URL insérées dans le contenu de son commentaire (si elles sont connues du service -> mauvaise réputation -> on retire des points au score). Il n'y a donc aucune dépendance à un service externe et c'est ce que j'ai toujours apprécié chez ce plugin. C'est pour ça que je n'ai pas choisi la boîte noire Akismet qui est le filtre antispam de commentaires de référence, activé par défaut dans Wordpress. Voir ici pour un regard critique sur Akismet :
http://sebsauvage.net/rhaa/?2010/08/12/11/19/09-probleme-avec-akismet . Avec Spam Karma 2, je peux consulter les critères de blocage de chaque commentaire. Je peux débloquer les commentaires si je veux (Akismet aussi sur ce point). Je peux agir sur tous les critères de classification. Et je ne participe pas à une concentration malsaine en un seul service de filtrage (Akismet) qui, du coup, devient hyper puissant et critique.
L'aspect négatif c'est que le développement de Spam Karma 2 n'est plus assuré depuis juillet 2008 (voir
http://unknowngenius.com/blog/archives/2008/07/14/spam-karma-is-gpl/ ). Il devient difficile de le télécharger. Je l'utilise sans problème depuis juillet 2010 mais il est évident que plus le temps passe, plus la probabilité qu'une mise à jour de Wordpress casse Spam Karma augmente. Et évidemment, les failles de sécurité ne seront pas corrigées alors qu'on sait que les problèmes de sécurité avec Wordpress viennent en majorité des plugins. Ne parlons pas de l'absence d'une communauté qui a aussi un impact sur la sécurité.
Je viens de corriger 4 problèmes avec le Spam Karma de mon Wordpress dont deux sont de ma faute. Je les mentionne aussi ici même si ça allonge la longueur de ce shaarli car ça nous rappelle des leçons importantes.
* Commençons par le plus facile : un des critères de notation de Spam Karma consiste à pénaliser les commentaires postés sur de vieux articles. Par défaut, si le commentaire est déposé sur un article qui a été publié il y a plus de 15 jours -> vlam, pénalité. Sauf que mon article le plus récent date de mars 2015. 1 an, donc. Et les deux qui suivent datent d'octobre et août 2014... bientôt 2 ans... Sauf que ce blog n'est pas mort. Je lis les commentaires. Je corrige mes articles ou je fais référence à des documentations complémentaires. Dans ces conditions, il est injuste de pénaliser un visiteur qui poste des années après la publication d'un article. Un commentaire humain passe malgré la pénalité mais c'est parfois limite. J'ai donc ajusté ce plugin de Spam Karma 2 pour pénaliser les commentaires déposés sur des articles vieux de plus de 2 ans. Moralité : on se souvient qu'une politique de sécurité, même antispam, doit être ajustée au fil du temps. :-
* En cherchant à tester le point précédent, je me suis aperçu que le captcha de la deuxième chance ne fonctionne pas. L'image ne s'affiche pas, seul le texte alternatif s'affiche. On devine que la bibliothèque GD n'est pas installée/activée ou foire. Ce que confirment les logs Apache : « PHP Fatal error: Call to undefined function imagecreate() in /chemin/vers/wordpress/wp-content/plugins/sk2/sk2_captcha_graphic.php on line 37 ». Solution : apt install php5-gd + systemctl restart apache2 . J'ai dû virer le package il y a des mois et des mois en me disant qu'aucune des applications web que j'utilise ne dépend de GD. Réduire les logiciels installés sur un serveur pour réduire la surface d'attaque, c'est bien mais il faut penser à tout. :-
* En regardant mes logs Apache, je constate que les error logs de février et mars 2016 occupent largement plus d'espace que d'habitude : 748 Mo pour mars, 1,1G pour février (compressés en 19 Mo de gzip). Ma volumétrie habituelle d'error log ne dépasse pas 100 Ko de texte non compressé ! Il y a donc un problème. Ce problème est la répétition incessante de ces messages d'erreurs : « PHP Warning: mysql_real_escape_string(): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) in /chemin/vers/wordpress/wp-content/plugins/sk2/sk2_functions.php on line 107 [...] PHP Warning: mysql_real_escape_string(): A link to the server could not be established in /chemin/vers/wordpress/wp-content/plugins/sk2/sk2_functions.php on line 107 ».
Je regarde sur tous les Wordpress que j'administre : même problème partout. À noter : sur des installations Apache+MySQL non chrootées (consulter la ressource suivante pour connaître les raisons qui me font aujourd'hui douter de l'intérêt d'un chroot Apache+MySQL :
http://shaarli.guiguishow.info/?08T1Cw ), les logs contiennent plutôt « PHP Warning: mysql_real_escape_string(): Access denied for user 'www-data'@'localhost' (using password: NO) in /chemin/vers/wordpress/wp-content/plugins/sk2/sk2_functions.php [...] PHP Warning: mysql_real_escape_string(): A link to the server could not be established in /chemin/vers/wordpress/wp-content/plugins/sk2/sk2_functions.php on line 107 ».
Quand ces erreurs sont-elles apparues dans mes logs ? 22 janvier 2016 sur une installation, 24 décembre 2015 sur une autre.
Que s'est-il passé ?
* J'ai pour méthode de ne jamais mettre Wordpress à jour lors de la sortie d'une version majeure car je sais qu'une version corrigée de nouvelles failles de sécurité introduites par cette version majeure sortira un ou deux mois plus tard. C'est toujours comme ça avec Wordpress, voir
http://shaarli.guiguishow.info/?ikqFrg . Donc ce n'est pas le passage à Wordpress 4.4, dont la date de sortie (début décembre) pourrait correspondre, qui a tout cassé. Fausse route.
* Les logs dpkg montrent une mise à jour de PHP le 24/12/2015, avant l'apparition des erreurs. Mais rien le 22 janvier.
* Le changelog Debian (voir la ressource suivante
http://shaarli.guiguishow.info/?feXm_A pour le trouver ) des packages libapache-mod-php5 / php5-mysql ne mettent rien en évidence, juste un passage à la version 5.6.16 upstream qui corrige des crashs le 22/12/2015.
* En revanche, c'est le 24/12/2015 que j'ai mis à jour ce serveur de Debian Wheezy à Jessie, il n'y a qu'à voir la volumétrie du log dpkg ce jour-là. Je passe donc de PHP 5.4 (Wheezy) à PHP 5.6 (Jessie) tout en sachant que l'extension mysql et donc mysql_real_escape_string() devient dépréciée à partir de PHP 5.5 . Sauf que tout cela est encore parfaitement utilisable par n'importe quel script... Fausse route, donc.
* À moins que... Qu'est-ce que Wordpress utilise ? MySQL, MySQLi ou PDO ? Réponse dans wp-includes/wp-db.php : Wordpress utilise MySQLi depuis sa version 3.9 (voir
https://make.wordpress.org/core/2014/04/07/mysql-in-wordpress-3-9/ ou
https://core.trac.wordpress.org/browser/tags/3.9/src/wp-includes/wp-db.php ). « /* Use ext/mysqli if it exists and:
- WP_USE_EXT_MYSQL is defined as false, or
- We are a development version of WordPress, or
- We are running PHP 5.5 or greater, or
- ext/mysql is not loaded. »
Je ne remplissais aucune des conditions : l'extension MySQL était bien chargée, je n'utilisais pas une version de développement, je n'ai pas désactivé manuellement l'usage de l'extension MySQL dans Wordpress et je n'avais pas une version de PHP >= 5.5 . Mais en passant à Jessie, cette dernière condition est devenue vraie.
* Ainsi Wordpress utilise MySQLi depuis ma mise à jour vers Jessie (et donc PHP 5.6.X) et donc mysql_real_escape_string() n'est plus utilisable.
* Pourquoi ce changement s'est-il manifesté uniquement le 22 janvier sur une autre installation ? Car ce site est beaucoup moins consulté, même par les bots donc aucun envoi de commentaire n'a été tenté et donc aucune erreur dans les logs.
Analyse :
* La ligne 107 de sk2_functions.php tombe en plein dans la fonction sk2_escape_string() dont le rôle est d'être un wrapper : est-ce que nous sommes en PHP 5, auquel cas la fonction mysql_real_escape_string() existe et on l'utilise, sinon on est en PHP 4 et il faut utiliser mysql_escape_string(). Sauf que mysql_real_escape_string ne peut plus fonctionner puisque Wordpress utilise mysqli.
Solution :
* Pour bien faire, il faudrait utiliser les requêtes SQL préparées (donc la fonction prepare() de l'objet $wpdb, "abstraction" de la base de données par Wordpress) mais ça serait une vraie plaie de modifier tout Spam Karma dont le code n'est clairement pas simple à migrer àmha car les requêtes SQL sont conditionnelles et gérées par une palanquée de if/else...
* On pourrait utiliser mysqli_real_escape_string() sauf que, contrairement à mysql_real_escape_string(), il faut obligatoirement passer la variable représentant la connexion à la base de données. Chose que $wpdb ("abstraction" de la base de données par Wordpress) n'expose pas...
* On regarde la doc' Wordpress
https://codex.wordpress.org/Data_Validation#Database : donc il faut utiliser les requêtes préparées et quand on ne peut pas, il faut utiliser esc_sql() qui est une fonction interne de Wordpress (voir
https://codex.wordpress.org/Function_Reference/esc_sql ) qui semble moins bien faire le job que mysql_real_escape_string() genre elle ne gère pas le jeu de caractères de la base de données, par exemple.
* En pratique : dans les fonctions sk2_escape_string() et sk2_escape_form_string() du fichier sk2_functions.php de Spam Karma, on empêche l'exécution du code actuel en le précédant par « return esc_sql($string); ».
Voici ce que donnent les modifs :
function sk2_escape_string ($string)
{
return esc_sql($string);
if(function_exists('mysql_real_escape_string'))
return mysql_real_escape_string($string);
else
return mysql_escape_string($string);
}
function sk2_escape_form_string ($string)
{
if (get_magic_quotes_gpc())
$string = stripslashes($string);
return esc_sql($string);
if(function_exists('mysql_real_escape_string'))
return mysql_real_escape_string($string);
else
return mysql_escape_string($string);
}
* Dernier problème : lors du dépôt d'un commentaire, Spam Karma explose en vol et affiche ceci à l'utilisateur :
« Failed to insert blacklist entry: ip_white - <IP_visiteur>.
SQL error: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
Error: cannot update comment entry ID: 36836 to status: approved.
SQL error: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
Failed inserting/updating sk2_kSpamTable record for comment ID:36836 (mode: overwrite).
Query: INSERT INTO `wp_sk2_spams` SET `karma` = 29.25,`karma_cmts` = '', `unlock_keys` = '', `remaining_attempts` = '', `last_mod` = NOW(), `comment_ID` = 36836
SQL error: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
Sorry, but your comment has been flagged by the spam filter running on this blog: this might be an error, in which case all apologies. Your comment will be presented to the blog admin who will be able to restore it immediately.
You may want to contact the blog admin via e-mail to notify him. »
Dans les logs Apache, on retrouve les erreurs du point précédent. C'est donc le même problème. Appliquer la correction proposée au point précédent corrige celui-ci aussi. \o/