Lorsque «a» n'est pas égal à «a». Dans le sillage d'un hack

Une histoire désagréable est arrivée à l'un de mes amis. Mais pour autant que cela se soit avéré désagréable pour Mikhail, c'était tout aussi divertissant pour moi.

Je dois dire que mon ami est un utilisateur assez UNIX : il peut installer le système lui-même, installer mysql , php et effectuer les réglages nginx les plus simples.
Et il dispose d'une dizaine de sites et demi dédiés aux outils de construction.

L'un de ces sites, dédié aux tronçonneuses, est solidement ancré dans les moteurs de recherche TOP. Ce site est un examinateur à but non lucratif, mais quelqu'un est tombé sur sa gorge et l'a attaqué. Soit DDoS , puis force brute, puis les commentaires écriront obscènes et enverront des abus à l'hébergement et à l'ILV.
Du coup, tout s'est calmé et cette accalmie n'était pas bonne, et le site a commencé à sortir progressivement des premières lignes des résultats.

image

C'était un dicton, puis le vélo d'administration lui-même.

Le temps approchait du sommeil quand le téléphone sonna: «Sanya, tu ne regarderas pas mon serveur? Il me semble qu’ils m’ont piraté, je ne peux pas le prouver, mais le sentiment n’a pas disparu la troisième semaine. Peut-être que je dois juste me faire soigner pour la paranoïa? »

Vint ensuite une discussion d'une demi-heure qui peut se résumer comme suit:

  • le terreau était assez fertile;
  • le pirate pourrait obtenir des droits de superutilisateur;
  • l'attaque (si elle a eu lieu) visait spécifiquement ce site;
  • les zones à problèmes sont corrigées et il vous suffit de comprendre s'il y a eu un fait de pénétration;
  • le piratage n'a pas pu toucher le code du site et les bases de données.

Concernant le dernier paragraphe.

image

Seul le frontend IP blanc regarde le monde. Il n'y a pas d'échange entre backends et frontend sauf http (s), les utilisateurs / mots de passe sont différents, les clés ne sont pas échangées. Sur les adresses grises, tous les ports sauf 80/443 sont fermés. Les backends IP blancs ne sont connus que de deux utilisateurs auxquels Michael fait entièrement confiance.

Debian 9 est installée sur le frontal et au moment de l'appel, le système est isolé du monde par un pare-feu externe et arrêté.

«Ok, donnez-moi accès», je décide de reporter le sommeil d'une heure. "Je vais voir de mes propres yeux."

Ci-après:

$ 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 

A la recherche d'un éventuel hack


Je démarre le serveur, d'abord en mode secours . Je monte des disques, je feuillette les journaux d' authentification , l' historique , les journaux système, etc., si possible, vérifie les dates de création de fichier, bien que je comprenne qu'un pirate normal «transpire» derrière lui, et Misha «piétine» déjà en connaissance de cause.

Je commence en mode normal, surtout ne sachant pas encore quoi chercher, j'étudie les configs. Il s'intéresse principalement à nginx car, en général, il n'y a rien d'autre que lui sur le frontend.
Les configs sont petites, bien structurées en une douzaine de fichiers, je les regarde juste à tour de rôle. Tout semble être propre, mais vous n'en avez jamais manqué certains, je vais faire une liste complète:

 $ nginx -T nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful 

Je n'ai pas compris: "Où est la liste?"

 $ nginx -V nginx version: nginx/1.10.3 TLS SNI support enabled configure arguments: --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 

La seconde est ajoutée à la question de listage: «Pourquoi une version si ancienne de nginx?»

De plus, le système estime que la version est installée à neuf:

 $ dpkg -l nginx | grep "[n]ginx" ii nginx 1.14.2-2+deb10u1 all small, powerful, scalable web/proxy server 

J'appelle:
- Misha, pourquoi reconstruis-tu Nginx ?
- Ok, je ne sais même pas comment faire!
- D'accord, dors ...

