31 mai 2017

Wordpress : étude d'un site web victime de piratage

A Needle in a Hay StackDurant le mois de février 2017, j'ai été sollicité pour analyser et "nettoyer" un site web piraté. Le site web en question fonctionnait sous Wordpress. Je partage mon expérience ici, en espérant que cela aide certaines personnes plus tard. J'ai choisi de ne pas mentionner le nom du site ou de son webmestre, si des commentaires venaient à divulguer ces informations, je me permettrai de les éditer ou de les refuser.

Mais avant de continuer, une mise en garde : en cas de piratage avéré de votre site, l'option la plus sûre reste de tout effacer, de restaurer des sauvegardes et de mettre à jour votre CMS ainsi que ses plugins ! Malheureusement, tout le monde ne fait pas de sauvegarde, et se retrouve parfois, selon l'hébergeur, avec un site web hors ligne le temps que le "nettoyage" soit fait.

Ah, et au passage : les adresses IP et noms de domaines ont été anonymisés. Si jamais il y a un oubli, faites-le moi savoir, et je corrigerai au plus vite !

Première étape : l'inventaire

J'ai commencé par faire un rapide inventaire de ce que je pouvais récupérer : le webmestre du site m'a aimablement fourni les accès à son hébergement et à son site, de sorte que je puisse effectuer sans difficulté toutes les actions dont j'aurais besoin. Je récupère donc les éléments suivants :

  • logs d'accès sur environ 16 jours ;
  • copie complète des fichiers sur l'hébergement ;
  • export de la base de données.

Première recherche dans les logs

Etudions donc ces logs, et voyons ce qui peut en ressortir. Je regarde d'abord le nombre de lignes de chaque fichier (la rotation semble se faire de manière quotidienne) :

13:19 nils@shell2:~/tmp/exemple/access_logs$ wc -l *.log
   29771 exemple.fr-03-02-2017.log
   11377 exemple.fr-04-02-2017.log
   12504 exemple.fr-05-02-2017.log
   12279 exemple.fr-06-02-2017.log
    9700 exemple.fr-07-02-2017.log
    6182 exemple.fr-08-02-2017.log
   11819 exemple.fr-09-02-2017.log
   11918 exemple.fr-10-02-2017.log
   19616 exemple.fr-11-02-2017.log
   15377 exemple.fr-12-02-2017.log
   11232 exemple.fr-13-02-2017.log
    8253 exemple.fr-14-02-2017.log
    6791 exemple.fr-15-02-2017.log
   13711 exemple.fr-16-02-2017.log
   23480 exemple.fr-17-02-2017.log
   16602 exemple.fr-18-02-2017.log
  220612 total

Trois jours deviennent intéressant, le premier avec 29771 requêtes, un autre 19616, et enfin un dernier à 23480. Je vais donc rechercher dans ces logs, des requêtes étranges, en particulier beaucoup de requêtes POST vers une ou plusieurs pages précises, qui ne semblent pas faire partie du site. Je suis content d'avoir retenu quelque chose de mon expérience précédente.

Pour aller voir si quelque chose ressort des requêtes POST, je réutilise les one-liners awk dont j'avais parlé ici et . Voyons donc ce qui effectue le plus de requêtes POST dans le premier log :

nils@shell2:~/tmp/exemple/access_logs$ grep POST exemple.fr-03-02-2017.log | grep -v "POST /wp-cron.php" | awk '{frequencies[$1]++;} END {for (ip in frequencies) printf "%d\t%s\n" , frequencies[ip] , ip;}' | sort -rn | head -5
98      10.105.31.167
46      10.169.249.134
13      10.0.164.52
11      10.186.33.40
7       10.43.0.21

En effectuant des résolutions DNS inverses et des whois des adresses IP, je retrouve entre autres le FAI du webmestre, ainsi que le cluster de l'hébergeur. Mais je trouve aussi une adresse IP allemande, une autre tchétchène, et une russe. Surprenant pour un blog francophone, n'est-ce pas ? Bon, avant d'être accusé de racisme, allons voir ce que ces adresses IP ont fait comme requêtes. Extrait :

