Linuchs: Wie kann PHP / SQL gleichzeitige Anfragen abwehren?

Moin,

immer wieder wird mein Server attackiert mit zeitgleichen Anfragen unter derselben REMOTE_ADDR. So wie die Parameter aussehen, werden da Listen mit Links (Orte, Events, Veranstaltungstypen) abgearbeitet.

Wenn der Durchlauf 5 sec überschreitet, wird eine Mail generiert mit der Ende-Zeit. Ich weiß nicht, wieviele der Attacken innerhalb 5 sec durchgelaufen sind, ohne eine Mail auszulösen. Es könnten dutzende oder hunderte sein. Eine einzelne dieser Seiten benötigt unter 0,2 sec Durchlaufzeit. Keine Ahnung, wieviele parallel bearbeitet werden.

Als Gegenmaßnahme speichere ich Startzeit und IP, dieselbe IP wird erst 3 sec später wieder bedient.

Doch das Wegschreiben der IP in die Datenbank dauert und so kann mein Trick wohl unterlaufen werden.

Wie kann ich mich wehren?

Links erst mit JS erst freigeben, wenn draufgeklickt wurde?

fragt Linuchs

  1. Als Gegenmaßnahme speichere ich Startzeit und IP, dieselbe IP wird erst 3 sec später wieder bedient.

    Doch das Wegschreiben der IP in die Datenbank dauert

    Das liest sich wie:

    • „Problem: Durch die vielen Zugriffe von einzelnen IPs wird meine Datenbank überlastet.“
    • „Ha! Ich hab's! Ich schreibe die Zugriffe aller Clients in diese Datenbank und zähle jeweils wie oft von dieser IP schon zugegriffen wird!“

    und entspricht, vom Schema her, dieser Quer„denker“-Lösung:

    • „Ich komme mit dem Fahrrad kaum den Berg hoch.“
    • „Ha! Ich hab's! Wenn ich mir einen schweren Rucksack aufsetze wird das bestimmt besser!“

    oder dieser:

    • „Die Gäste beschweren sich, dass die Bedienung so langsam erfolgt.“
    • „Ha! Ich hab's! Vor dem Servieren sollen die Kellner mit dem ICE zur nächsten Stadt und zurück und den Gästen sagen, dass sie mit 250 Sachen unterwegs waren!

    Jetzt denken wir mal nicht „quer“, sondern beachten die Fakten und handeln zielorientiert und pragmatisch:

    Die beste Lösung für diese Problemgruppe heißt „mod_evasive“. Das muss Dein Hoster installieren. Als Kunde eines Massenhosters kannst Du (hoffentlich) in der .htacces den Zugriff von der betreffenden IP dauerhaft verbieten.

    Hast Du schon versucht, den Abfrager mit der robots.txt zu überzeugen? Da kann man auch einen Abstand zwischen zwei Requests erbitten oder einen robot bitten, draußen zu bleiben. Ist er ignorant und kommt der „Bitte“ nicht nach, dann wird er eben gesperrt.

    1. Hallo Raketenwilli,

      mod_evasive ist seit 5 Jahren unberührt, bei StackOverflow benutzte jemand das böse Wort "abandoned".

      Ob ein Hoster ein solches Modul installiert, ist die große Frage.

      Laut Doku ist mod_evasive für Apache 1.3 und 2.0 vorgesehen, Linuchs arbeitet laut Response Header mit Apache 2.4.25. Würde das funktionieren?

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Moin Rolf

        mod_evasive ist seit 5 Jahren unberührt,

        Der Grund dafür kann sein, dass es „perfekt“ ist und dass der Autor nicht einmal daran denkt, diesen Zustand durch das Hinzufügen von Gimmicks dahingehend zu „verbessern“, dass es dann langsamer wird und sich Fehler einschleichen.

        bei StackOverflow benutzte jemand das böse Wort "abandoned".

        Vielleicht sollte er den Autor fragen.

        Ob ein Hoster ein solches Modul installiert, ist die große Frage.

        apt list *evasive*
        Auflistung… Fertig
        libapache2-mod-evasive/jammy 1.10.1-4 arm64
        libapache2-mod-evasive/jammy 1.10.1-4 armhf
        

        Auch andere Linux-Distros haben es im Repository.

        Laut Doku ist mod_evasive für Apache 1.3 und 2.0 vorgesehen, Linuchs arbeitet laut Response Header mit Apache 2.4.25. Würde das funktionieren?

        Guckst Du:

        https://home.fastix.org/phpinfo.php?show_apache_only=true

        Ein versehentlicher Funktionstest sagte mir vor zwei Wochen, dass es auch tut was es soll.

        1. Laut Doku ist mod_evasive für Apache 1.3 und 2.0 vorgesehen,

          Naja. Das ledige Thema Dokumentation. Macht mancher nicht gern („Warum für Dich aufschreiben? Guckst Du Quelltext!"), liegt wohl an den Genen. Wohl deshalb hab ich mit mod_evasive einen ausgesprochen brüderlichen Dissens.

          Jedenfalls die Maintainer von Ubuntu sind der Auffassung, dass das von Ubuntu aktuell gelieferte mod_evasive sogar mindestens einen Apache 2.4.16 voraussetzt.

          Package: libapache2-mod-evasive
          Version: 1.10.1-4
          Priority: optional
          Section: universe/web
          Source: libapache-mod-evasive
          Origin: Ubuntu
          Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
          Original-Maintainer: Alberto Gonzalez Iniesta <agi@inittab.org>
          Bugs: https://bugs.launchpad.net/ubuntu/+filebug
          Installed-Size: 49,2 kB
          Depends: libc6 (>= 2.17), apache2-api-20120211, apache2-bin (>= 2.4.16), bsd-mailx | mailx
          Homepage: https://github.com/jzdziarski/mod_evasive
          Download-Size: 14,5 kB
          APT-Sources: http://ports.ubuntu.com jammy/universe arm64 Packages
          Description: evasive module to minimize HTTP DoS or brute force attacks
          

          Hinweis an Linuchs: Wenn er tatsächlich das Versenden von Mails aktiviert, dann sollte er sich bitte merken, dass er das getan hat - und am Smartphone abstellen, dass dieses bei eingehenden Mails irgendwelche Geräusche oder Lichtzeichen macht. Sowas wirkt einer entstehenden Verunsicherung und Schlaflosigkeit vor.

          1. Wie um mir zu beweisen, dass es geht, wollte ein Möchtergernhackerchen das mal testen: mod_evasive hat dann „zugebissen“:

            (Die IP 13.80.71.19 gehört zur Microsoft-Cloud (MSFT), das sind also keine „personenbezogenen Daten“)

            13.80.71.19 - - [26/Jul/2022:09:24:45 +0000] "GET /showFile.php?file=/etc/passwd HTTP/1.1" 404 12928 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:46 +0000] "GET /showFile.php?file=../etc/passwd HTTP/1.1" 404 8178 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:46 +0000] "GET /showFile.php?file=../../etc/passwd HTTP/1.1" 404 8190 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:47 +0000] "GET /showFile.php?file=../../../etc/passwd HTTP/1.1" 404 8202 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:48 +0000] "GET /showFile.php?file=../../../../etc/passwd HTTP/1.1" 404 8214 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:48 +0000] "GET /showFile.php?file=../../../../../etc/passwd HTTP/1.1" 404 8255 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=/etc/passwd%00 HTTP/1.1" 500 1536 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=../etc/passwd%00 HTTP/1.1" 500 6294 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=../../etc/passwd%00 HTTP/1.1" 500 6294 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=../../../etc/passwd%00 HTTP/1.1" 500 6294 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=../../../../etc/passwd%00 HTTP/1.1" 500 6294 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:49 +0000] "GET /showFile.php?file=../../../../../etc/passwd%00 HTTP/1.1" 500 6294 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:50 +0000] "GET /showFile.php?file=%2Fetc%2Fpasswd HTTP/1.1" 404 12928 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:50 +0000] "GET /showFile.php?file=..%2Fetc%2Fpasswd HTTP/1.1" 404 8178 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:51 +0000] "GET /showFile.php?file=..%2F..%2Fetc%2Fpasswd HTTP/1.1" 404 8190 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:52 +0000] "GET /showFile.php?file=..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1" 404 8202 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:52 +0000] "GET /showFile.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1" 404 8214 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:53 +0000] "GET /showFile.php?file=..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1" 404 8255 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:53 +0000] "GET /showFile.php?file=%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7678 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:54 +0000] "GET /showFile.php?file=..%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7688 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:54 +0000] "GET /showFile.php?file=..%2F..%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7703 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:55 +0000] "GET /showFile.php?file=..%2F..%2F..%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7718 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:55 +0000] "GET /showFile.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7733 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:56 +0000] "GET /showFile.php?file=..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd%2500 HTTP/1.1" 404 7748 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:56 +0000] "GET /showFile.php?file=\\etc\\passwd HTTP/1.1" 404 6072 "-" "-"
            13.80.71.19 - - [26/Jul/2022:09:24:56 +0000] "GET /showFile.php?file=..\\etc\\passwd HTTP/1.1" 403 7273 "-" "-"
            

            Letzte Zeile: Status 403 (Peng)

            Und:

            grep '13.80.71.19' /var/log/syslog
            Jul 26 09:24:56 raspi4 mod_evasive[2105]: Blacklisting address 13.80.71.19: possible DoS attack.
            Jul 26 09:24:57 raspi4 fwblock4time: IP 13.80.71.19 will blocked temporary. Block end in 'now +10minutes'
            

            (Alle Zeiten in GMT)

  2. Lieber Linuchs,

    schönes Bild. Keine neuen Erkenntnisse in Deiner eigentlichen Sache...

    [Diesen Thread ausblenden] click

    Liebe Grüße

    Felix Riesterer

  3. Hallo Linuchs,

    wenn Du DOS Attacken unterstellst, wäre zuerst einmal eine Anfrage bei deinem Hoster fällig, welche Maßnahmen er dagegen in seinem Portfolio hat. Vielleicht ist ja alles ganz einfach und nur einen kleinen Konfigurationseintrag entfernt.

    Wenn Du Dich selbst wehren musst, tja. Ich hätte fast gesagt: Verwende die Session - PHP sorgt von sich aus dafür, dass immer nur ein Request an die Session herankommt. Aber wenn das ein Bot ist, dann wird er auch keinen Session-Keks mitbringen und dann nützt es nichts.

    Das von Jörg genannte mod_evasive hat gewisse Probleme, die habe ich in meiner Antwort an ihn aufgezeigt. Grundsätzlich wäre dieses Modul eine gute Idee, praktisch muss man das validieren.

    Eine andere manuelle Möglichkeit wäre, zu Beginn eines Requests eine Datei zu erzeugen, in deren Name die Absender-IP Adresse codiert ist, und diese Datei brutal zu locken. Kommt von der gleichen IP ein anderer Request herein, wird er durch den Lock blockiert und du kannst ihn mit HTTP Status 500 beantworten (oder Status 418, um den Bot zu verwirren). Endet der PHP Request, kannst Du die Datei schließen und löschen. Kleiner Nachteil: Falls Du tatsächlich mal in die Lage kommst, zwei Requeste für eine User parallel ausführen zu wollen, wirkt die Sperre auch dort. Und wenn Du von einer Bot-Farm geDDOSt wirst, hilft eine IP Sperre auch nichts. Ob das der Fall ist, kannst Du nur mit vollständiger Protokollierung aller Requests erkennen (die Du aber nicht zu lange speichern darfst, DSGVO sei Dank - kurzfristig sollte OK sein, weil die Analyse eines DOS-Angriffs ein berechtigtes Interesse ist).

    Ich weiß nicht, wieviele der Attacken innerhalb 5 sec durchgelaufen sind, ohne eine Mail auszulösen.

    Sorry - aber das kann es nicht sein. Der Webserver sollte jeden Request loggen; als Serverbetreiber musst Du darauf zugreifen können.

    Eine dritte Möglichkeit kann sein, die Ermittlung größerer Datenmengen grundsätzlich nach einer gewissen Zeit abzubrechen. Verwende eine Abbruchzeit, in der die Requeste NORMALERWEISE erfolgreich durchlaufen.

    Eine weitere Möglichkeit könnte mysqli_options(MYSQLI_OPT_CONNECT_TIMEOUT) sein. Ich würde nämlich unterstellen (was Du natürlich erstmal untersuchen müsstest), dass der Zeitfresser der Connect zur DB ist, weil die Maximalzahl der Connections überschritten wird und die Folge-Connects in einer Queue laden. Dafür musst Du allerdings den Connect zur DB etwas modifizieren. Statt einem einzigen Aufruf mysqli_connect (bzw. new mysqli()) verwendest Du

    $db = mysqli_init();
    if (!$db) { /* fail */ }
    if (!$db->options(MYSQLI_OPT_CONNECT_TIMEOUT, 2)) { /* fail */ }
    if (!$db->real_connect($host, $user, $password, $database)) { /* fail */ }
    

    Das geht natürlich auch mit dem funktionalen Interface (mysqli_options, mysqli_real_connect). Falls dein altehrwürdiger Betrieb noch mit mysql arbeitet (ich glaube, da war was), musst Du vor dem Connect mittels ini_set die Option mysql.connect_timeout passend setzen.

    Auf diese Weise würdest Du auf einen Fehler laufen, wenn der Connect länger als 2 Sekunden dauert. Diese Connection-Zeit wirst Du im Normalbetrieb nie erleben.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      danke für die ausführliche Beratung.

      Habe einen eigenen Server, kann mit root zugreifen. Aber als Anwendungsprogrammierer wenig Ahnung von dessen Innenleben so wie ein Autofahrer vom Innenleben des Motors und Getriebe.

      1. @@Linuchs

        Habe einen eigenen Server, kann mit root zugreifen. Aber als Anwendungsprogrammierer wenig Ahnung von dessen Innenleben so wie ein Autofahrer vom Innenleben des Motors und Getriebe.

        Sehr gute Voraussetzungen, um einen eigenen Webserver zu betreiben. 😱

        🖖 Живіть довго і процвітайте

        --
        When the power of love overcomes the love of power the world will know peace.
        — Jimi Hendrix
      2. Hallo Linuchs,

        Habe einen eigenen Server, kann mit root zugreifen.

        Na dann, rein mit dem mod_evasive und gut ist.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Schade, dass Rückmeldungen von @Linuchs ausbleiben.

          Etwas wie …

          Aber als Anwendungsprogrammierer wenig Ahnung von dessen Innenleben so wie ein Autofahrer vom Innenleben des Motors und Getriebe.

          … kann nämlich nur der erste Schritt sein. Nämlich dazu, sich entweder mit mit den Grundlagen eigenen Handelns vertraut zu machen oder halt wenigstens Ratschläge anzunehmen.

          Und natürlich will der Ratgebende wissen, ob es beim Empfänger funktioniert hat und ob weitere Beratschlagungen notwendig sind.

          Oh. Und das auf meinem Server zuverlässig funktionierende Zeug hatte in den letzten 3 Jahren auch kein Update nötig. Vielleicht kommt eines auf ufw zur Steuerung der Firewall (statt des derzeitig verwendeten Befehls iptables, welcher aber noch für Jahre funktionieren wird… und eine „Erweiterung“ auf IPv6)

          1. Lieber Raketenwilli,

            Schade, dass Rückmeldungen von @Linuchs ausbleiben.

            es schmälert den Eindruck, dass er wirklich Hilfe will und vermittelt stattdessen den Eindruck, dass er seine Denkweise bestätigt haben will, um dann den Hinweis zu bekommen, wie man damit an das von ihm gewünschte Ziel gelangt.

            Oder er kann nur noch für unmittelbar zielführende Hinweise die Zeit aufbringen, hier zu posten.

            Liebe Grüße

            Felix Riesterer