Nginx est réassemblé sans ambiguïté et la liste de liste pour "-T" est masquée pour une raison. Il n'y a aucun doute sur le piratage et vous pouvez simplement l'accepter et (puisque Misha a remplacé le serveur par un nouveau de toute façon), considérez le problème résolu.

Et en effet, puisque quelqu'un a les privilèges root , il est logique de ne réinstaller que le système et de rechercher quelque chose qui a été mal consommé là-bas, mais cette fois, la curiosité a vaincu le rêve. Comment savoir ce qu'ils voulaient nous cacher?

Essayons de retracer:

 $ strace nginx -T 

Nous regardons à travers la trace n'est clairement pas assez de lignes à la

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

Pour le plaisir, comparez les conclusions

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

Je pense que cette 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é réduit à la forme:

  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; 

par conséquent, la liste par -T n'est pas affichée.

Mais comment voir notre config?


Si mon idée est correcte et que le problème se trouve uniquement dans la variable ngx_dump_config, nous essaierons de l'installer en utilisant gdb , car la clé --with-cc-opt -g est présente et nous espérons que l'optimisation -O2 ne nous fera pas de mal. En même temps, puisque je ne sais pas comment ngx_dump_config pourrait être traité dans le cas 'T' :, nous n'appellerons pas ce bloc, mais l'installer en utilisant le cas 't':

Pourquoi puis-je utiliser '-t' avec '-T'
Le traitement du bloc if (ngx_dump_config) se produit à l'intérieur de if (ngx_test_config) :
  if (ngx_test_config) { if (!ngx_quiet_mode) { ngx_log_stderr(0, "configuration file %s test is successful", cycle->conf_file.data); } if (ngx_dump_config) { cd = cycle->config_dump.elts; for (i = 0; i < cycle->config_dump.nelts; i++) { ngx_write_stdout("# configuration file "); (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 est modifié dans cette partie, et non dans le cas 'T' :, alors ma méthode ne fonctionnera pas.

Testez nginx.conf
Ayant déjà résolu le problème de manière empirique, il a été constaté que pour le fonctionnement du malware, une configuration nginx minimale du formulaire est nécessaire:

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

Nous allons l'utiliser par souci de concision dans l'article.

Exécutez le débogueur
 $ gdb --silent --args nginx -t Reading symbols from nginx...done. (gdb) break main Breakpoint 1 at 0x1f390: file src/core/nginx.c, line 188. (gdb) run Starting program: nginx -t [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=2, argv=0x7fffffffebc8) at src/core/nginx.c:188 188 src/core/nginx.c: No such file or directory. (gdb) print ngx_dump_config=1 $1 = 1 (gdb) continue Continuing. nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # configuration file /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/*; } # configuration file /etc/nginx/sites-enabled/default: [Inferior 1 (process 32581) exited normally] (gdb) quit 


Les étapes:

  • définir un point d'arrêt dans la fonction main ()
  • exécuter le programme
  • modifier la valeur de la variable définissant la sortie de la configuration ngx_dump_config = 1
  • continuer / terminer le programme

Comme vous pouvez le voir, la vraie configuration est différente de la nôtre, nous en sélectionnons une pièce 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; 

Voyons dans l'ordre ce qui se passe ici.

Défini par yandex / google de User-Agent :

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

Les pages de service Wordpress sont exclues :

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

Et pour ceux qui tombent dans les deux conditions ci-dessus

 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 de la page html- change «o» en «o» et «a» en «a» :

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

Exactement ainsi, la subtilité est que 'a'! = 'A' est identique à 'o'! = 'O' :

image

Ainsi, les robots des moteurs de recherche reçoivent, au lieu du texte 100% cyrillique normal, des ordures modifiées diluées avec le latin «a» et «o» . Je ne prétends pas discuter de la façon dont cela affecte le référencement, mais il est peu probable qu'un tel hachage littéral affecte positivement les positions dans le SERP.

Que disent les gars fantastiques.

Les références


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, tronçonneuses et scies électriques

Source: https://habr.com/ru/post/fr465355/


All Articles