10.0.164.52 www.exemple.fr - [03/Feb/2017:14:56:17 +0100] "POST /wp-content/mybkl.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; MAARJS; rv:11.0) like Gecko"
10.0.164.52 www.exemple.fr - [03/Feb/2017:16:25:19 +0100] "POST /wp-content/mbsrd.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13F69"
10.0.164.52 www.exemple.fr - [03/Feb/2017:17:41:16 +0100] "POST /wp-content/vipmpnjen.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
10.0.164.52 www.exemple.fr - [03/Feb/2017:18:06:57 +0100] "POST /wp-content/bvcomjjaf.php HTTP/1.1" 200 11 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; Tablet PC 2.0)"
10.0.164.52 www.exemple.fr - [03/Feb/2017:18:10:18 +0100] "POST /wp-content/mmgi.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 5.1; rv:50.0) Gecko/20100101 Firefox/50.0"
10.0.164.52 www.exemple.fr - [03/Feb/2017:18:24:07 +0100] "POST /wp-content/gruk.php HTTP/1.1" 404 56215 "-" "Mozilla/5.0 (iPad; CPU OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13D15 Safari/601.1"
10.0.164.52 www.exemple.fr - [03/Feb/2017:18:24:11 +0100] "POST /wp-content/fkjzhl.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Linux; Android 6.0.1; SM-N920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36"
10.0.164.52 www.exemple.fr - [03/Feb/2017:18:32:00 +0100] "POST /wp-content/ssauz.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
10.0.164.52 www.exemple.fr - [03/Feb/2017:20:19:02 +0100] "POST /wp-content/zpamh.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36"
10.0.164.52 www.exemple.fr - [03/Feb/2017:20:47:29 +0100] "POST /wp-content/bvcomjjaf.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
10.0.164.52 www.exemple.fr - [03/Feb/2017:20:48:09 +0100] "POST /wp-content/ssauz.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (iPad; CPU OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1"
10.0.164.52 www.exemple.fr - [03/Feb/2017:20:50:33 +0100] "POST /wp-content/vgmq.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
10.0.164.52 www.exemple.fr - [03/Feb/2017:21:09:03 +0100] "POST /wp-content/pwemtiqeb.php HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; ASU2JS; rv:11.0) like Gecko"

Je récupère alors une archive propre de Wordpress depuis le site officiel, par acquis de conscience, mais personnellement, fkjzhl.php ou pwemtiqeb.php ça me semble louche comme nom de fichier. Comme prévu, ces fichiers n'ont absolument rien d'officiel, et les plugins Wordpress ne s'installent pas dans /wp-content/.

A ce moment-là, ma conclusion est la suivante : l'intrus (en supposant qu'il soit seul) a déposé une multitude de fichiers un peu partout dans l'arborescence afin de rendre plus difficile un éventuel nettoyage. De plus, en accédant à plusieurs fichiers, depuis plusieurs adresses IP différentes, cela noie les requêtes dans la masse et rend là aussi, la détection plus difficile.

Même joueur joue encore

Passons au deuxième fichier, en utilisant la même méthode :

nils@shell2:~/tmp/exemple/access_logs$ grep POST exemple.fr-17-02-2017.log | grep -v "POST /wp-cron.php" | awk '{frequencies[$1]++;} END {for (ip in frequencies) printf "%d\t%s\n" , frequencies[ip] , ip;}' | sort -rn | head -5
10143   10.9.129.250
240     10.28.47.221
234     10.135.219.59
229     10.213.224.115
199     10.123.209.172

Là aussi, la géolocalisation est assez variée : Allemagne, Hong-Kong, Pologne, Russie, Lituanie. Jetons alors un œil aux requêtes POST les plus visitées :

grep POST exemple.fr-17-02-2017.log | awk '{frequencies[$7]++;} END {for (field in frequencies) printf "%s\t%d\n" , field , frequencies[field];}' | sort -nr -k 2,2 | grep -v "/wp-cron.php"
/hostdata4.php  11299
/wp-includes/js/tinymce/dir.php 1211
/xmlrpc.php     240
/wp-content/from.php    75
/wp-content/common.php  39
/wp-login.php   8
/wp-content/db_model.php        7
/wp-content/rss_feeder.class.php        4
/wp-includes/customize/db2.php  3
/wp-admin/network/plugin-editor.php     3
/wp-includes/js/tinymce/f53585.php      3
/wp-includes/Requests/Exception/HTTP/431.php    3
/wp-content/tongue_lib.php      2
/palaute.php    2
/addfavorites.php       2
/wp-content/uploads/2015/07/lib.php     2
/ranking.php    2
/confirmorder.php       2
/wp-content/press_lib.php       2
/wp-json/wp/v2/posts/5529       1
/wp-content/adodb.class.php?test_url=true       1
/e28441e709.php?test_url=true   1
/index.php/wp-json/wp/v2/posts/5529     1
/wp-content/index.php   1
/wp-content/powerful.inc.php    1
/wp-content/991e700dbd.html     1
/       1
/xmlrpc.php?for=jetpack&token=anonymized      1

