Newbee: Hacken verhindern (erschweren)

Welche Zeichen muss man per Str_replace ersetzen, um das Hacken der eigenen Seite zB durch einen Gästebucheintrag zu verhindern? Reicht es nur die Zeichen < und > durch "ungefährlichere" zu ersetzen?

Kann Programmcode auch per Übergabewert

index.php?wert=<gefährlicher_code>

übergeben und versehntlich ausgeführt werden?
Was tut ihr dagegen?

Danke :)

  1. Moin!

    Welche Zeichen muss man per Str_replace ersetzen, um das Hacken der eigenen Seite zB durch einen Gästebucheintrag zu verhindern? Reicht es nur die Zeichen < und > durch "ungefährlichere" zu ersetzen?

    Kann Programmcode auch per Übergabewert

    index.php?wert=<gefährlicher_code>

    übergeben und versehntlich ausgeführt werden?
    Was tut ihr dagegen?

    Auf was du anspielst, nett sich Cross Site Scripting (kurz XSS), und es gibt ein recht einfaches Mittel dagegen: Kontextgerecht escapen.

    Ich habe da gerade unlängst eine Klasse im Zend Framework 2 gesehen, die für die häufigsten Kontexte in einer Webseite Methoden anbietet: https://github.com/zendframework/zf2/blob/master/library/Zend/Escaper/Escaper.php

    Wie man sieht: Es gibt Methoden für die Textausgabe in Javascript, in CSS, in URLs, und in HTML sowohl für den normalen Body-Bereich, als auch für Attributwerte.

    Für die Ausgabe im HTML-Body reicht einfach die PHP-Funktion htmlspecialchars(), allerdings sollte man lieber auch das Encoding des Strings angeben, und außerdem das Flag ENT_QUOTES, damit auch einfache Anführungszeichen behandelt werden.

    Das Escaping im URL-Kontext ist ebenfalls sehr simpel: rawurlencode().

    Alles andere ist komplizierter. Pauschal kann man sagen: Alles, was nicht garantiert ein ungefährlich nutzbares Zeichen ist, wird von der Klasse in eine (meist hexadezimale) Ersatzform gebracht, die definitiv nicht als syntax-relevantes Zeichen wirken kann.

    - Sven Rautenberg

    1. Tach!

      Für die Ausgabe im HTML-Body reicht einfach die PHP-Funktion htmlspecialchars(), allerdings sollte man lieber auch das Encoding des Strings angeben, und außerdem das Flag ENT_QUOTES, damit auch einfache Anführungszeichen behandelt werden.

      Das Encoding anzugeben schadet nicht, bringt aber hierzulande (also für die gängigen Kodierungen ISO-8859-1 (eigentlich alle der ISO-8859-Familie) und UTF-8) keine Änderung im Ergebnis. Alle von der Funktion berücksichtigten Zeichen liegen im ASCII-Bereich. Eine Verwechslungsgefahr besteht nicht, da die "ASCII-Bytes" (00-7F) nicht noch einmal anderweitig verwendet werden, also vorwärts und rückwärts eindeutig sind. Anders wäre das bei mindestens einer der asiatischen Kodierungen, da werden einige Zeichen in Kombination mit Bytes aus dem Bereich 00..7F gebildet. Da ist die Kenntnis der genauen Kodierungsvorschrift wichtig, um Datenverlust zu vermeiden.

      Im HTML-Body, also außerhalb von Tags (sprich: außerhalb von <...>) sind Anführungszeichen völlig ungefährlich. Ob da nur " oder auch ' oder keins von beiden umgeschrieben wird, ist sicherheitstechnisch egal. Anführungszeichen müssen lediglich als Inhalt eines Attributs Berücksichtigung finden, und da braucht es ENT_QUOTES nur, wenn man einfache Anführungszeichen als Attributbegrenzer nimmt. Auch hier schadet also ein generelles ENT_QUOTES nicht, bringt aber nur bedingt Punkte.

      Dass das Framework trotzdem alle Erdenklichkeiten berücksichtigt, liegt daran, dass es ja universell einsetzbar sein soll und damit potentiell mit allen Erdenklichkeiten in Berührung kommen kann. Für ein genau abgegrenzes Projekt ist dieses "Alle-Situationen-berücksitigen" nicht unbedingt erforderlich. Das heißt allerdings nicht, dass man nicht genau wissen muss, welche Behandlung welches Ergebnis bringt und wann genau sie anzuwenden ist.

      dedlfix.

      1. Anführungszeichen müssen lediglich als Inhalt eines Attributs Berücksichtigung finden, und da braucht es ENT_QUOTES nur, wenn man einfache Anführungszeichen als Attributbegrenzer nimmt.

        Und auch hier gilt, dass man nur "die anderen" escapen muss - was du ja implizit mit dem 2. Teil meinst.

        attribut="foo'bar" oder attribut='foo"bar' ist völlig ok - wenn man den Attributbegrenzer aber nicht selbst wählen kann, empfiehlt sich einfach immer alles zu escapen, das tut nicht weh und man ist auf der sicheren Seite.

    2. OK dass mit dem Escapen scheint genau dass zu sein was ich suche.

      $_POST['nachricht'] = preg_replace('/[^-_.0-9a-zA-ZäÄöÖüÜß., @]/', '', $_POST["nachricht"]);

      funktioniert soweit. Jetzt würde ich gerne zusätzlich die Zeichen ! und / erlauben. Muss diese wohl irgendwie maskieren...nur wie???

      1. OK dass mit dem Escapen scheint genau dass zu sein was ich suche.
        $_POST['nachricht'] = preg_replace('/[^-_.0-9a-zA-ZäÄöÖüÜß., @]/', '', $_POST["nachricht"]);

        Maskieren(=escapen) und die Zeichen komplett löschen sind nicht das selbe!

        Es ist auch nicht die feine Art in $_POST herumzuschreiben, z.B. wird das "fatal", wenn man gewisse Daten für 2 Verschiedene Kontexte aufbereiten muss. (Z.B. Text für eine URL und dann den gleichen Text noch für SQL)

        Wie du nun richtig maskierst hängt davon ab, was du mit den Daten anstelllen willst:
        "Denn die aufbereiteten Daten sind in aller Regel nur für das jeweilige Ausgabemedium nützlich."

        funktioniert soweit. Jetzt würde ich gerne zusätzlich die Zeichen ! und / erlauben. Muss diese wohl irgendwie maskieren...nur wie???

        In regulären Ausdrücken maskiert man mit einem vorangestelltem Backslash. Je nach dem ob du doppelte(") oder einfache(') Anführungszeichen für den String benutzt, und welches Zeichen du maskieren willst musst du eventuell den Backslash selbst escapen.

        Bsp.:
        '/[^\/]/'
        "/[^\\n]/"
        "/[^\\\"/]/"
        (hier sieht man schon am Syntax-Highlight, was maskiert wird und was nicht)

        Mehr dazu hier.

        MfG
        bubble

        --
        If "god" had intended us to drink beer, he would have given us stomachs. - David Daye
  2. ola,

    wenn du als "wert=" z.b. Zahlen erwartest, dann prüfe ob der übergebene Wert eine Zahl ist mit den vorhandenen Funktionen (is_numeric, is_int bei natürlichen Zahlen usw.)

    wenn du als "wert=" nur Buchstaben erwartest und/oder wenige andere Zeichen, kannst du einen regulären Ausdruck benutzen.

    wenn du als "wert=" eine bestimmte Länge des Strings erwartest, dann kannst du schauen ob er die Anzahl an Zeichen enthält, die du erwartest (z.B. strlen)

    mysql_real_escape_string($_GET['wert'] nimmst du bei Datenbankabfragen, ideal mit sprintf oder einfach direkt nur PDO. PDO ist der neue Standard...

    Erwarte einfach etwas und schaue was nicht zutreffen darf. Wenn es doch zutrifft, dann wurden falsche Daten übermittelt. In dem Fall brichst du jede weitere Bearbeitung der Daten ab oder du machst sie mit z.b. htmlspecialchars oder mysql_real_escape_string + weitere unschädlich. Ist aber alles situationsbedingt. Wann etwas sinnvoll ist oder wie weit du gehst etwa Eingabefehler abzufangen und zu bearbeiten, liegt letztenendes an dir.

    Das hilft dir vll. weiter, aber du solltest auch grundlegende Kenntnisse mitbringen bzw. dir das PHP Handbuch zur Hilfe nehmen.

    mfg,
    Rolfi

    1. Tach!

      wenn du als "wert=" z.b. Zahlen erwartest, dann prüfe ob der übergebene Wert eine Zahl ist mit den vorhandenen Funktionen (is_numeric, is_int bei natürlichen Zahlen usw.)

      wenn du als "wert=" nur Buchstaben erwartest und/oder wenige andere Zeichen, kannst du einen regulären Ausdruck benutzen.

      wenn du als "wert=" eine bestimmte Länge des Strings erwartest, dann kannst du schauen ob er die Anzahl an Zeichen enthält, die du erwartest (z.B. strlen)

      Das sind Prüfungen, die man vor allem aus fachlichen Gesichtspunkten durchführen sollte. Um Sicherheit zu gewährleisten sind sie nur bedingt tauglich. Besser als ein "alles weg, was nicht darf" ist ein "alles so notieren, dass es wie gewünscht interpretiert wird". Beim Alles-weg-Ansatz übersieht man gern die eine oder andere Situation. Hingegen ist die Liste der zu berücksichtigenden Zeichen für einen bestimmten Kontext üblicherweise recht klein und eindeutig bekannt.

      PDO. PDO ist der neue Standard...

      Naja, PDO ist nicht als Ablösung für native Treiber gedacht. Für einige Situationen (abseits der 08/15-Anwendungsfälle) ist es sogar nicht verwendbar. Dein Artikel ist nicht schlecht, aber mir ist er zu wertend. Ob zum Beispiel PDO nicht alle DBMSe unterstützt, kann mir egal sein, wenn ich nur MySQL und vielleicht noch PostgreSQL und noch SQLite benötige. Das wäre also keine "schlechte Meldung". OOP und prozedurale Funktionen in der mysqli-Erweiterung sind auch kein fauler Kompromiss, was das Datenbankhandling angeht, sondern sind der PHP-Philosophie geschuldet. Man mag es vielleicht sogar als Nachteil ansehen, dass PDO keine prozedurale Vorgehensweise erlaubt. Unstrittig ist, dass das Prepared-Statement-Handling um eine ganze Ecke anwenderfreundlicher ist als bei mysqli. Wie du am Ende selbst festellst, ist PDO noch lange kein Allheilmittel, weil es die Unterschiede der SQL-Dialekte nicht auszugleichen vermag. Dafür ist es auch nicht ausgelegt. Es ist lediglich eine Datenbankzugriffs-Abstraktion und keine Datenbank-Abstraktion. Und es ist auch nicht angetreten, alle Funktionen mysql(i)s zu verbessern, das kann es gar nicht vollumfänglich.

      Erwarte einfach etwas und schaue was nicht zutreffen darf.

      Das ist nicht nur aus sicherheitstechnischen Aspekten bedenklich, weil dabei zu viel übersehen werden kann.

      Wenn es doch zutrifft, dann wurden falsche Daten übermittelt. In dem Fall brichst du jede weitere Bearbeitung der Daten ab

      Das ist eine Vorgehensweise, um bestimmte Missbrauchsmuster zu erkennen. Das (sprich: Spam verhindern) will man aber eher aus inhaltlichen Gründen.

      oder du machst sie mit z.b. htmlspecialchars oder mysql_real_escape_string + weitere unschädlich.

      Das ist keine Oder-Angelegenheit. Kontextgerechtes Escaping benötigt man immer, sowohl für die "guten Daten", als auch um Injection-Angriffe zu verhindern. Die fachliche Inhaltsprüfung ist davon komplett unabhängig.

      Das hilft dir vll. weiter,

      Der SQL-Injection-Abschnitt ist veraltet. Das Feature Magic Quotes gibt es nur in alten PHP-Versionen. Es hat nämlich nur bedingt geholfen und das Problem nicht generell beseitigen können (nur Nutzereingaben behandelt, andere Datenquellen nicht berücksichtigt), sowie einige unangenehme Nebenwirkungen gehabt (wirkt am Script-Anfang und stört, wenn mit den Daten kein DBMS-Zugriff erfolgen soll). Deswegen ist es auch schon seit längerer Zeit nicht mehr per Default eingeschaltet und mittlerweile entfernt worden. Die richtige Vorgehensweise wäre kontextgerechtes Behandeln, situationsabhängig und am Ziel, nicht am Start.

      dedlfix.

      1. ola dedlfix,

        Für einige Situationen (abseits der 08/15-Anwendungsfälle) ist es sogar nicht verwendbar.

        Würdest du mir eventuell diese Fälle erläutern, oder hast du eine gute Seite, die das Problem anspricht?

        Das ist keine Oder-Angelegenheit. Kontextgerechtes Escaping benötigt man immer, sowohl für die "guten Daten", als auch um Injection-Angriffe zu verhindern. Die fachliche Inhaltsprüfung ist davon komplett unabhängig.

        Da hast du recht, das "oder" hat hier eindeutig nichts zu suchen. Man sollte immer alles ordentlich prüfen, denn auch wenn nicht vom Anwender beabsichtigt, können sich ungewollte Fehler einschleichen.

        mfg,
        Rolfi

        1. Tach!

          Für einige Situationen (abseits der 08/15-Anwendungsfälle) ist es sogar nicht verwendbar.
          Würdest du mir eventuell diese Fälle erläutern, oder hast du eine gute Seite, die das Problem anspricht?

          Es gibt einige selten verwendete Funktionalität, die von PDO nicht sinnvoll umgesetzt werden kann, wie mysqli_kill(). Bei Querys kann man sich mit mysqli aussuchen, ob das Resultset automatisch in Gänze zum Client übertragen wird und das Fetchen die Datensätze nur noch aus diesem Puffer holt, oder ob die Datensätze wirklich einzeln gefetcht werden sollen (store_- vs. use_result). Es sind nur Kleinigkeiten, die man üblicherweise nicht benötigt. Mit mysqli ist man näher dram am DBMS, so dass man solche Funktionalität direkt nutzen kann. Andere Features können mit einfachem Funktionsaufruf erledigt werden, mit PDO braucht es dazu ein Statement (Default-Datenbank innerhalb einer bestehenden Verbindung wechseln: mysqli_select_db(...) vs. USE ...).

          Man sollte immer alles ordentlich prüfen, denn auch wenn nicht vom Anwender beabsichtigt, können sich ungewollte Fehler einschleichen.

          Mit der Formulierung bin ich auch nicht so richtig einverstanden. Prüfen ist nicht immer sinnvoll machbar. Was will man zum Beispiel bei einem Kommentar prüfen? Viel mehr als die Länge ist da nicht drin - generell gesagt. In individuellen Anwendungsfällen mag das anders aussehen. Das ist also für jedes Datum eine Einzelfallentscheidung. Wenn etwas _immer_ gemacht werden sollte, dann ist es das Escaping für den jeweiligen Ausgabekontext. Und das ist keine Prüfung, sondern nur stures Zeichenersetzen (oder ein Typecast).

          dedlfix.

  3. http://wiki.selfhtml.org/wiki/Artikel:Kontextwechsel und http://wiki.selfhtml.org/wiki/Artikel:Kontextwechsel/erkennen_und_behandeln haben mir dabei immer geholfen, dir bestimmt auch.

    MfG
    bubble

    --
    If "god" had intended us to drink beer, he would have given us stomachs. - David Daye