Pas mal de fichiers me semblent bizarres, et on peut s'amuser à aller les recherche dans l'archive "saine" de Wordpress. Spoiler Alert : il n'y sont pas.

Prenons un peu de hauteur

Avant de passer à autre chose, j'ai décidé de regarder les requêtes POST les plus visitées sur la totalité des fichiers (en retirant un peu plus de requêtes "classiques") :

nils@shell2:~/tmp/exemple/access_logs$ for i in $(find . -type f -print | sort); do cat $i >> ../exemple.fr-global.log; done
nils@shell2:~/tmp/exemple/access_logs$ grep POST ../exemple.fr-global.log | grep -v "/wp-cron.php\|/wp-login.php\|/xmlrpc.php" | awk '{frequencies[$7]++;} END {for (field in frequencies) printf "%s\t%d\n" , field , frequencies[field];}' | sort -nr -k 2,2 | head -50
/hostdata4.php  24322
/wp-includes/js/tinymce/dir.php 10057
/_index.php     1373
/wp-admin/admin-ajax.php        762
/wp-content/izodltnu.php        753
/wp-content/vgmq.php    705
/wp-content/zpamh.php   699
/wp-content/fkjzhl.php  654
/wp-content/ulimzaggf.php       645
/wp-content/jbrv.php    642
/wp-content/omfxde.php  629
/wp-content/ohvvdgk.php 625
/wp-content/pwemtiqeb.php       614
/wp-content/mmgi.php    613
/wp-content/nrzekbal.php        609
/wp-content/bvcomjjaf.php       605
/wp-content/yuflla.php  601
/wp-content/mybkl.php   598
/wp-content/mbsrd.php   594
/wp-content/vipmpnjen.php       578
/wp-content/ssauz.php   542
/wp-content/common.php  378
/wp-content/gruk.php    313
/wp-content/db_model.php        133
/wp-content/nwjtirmy.php        96
/wp-content/nrkg/wzvzrtnqh.php  95
/wp-content/iari.php    89
/wp-content/from.php    77
/wp-admin/admin.php?page=stats&noheader&chart=flot-stats-data   72
/wp-admin/admin-ajax.php?action=wp_ewwwio_async_optimize_media&nonce=c8aa5f3464 24
/wp-content/plugins/thank-me-later/lib/start37.php      23
/wp-admin/admin-ajax.php?action=wordfence_testAjax      23
/wp-content/uploads/2015/07/lib.php     19
/wp-includes/Requests/Exception/HTTP/431.php    19
/wp-includes/customize/db2.php  17
/wp-includes/js/tinymce/f53585.php      13
/wp-admin/meta/output.php       12
/post.php       11
/wp-admin/post.php      9
/wp-admin/network/plugin-editor.php     9
/wp-admin/admin-ajax.php?action=wp_ewwwio_async_optimize_media&nonce=50967874b9 8
/       7
/wp-comments-post.php?for=jetpack       6
/wp-content/egatl/wnxavysc.php  5
/3cdb6f452a.php?test_url=true   5
/wp-content/sad.func.php        4
/addfavorites.php       4
/palaute.php    4
/wp-content/rss_feeder.class.php        4
/wp-content/hook-filters.php    4

Afin d'éviter que ce blog tire en longueur juste pour des lignes de requête POST, je me suis limité au 50 premières lignes. Rien de plus que dans les autres recherche, on note comme avant que l'intrus a pris ses aises dans les répertoires /wp-content/ et /wp-includes/. L'intérêt de faire cette recherche est de faire remonter certaines requêtes qui seraient passées sous les radars si j'avais continué fichier par fichier.

Résultat des courses

J'ai donc repéré comme ça un certain nombre de fichiers qui n'ont rien à voir avec le contenu réel du blog, et que je vais pouvoir supprimer sans regret. Cela n'est hélas pas suffisant, car rien n'empêche un intrus d'insérer des fichiers qui ne sont presque pas accédés. Dans un prochain billet, j'espère donc comparer au niveau fichier l'export de ce blog avec une archive saine.

Vous avez aimé cet article ? Alors partagez-le sur les réseaux sociaux ! Si en plus vous avez des remarques, ou des propositions d'améliorations, n'hésitez pas : les commentaires sont là pour ça !

Crédit photo : Michael Gil - A Needle in a Hay Stack.

18 avr. 2017

PHP Malware Finder : gestion des listes blanches

Lime & List

Rappelez-vous : dans le billet précédent, nous avons découvert PHP Malware Finder. Aujourd'hui, allons plus loin et passons à la gestion des listes blanches ! Pour cela, prenons l'exemple d'un blog fonctionnant sous Wordpress. Cette gestion des listes blanches se fera en trois étapes :

  • tout d'abord, il s'agit de faire un état de l'existant, en exécutant PHP Malware Finder et en observant des faux-positifs ;
  • ensuite, la deuxième étape sera de comprendre le fonctionnement des règles et des listes blanches ;
  • enfin, la troisième étape consistera à générer la liste blanche et à l'intégrer dans les règles existantes.

Et pour finir, en bonus, un script de génération de liste blanche sera abordé. Au moment de l'écriture de cet article, la dernière version de Wordpress est la 4.7.3. La dernière version de PHP Malware Finder est la 0.3.4.

Exécution de PHP Malware Finder sur un site de test

Nous allons donc commencer par récupérer une archive de Wordpress directement sur le site officiel, afin de s'assurer qu'elle est saine :

nils@Dalaran:~/tmp$ wget https://wordpress.org/wordpress-4.7.3.tar.gz
--2017-04-11 21:54:31--  https://wordpress.org/wordpress-4.7.3.tar.gz
Résolution de wordpress.org… 66.155.40.250, 66.155.40.249
Connexion à wordpress.org|66.155.40.250|:443… connecté.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : 8008833 (7,6M) [application/octet-stream]
Sauvegarde en : « wordpress-4.7.3.tar.gz »

wordpress-4.7.3.tar.gz                                      100%[=========================================================================================================================================>]   7,64M  3,27MB/s    ds 2,3s    

2017-04-11 21:54:34 (3,27 MB/s) — « wordpress-4.7.3.tar.gz » sauvegardé [8008833/8008833]

nils@Dalaran:~/tmp$ tar -xzf wordpress-4.7.3.tar.gz

Puis, décompressons-la et scannons son contenu :

nils@Dalaran:~/tmp$ cd wordpress
nils@Dalaran:~/tmp/wordpress$ phpmalwarefinder .
ObfuscatedPhp ./wp-admin/includes/class-ftp.php
DodgyStrings ./wp-admin/includes/ajax-actions.php
ObfuscatedPhp ./wp-admin/includes/class-wp-plugins-list-table.php
DodgyPhp ./wp-admin/includes/schema.php
ObfuscatedPhp ./wp-admin/includes/media.php
DodgyStrings ./wp-admin/includes/template.php
ObfuscatedPhp ./wp-admin/includes/template.php
DodgyStrings ./wp-admin/includes/upgrade.php
ObfuscatedPhp ./wp-includes/bookmark-template.php
DodgyPhp ./wp-includes/class-pop3.php
DodgyStrings ./wp-includes/class-phpmailer.php
DodgyPhp ./wp-includes/class-phpmailer.php
ObfuscatedPhp ./wp-includes/class-wp-meta-query.php
ObfuscatedPhp ./wp-includes/class-wp-tax-query.php
DodgyStrings ./wp-includes/class-wp-query.php
DodgyStrings ./wp-includes/comment.php
ObfuscatedPhp ./wp-includes/date.php
DodgyStrings ./wp-includes/deprecated.php
ObfuscatedPhp ./wp-includes/deprecated.php
DodgyStrings ./wp-includes/functions.php
DodgyPhp ./wp-includes/functions.php
DangerousPhp ./wp-includes/functions.php
DodgyStrings ./wp-includes/formatting.php
ObfuscatedPhp ./wp-includes/IXR/class-IXR-date.php
DodgyPhp ./wp-includes/load.php
DodgyStrings ./wp-includes/media.php
ObfuscatedPhp ./wp-includes/post-template.php
ObfuscatedPhp ./wp-includes/js/tinymce/tinymce.min.js
DodgyStrings ./wp-includes/post.php
ObfuscatedPhp ./wp-includes/SimplePie/Parse/Date.php

Certains fichiers sont présentés comme malveillants, mais il n'en est rien : ce sont donc des faux-positifs. Il faut donc les mettre en liste blanche, mais avant, il convient de comprendre où sont les signatures et cette liste blanche.

Signatures et listes blanches

PHP Malware Finder est basé sur YARA, un outil qui recherche des fichiers selon certains critères, comme la présence d'une chaîne de caractères, ou une expression rationnelle. Les signatures sont définies dans les fichiers asp.yar, common.yar et php.yar. On comprend alors qu'un fichier est dédié aux fichiers ASP, un autre aux fichiers PHP, et le troisième regroupe des signatures communes aux deux langages.

Passons ensuite à la lecture des fichiers asp.yar et php.yar : on voit assez vite que pour qu'un fichier soit reconnu comme malveillant, il doit non seulement remplir certaines conditions (les règles définies), mais il doit aussi ne pas remplir les conditions des fichiers de liste blanche.

En fait, les fichiers de liste blanche sontdes sommes de contrôle SHA1 de la taille des fichiers considérés comme faux-positifs. Allons maintenant à la création du fichier contenant ces informations !

Création du fichier de liste blanche

Pour ajouter nos faux-positifs en liste blanche, nous allons avoir besoin de deux choses :

  • d'abord, python-yara, une bibliothèque qui permet d'accéder à yara depuis Python ;
  • ensuite un script, generate_whitelist.py, qui se trouve être écrit... en Python.

Ce script est disponible dans le répertoire utils de PHP Malware Finder, ou bien dans ${PREFIX}/share/php-malware-finder/utils/ s'il est installé depuis pkgsrc (${PREFIX} dépendant de l'installation de pkgsrc).

Testons donc ce script sur notre répertoire wordpress :

nils@Dalaran:~/tmp/php-malware-finder/php-malware-finder/utils$ /opt/pkg/bin/python2.7 ./generate_whitelist.py ma_liste_blanche ~/tmp/wordpress

Le résultat est alors ressemblant à celui-ci :

import "hash"

private rule maListeblanche
{
    condition:
        /* maListeblanche */
        hash.sha1(0, filesize) == "12a18329072bed94b6f9c4d9f16d7a079ca64655" or // /Users/nils/tmp/wordpress/wp-admin/includes/ajax-actions.php
        hash.sha1(0, filesize) == "6bccf04c8b46c8d6cdf79db8b509f4b76689f3bf" or // /Users/nils/tmp/wordpress/wp-admin/includes/class-ftp.php
        hash.sha1(0, filesize) == "aa6a12a0325056b9649f58f8072fa02a1e264551" or // /Users/nils/tmp/wordpress/wp-admin/includes/class-wp-plugins-list-table.php
        hash.sha1(0, filesize) == "3e73204644f0ce7b0971aad885fdcbcabba629fc" or // /Users/nils/tmp/wordpress/wp-admin/includes/media.php
        hash.sha1(0, filesize) == "81b1ae432ba765a43c6d81fb6d6c35ce72efd0e8" or // /Users/nils/tmp/wordpress/wp-admin/includes/schema.php
        hash.sha1(0, filesize) == "2ef50e790fdd42daa8ccd64d4c7c4be75d21742d" or // /Users/nils/tmp/wordpress/wp-admin/includes/template.php
        hash.sha1(0, filesize) == "9835d10a7561deeef1f8381da065b4b45d7f2662" or // /Users/nils/tmp/wordpress/wp-admin/includes/upgrade.php
        hash.sha1(0, filesize) == "b92aefa2917fc319ca7ceab092e183cafc651a6d" or // /Users/nils/tmp/wordpress/wp-includes/bookmark-template.php
        hash.sha1(0, filesize) == "cb0c5a355409d807202bbf52749a3e74a9967a6a" or // /Users/nils/tmp/wordpress/wp-includes/class-phpmailer.php
        hash.sha1(0, filesize) == "e4f0694bc96f99d5e30201171a3e7fc86e9e5ae4" or // /Users/nils/tmp/wordpress/wp-includes/class-pop3.php
        hash.sha1(0, filesize) == "c06a15f4869c5459a782b714572eacea5c82d570" or // /Users/nils/tmp/wordpress/wp-includes/class-wp-meta-query.php
        hash.sha1(0, filesize) == "72dbc1d4f2bbc8efdcdd834ecaf3771cbf17f64e" or // /Users/nils/tmp/wordpress/wp-includes/class-wp-query.php
        hash.sha1(0, filesize) == "348c3a60d99768041be690b65b008628f53badb7" or // /Users/nils/tmp/wordpress/wp-includes/class-wp-tax-query.php
        hash.sha1(0, filesize) == "0aab95245b9668f954151f4312b678fb0ee798cf" or // /Users/nils/tmp/wordpress/wp-includes/comment.php
        hash.sha1(0, filesize) == "c8c9182aa25fb92ca91fcc96c3419847acdcf6e0" or // /Users/nils/tmp/wordpress/wp-includes/date.php
        hash.sha1(0, filesize) == "5877695771fbe7a5667f4a06f4d897a37ef3fceb" or // /Users/nils/tmp/wordpress/wp-includes/deprecated.php
        hash.sha1(0, filesize) == "806d2872676ea22e0a6fa6b32fbd4652298023ee" or // /Users/nils/tmp/wordpress/wp-includes/formatting.php
        hash.sha1(0, filesize) == "3083b9a58e76d42455935811a457f29f57620145" or // /Users/nils/tmp/wordpress/wp-includes/functions.php
        hash.sha1(0, filesize) == "f53f80c4ee7446f0b605443b6d2f05acd8064d13" or // /Users/nils/tmp/wordpress/wp-includes/load.php
        hash.sha1(0, filesize) == "bea5ea598f537e7acb20b77a1421f819c0a9ec75" or // /Users/nils/tmp/wordpress/wp-includes/media.php
        hash.sha1(0, filesize) == "abcf1a0801694db4774cd2abb29b5392e10dd632" or // /Users/nils/tmp/wordpress/wp-includes/post-template.php
        hash.sha1(0, filesize) == "5ddc1e5c5c6302211b1aecbf930f76417b65d678" or // /Users/nils/tmp/wordpress/wp-includes/post.php
        hash.sha1(0, filesize) == "040ef40d245242723de200e494a27545ea0b121b" or // /Users/nils/tmp/wordpress/wp-includes/IXR/class-IXR-date.php
        hash.sha1(0, filesize) == "086986cdf03ede58494034661d38c4842af38fe3"    // /Users/nils/tmp/wordpress/wp-includes/SimplePie/Parse/Date.php
}

Il faut ensuite ajouter la règle générée à l'instant aux listes blanches déjà présentes. Pour aller vite, on peut directement éditer le fichier whitelist.yar et ajouter notre règle juste avant la dernière (IsWhitelisted). Il ne faut donc pas oublier d'ajouter dans cette dernière règle celle qu'on vient de créer. Dans mon cas, la dernière règle ressemble à cela :

private rule IsWhitelisted
{
    condition:
        Symfony or
        Wordpress or
        Prestashop or
        Magento or
        Magento2 or
        Drupal or
        Roundcube or
        Concrete5 or
        Dotclear or
        Owncloud or
        Phpmyadmin or
        Misc or
        maListeblanche
}

Vérifions maintenant que la liste blanche est bien à jour et assurons-nous qu'il n'y a plus de faux-positif dans notre répertoire :

nils@Dalaran:~/tmp/wordpress$ phpmalwarefinder ./
ObfuscatedPhp .//wp-includes/js/tinymce/tinymce.min.js

Et là, c'est le drame. Mais pourquoi ? En fait, c'est parce que le script utilisé à l'instant ne prend en compte que les fichiers PHP. Cela peut être une idée d'amélioration pour une version future.

Création facile de liste blanche pour divers logiciels PHP

Il existe un autre script fort utile : mass_whitelist.py. Son but est de faciliter la création de liste blanche pour des applications PHP connues, cela va de Wordpress à Drupal en passant par PHPMyAdmin. Il suffit de lui donner en argument le nom de l'application, l'URL de téléchargement (en remplaçant le numéro de version avec version), ainsi que les numéros de version mineurs et majeurs à rechercher.

Ce script va alors rechercher toutes les versions de l'application, les télécharger, et afficher une liste blanche les prenant toutes en compte. Par exemple, pour Wordpress :

nils@Dalaran:~/tmp/php-malware-finder/php-malware-finder/utils$ /opt/pkg/bin/python2.7 mass_whitelist.py wordpress https://wordpress.org/wordpress-__version__.tar.gz 4 7 3 | tee -a wordpress.yar

Le fichier de résultat se nomme donc wordpress.yar. Il suffira alors de le copier dans le répertoire de règles (et écraser le précédent) afin de le prendre en compte. Attention, car ce script est long, très long !

Vous avez aimé cet article ? Alors partagez-le sur les réseaux sociaux ! Si en plus vous avez des remarques, ou des propositions d'améliorations, n'hésitez pas : les commentaires sont là pour ça !

Crédit Photo : List_84 - Lime & List

11 avr. 2017

PHP Malware Finder : détecteur d'intrusion sur site PHP

Dans ma loupe - 2Lors des RMLL de Beauvais en 2015, j'ai eu l'occasion de voir une conférence présentant au passage un outil fort sympathique nommé PHP Malware Finder (site web). Le but de ce script est de détecter du code PHP qui semble obfusqué ou malveillant, voire même des fonctions trouvées généralement dans des malwares ou des webshells. On trouve, sur la page du projet, une liste (non exhaustive) des malwares qu'il est capable de trouver.

PHP Malware Finder scanne un répertoire pour trouver les malwares, on peut donc choisir de l'utiliser directement sur son serveur, soit depuis son ordinateur en ayant au préalable copié les fichiers de son site. La première option nécessitera les droits administrateurs pour les dépendances, il vaut mieux donc choisir la deuxième option pour la découverte de cet outil, et aussi parce qu'il est assez gourmand en accès disque.

Installer PHP Malware Finder

Son installation et utilisation sont simples, pourvu que YARA soit installé. Sur une distrbution Linux classique, une fois YARA installé, il suffit d'installer PHP Malware Finder en clonant le dépôt Github :

git clone https://github.com/nbs-system/php-malware-finder.git

Le script phpmalwarefinder se trouve dans le répertoire php-malware-finder/php-malware-finder/.

Il est par contre possible, sur un système NetBSD ou macOS, de l'installer facilement via pkgsrc-wip :

cd /usr/pkgsrc/wip/
make package-install

Avec cette manière, PHP Malware Finder est disponible directement dans $PATH :)

Utilisation

Il suffit maintenant de le lancer en spécifiant un endroit où il y a des pages PHP :

$ phpmalwarefinder /chemin/vers/ses/pages/

Si certains fichiers semblent réagir aux signatures, alors le script affichera le type de problème ainsi que le chemin du fichier. Sinon, il n'affiche rien. Bien sûr, d'autres options sont disponibles, et un résumé de celles-ci est disponible via l'option -h :

$ phpmalwarefinder -h
Usage phpmalwarefinder [-cfhtvl] <file|folder> ...
-c  Optional path to a configuration file
-f  Fast mode
-h  Show this help message
-t  Specify the number of threads to use (8 by default)
-v  Verbose mode
-l  Set language ('asp', 'php')
-L  Check long lines
-u  update rules

Par défaut, PHP Malware Finder va chercher ses signatures dans son répertoire (si utilisé depuis un clone du dépôt git), mais le paquet pkgsrc va les chercher dans /usr/pkg/etc/phpmalwarefinder/ (ou /opt/pkg/ pour macOS). Il est possible de préciser ses propres signatures via l'option -c.

D'autres options sont aussi très intéressantes, comme -f (pour accélérer le scan), ou -t qui permet de limiter le nombre de threads à utiliser en parallèle. Cela peut s'avérer très pratique dans le cas où le scan prend du temps et on veut continuer à utiliser sa machine pendant ce temps. Au passage, une recommandation : il vaut mieux éviter de lancer PHP Malware Finder directement sur son serveur, il a tendance à être assez gourmand en ressources !

Si vous aimez cet article, partagez-le sur les réseaux sociaux. Si vous avez des remarques, ou des propositions d'améliorations, n'hésitez pas : les commentaires sont là pour ça !

Crédit photo : Olivier Penet - Dans ma loupe-2

Propulsé par Dotclear