Schützen htmlspecialchars und mysql_real_escape_string meinen PHP-Code vor Injektion?

116

Heute wurde eine Frage zu Eingabevalidierungsstrategien in Web-Apps gestellt .

Die Top-Antwort zum Zeitpunkt des Schreibens schlägt vor, PHPnur htmlspecialcharsund zu verwenden mysql_real_escape_string.

Meine Frage ist: Ist das immer genug? Gibt es mehr, was wir wissen sollten? Wo brechen diese Funktionen zusammen?

Cheekysoft
quelle

Antworten:

241

Versuchen Sie bei Datenbankabfragen immer, vorbereitete parametrisierte Abfragen zu verwenden. Die mysqliund PDOBibliotheken unterstützen dies. Dies ist unendlich sicherer als die Verwendung von Escape-Funktionen wie mysql_real_escape_string.

Ja, mysql_real_escape_stringist praktisch nur eine String-Escape-Funktion. Es ist kein Wundermittel. Alles, was es tun wird, ist, gefährliche Zeichen zu entkommen, damit sie sicher in einer einzelnen Abfragezeichenfolge verwendet werden können. Wenn Sie Ihre Eingaben jedoch nicht im Voraus bereinigen, sind Sie für bestimmte Angriffsmethoden anfällig.

Stellen Sie sich folgendes SQL vor:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Sie sollten sehen können, dass dies anfällig für Exploits ist.
Stellen Sie sich vor, der idParameter enthält den gemeinsamen Angriffsvektor:

1 OR 1=1

Es gibt keine riskanten Zeichen, die codiert werden müssen, sodass sie direkt durch den entweichenden Filter geleitet werden. Verlassen uns:

SELECT fields FROM table WHERE id= 1 OR 1=1

Dies ist ein schöner SQL-Injection-Vektor, der es dem Angreifer ermöglichen würde, alle Zeilen zurückzugeben. Oder

1 or is_admin=1 order by id limit 1

was produziert

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Dadurch kann der Angreifer die Details des ersten Administrators in diesem vollständig fiktiven Beispiel zurückgeben.

Diese Funktionen sind zwar nützlich, müssen jedoch mit Vorsicht verwendet werden. Sie müssen sicherstellen, dass alle Web-Eingaben bis zu einem gewissen Grad validiert sind. In diesem Fall sehen wir, dass wir ausgenutzt werden können, weil wir nicht überprüft haben, ob eine Variable, die wir als Zahl verwendet haben, tatsächlich numerisch ist. In PHP sollten Sie häufig eine Reihe von Funktionen verwenden, um zu überprüfen, ob Eingaben Ganzzahlen, Gleitkommazahlen, alphanumerische Zeichen usw. sind. Beachten Sie jedoch bei SQL den Wert der vorbereiteten Anweisung am meisten. Der obige Code wäre sicher gewesen, wenn es sich um eine vorbereitete Anweisung handelte, da die Datenbankfunktionen gewusst hätten, dass 1 OR 1=1es sich nicht um ein gültiges Literal handelt.

Wie für htmlspecialchars(). Das ist ein eigenes Minenfeld.

Es gibt ein echtes Problem in PHP, dass es eine ganze Auswahl verschiedener HTML-bezogener Escape-Funktionen gibt und keine klare Anleitung, welche Funktionen genau was tun.

Erstens, wenn Sie sich in einem HTML-Tag befinden, sind Sie in echten Schwierigkeiten. Ansehen

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Wir befinden uns bereits in einem HTML-Tag, sodass wir <oder> nichts Gefährliches tun müssen. Unser Angriffsvektor könnte einfach seinjavascript:alert(document.cookie)

Jetzt sieht das resultierende HTML so aus

<img src= "javascript:alert(document.cookie)" />

Der Angriff kommt direkt durch.

Es wird schlimmer. Warum? weil htmlspecialchars(wenn es so genannt wird) nur doppelte Anführungszeichen und keine einfachen Anführungszeichen codiert. Also wenn wir hätten

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Unser böser Angreifer kann jetzt ganz neue Parameter einfügen

pic.png' onclick='location.href=xxx' onmouseover='...

gibt uns

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

In diesen Fällen gibt es keine magische Kugel, Sie müssen nur die Eingabe selbst santisieren. Wenn Sie versuchen, schlechte Zeichen herauszufiltern, werden Sie sicherlich scheitern. Nehmen Sie einen Whitelist-Ansatz und lassen Sie nur die Zeichen durch, die gut sind. Schauen Sie sich das XSS-Spickzettel an, um Beispiele dafür zu finden, wie unterschiedlich Vektoren sein können

Selbst wenn Sie htmlspecialchars($string)außerhalb von HTML-Tags verwenden, sind Sie dennoch anfällig für Multi-Byte-Zeichensatz-Angriffsvektoren.

Am effektivsten können Sie eine Kombination aus mb_convert_encoding und htmlentities wie folgt verwenden.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Selbst dies macht IE6 aufgrund der Art und Weise, wie es mit UTF umgeht, anfällig. Sie können jedoch auf eine eingeschränktere Codierung wie ISO-8859-1 zurückgreifen, bis die IE6-Nutzung abfällt.

Eine ausführlichere Studie zu den Multibyte-Problemen finden Sie unter https://stackoverflow.com/a/12118602/1820

Cheekysoft
quelle
24
Das einzige, was hier übersehen wird, ist, dass das erste Beispiel für die DB-Abfrage ... ein einfaches intval () die Injektion lösen würde. Verwenden Sie immer intval () anstelle von mysqlescape ... (), wenn Sie eine Zahl und keine Zeichenfolge benötigen.
Robert K
11
und denken Sie daran, dass Sie mit parametrisierten Abfragen Daten immer als Daten und nicht als Code behandeln können. Verwenden Sie eine Bibliothek wie PDO und verwenden Sie nach Möglichkeit parametrisierte Abfragen.
Cheekysoft
9
Zwei Bemerkungen: 1. Im ersten Beispiel wären Sie sicher, wenn Sie den Parameter auch in Anführungszeichen setzen, wie z $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";. B. 2. Im zweiten Fall (Attribut mit URL) ist dies überhaupt nicht sinnvoll htmlspecialchars. In diesen Fällen sollten Sie die Eingabe mit einem URL-Codierungsschema codieren, z rawurlencode. Auf diese Weise kann ein Benutzer javascript:et al. Nicht einfügen .
Marcel Korpel
7
"HTMLspecialchars codiert nur doppelte Anführungszeichen und keine einfachen": Das stimmt nicht, es hängt davon ab, welche Flags gesetzt werden, siehe seine Parameter .
Marcel Korpel
2
Dies sollte fett gedruckt sein: Take a whitelist approach and only let through the chars which are good.Eine schwarze Liste wird immer etwas verpassen. +1
Jo Smo
10

Zusätzlich zu Cheekysofts hervorragender Antwort:

  • Ja, sie schützen Sie, aber nur, wenn sie absolut korrekt verwendet werden. Wenn Sie sie falsch verwenden, sind Sie immer noch anfällig und haben möglicherweise andere Probleme (z. B. Datenbeschädigung).
  • Bitte verwenden Sie stattdessen parametrisierte Abfragen (wie oben angegeben). Sie können sie beispielsweise über PDO oder über einen Wrapper wie PEAR DB verwenden
  • Stellen Sie sicher, dass magic_quotes_gpc und magic_quotes_runtime immer ausgeschaltet sind und niemals versehentlich eingeschaltet werden, auch nicht kurz. Dies ist ein früher und zutiefst fehlgeleiteter Versuch der PHP-Entwickler, Sicherheitsprobleme zu vermeiden (die Daten zerstören).

Es gibt nicht wirklich eine Silberkugel, um die HTML-Injection zu verhindern (z. B. Cross-Site-Scripting), aber Sie können dies möglicherweise einfacher erreichen, wenn Sie eine Bibliothek oder ein Template-System für die Ausgabe von HTML verwenden. Lesen Sie dazu die Dokumentation, um zu erfahren, wie Sie den Dingen angemessen entkommen können.

In HTML müssen die Dinge je nach Kontext unterschiedlich maskiert werden. Dies gilt insbesondere für Zeichenfolgen, die in Javascript eingefügt werden.

MarkR
quelle
3

Ich würde den obigen Beiträgen definitiv zustimmen, aber ich muss eine kleine Sache als Antwort auf die Antwort von Cheekysoft hinzufügen, insbesondere:

Versuchen Sie bei Datenbankabfragen immer, vorbereitete parametrisierte Abfragen zu verwenden. Die Bibliotheken mysqli und PDO unterstützen dies. Dies ist unendlich sicherer als die Verwendung von Escape-Funktionen wie mysql_real_escape_string.

Ja, mysql_real_escape_string ist praktisch nur eine String-Escape-Funktion. Es ist kein Wundermittel. Alles, was es tun wird, ist, gefährliche Zeichen zu entkommen, damit sie sicher in einer einzelnen Abfragezeichenfolge verwendet werden können. Wenn Sie Ihre Eingaben jedoch nicht im Voraus bereinigen, sind Sie für bestimmte Angriffsmethoden anfällig.

Stellen Sie sich folgendes SQL vor:

$ result = "SELECT Felder FROM Tabelle WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Sie sollten sehen können, dass dies anfällig für Exploits ist. Stellen Sie sich vor, der ID-Parameter enthält den allgemeinen Angriffsvektor:

1 ODER 1 = 1

Es gibt keine riskanten Zeichen, die codiert werden müssen, sodass sie direkt durch den entweichenden Filter geleitet werden. Verlassen uns:

SELECT-Felder aus der Tabelle WHERE id = 1 OR 1 = 1

Ich habe eine kleine Funktion codiert, die ich in meine Datenbankklasse eingefügt habe und die alles entfernt, was keine Zahl ist. Es verwendet preg_replace, daher gibt es wahrscheinlich eine etwas optimierte Funktion, aber es funktioniert zur Not ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Also anstatt zu benutzen

$ result = "SELECT Felder FROM Tabelle WHERE id =" .mysqlrealescapestring ("1 OR 1 = 1");

ich würde ... benutzen

$ result = "SELECT Felder FROM Tabelle WHERE id =" .Numbers ("1 OR 1 = 1");

und es würde die Abfrage sicher ausführen

SELECT Felder FROM Tabelle WHERE id = 111

Sicher, das hat es nur daran gehindert, die richtige Zeile anzuzeigen, aber ich denke nicht, dass dies ein großes Problem für jeden ist, der versucht, SQL in Ihre Site einzufügen;)

BrilliantWinter
quelle
1
Perfekt! Dies ist genau die Art von Desinfektion, die Sie benötigen. Der ursprüngliche Code ist fehlgeschlagen, weil nicht überprüft wurde, ob eine Zahl numerisch ist. Ihr Code macht das. Sie sollten Numbers () für alle Ganzzahl-Vars aufrufen, deren Werte von außerhalb der Codebasis stammen.
Cheekysoft
1
Es ist erwähnenswert, dass intval () hierfür einwandfrei funktioniert, da PHP für Sie automatisch Ganzzahlen zu Zeichenfolgen zwingt.
Adam Ernst
11
Ich bevorzuge intval. Es wird 1abc2 zu 1, nicht 12.
jmucchiello
1
intval ist besser, besonders bei ID. Die meiste Zeit, wenn es beschädigt wurde, ist es genau wie oben, 1 oder 1 = 1. Sie sollten wirklich nicht den Ausweis anderer Leute verlieren. Intval gibt also die richtige ID zurück. Danach sollten Sie überprüfen, ob der ursprüngliche und der gereinigte Wert identisch sind. Es ist eine großartige Möglichkeit, nicht nur Angriffe zu stoppen, sondern auch die Angreifer zu finden.
Triunenature
2
Die falsche Zeile wäre katastrophal, wenn Sie persönliche Daten anzeigen und die Informationen eines anderen Benutzers sehen würden! stattdessen wäre es besser zu überprüfenreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte
2

Ein wichtiges Teil dieses Puzzles sind Kontexte. Jemand, der "1 OR 1 = 1" als ID sendet, ist kein Problem, wenn Sie jedes Argument in Ihrer Abfrage zitieren:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Was in ... endet:

SELECT fields FROM table WHERE id='1 OR 1=1'

das ist unwirksam. Da Sie der Zeichenfolge entkommen, kann die Eingabe nicht aus dem Zeichenfolgenkontext herausbrechen. Ich habe dies bis zur Version 5.0.45 von MySQL getestet, und die Verwendung eines Zeichenfolgenkontexts für eine Ganzzahlspalte verursacht keine Probleme.

Lucas Oman
quelle
15
und dann starte ich meinen Angriffsvektor mit dem Multi-Byte-Zeichen 0xbf27, das in Ihrer latin1-Datenbank von der Filterfunktion als 0xbf5c27 konvertiert wird - ein einzelnes Multibyte-Zeichen, gefolgt von einem einfachen Anführungszeichen.
Cheekysoft
8
Versuchen Sie, sich nicht vor einem einzigen bekannten Angriffsvektor zu schützen. Sie werden Ihren Schwanz bis zum Ende der Zeit verfolgen, indem Sie Patch für Patch auf Ihren Code anwenden. Wenn Sie sich zurücklehnen und die allgemeinen Fälle betrachten, erhalten Sie sichereren Code und eine sicherheitsorientiertere Denkweise.
Cheekysoft
Genau; Im Idealfall verwendet OP vorbereitete Anweisungen.
Lucas Oman
1
Obwohl das Zitieren von Argumenten, die in diesem Beitrag vorgeschlagen werden, nicht narrensicher ist, werden viele der gängigen Angriffe vom Typ 1 ODER 1 = 1 gemildert, sodass es erwähnenswert ist.
Nachteule
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Funktioniert gut, noch besser auf 64-Bit-Systemen. Beachten Sie die Einschränkungen Ihres Systems bei der Adressierung großer Zahlen. Bei Datenbank-IDs funktioniert dies jedoch in 99% der Fälle hervorragend.

Sie sollten auch eine einzige Funktion / Methode zum Reinigen Ihrer Werte verwenden. Auch wenn diese Funktion nur ein Wrapper für mysql_real_escape_string () ist. Warum? Denn eines Tages, wenn ein Exploit für Ihre bevorzugte Methode zum Bereinigen von Daten gefunden wird, müssen Sie diese nur an einer Stelle aktualisieren und nicht systemweit suchen und ersetzen.

cnizzardini
quelle
-3

Warum, oh WARUM, würden Sie keine Anführungszeichen für Benutzereingaben in Ihre SQL-Anweisung aufnehmen? scheint ziemlich dumm nicht zu! Das Einfügen von Anführungszeichen in Ihre SQL-Anweisung würde "1 oder 1 = 1" zu einem erfolglosen Versuch machen, nicht wahr?

Nun sagen Sie: "Was ist, wenn der Benutzer ein Anführungszeichen (oder doppelte Anführungszeichen) in die Eingabe einfügt?"

Nun, einfache Lösung dafür: Entfernen Sie einfach die vom Benutzer eingegebenen Anführungszeichen. zB : input =~ s/'//g;. jetzt scheint es mir sowieso, dass Benutzereingaben gesichert wären ...

Jarett L.
quelle
"Warum, oh WARUM, würden Sie keine Anführungszeichen für Benutzereingaben in Ihre SQL-Anweisung aufnehmen?" - Die Frage sagt nichts darüber aus, keine Benutzereingaben zu zitieren.
Quentin
1
"Nun, einfache Lösung dafür" - Schreckliche Lösung dafür. Das wirft Daten weg. Die in der Frage selbst erwähnte Lösung ist ein besserer Ansatz.
Quentin
Ich bin damit einverstanden, dass die Frage nicht das Zitieren von Benutzereingaben betrifft, aber es scheint immer noch so, als würde man die Eingabe nicht zitieren. und ich würde lieber Daten werfen als schlechte Daten eingeben. Im Allgemeinen möchten Sie bei einem Injektionsangriff diese Daten sowieso NICHT ... richtig?
Jarett L
"Obwohl ich damit einverstanden bin, dass die Frage nicht das Zitieren von Benutzereingaben betrifft, scheint es immer noch so, als würde man die Eingabe nicht zitieren." - Nein, das tut es nicht. Die Frage zeigt es nicht so oder so.
Quentin
1
@JarettL Gewöhnen Sie sich entweder daran, vorbereitete Anweisungen zu verwenden, oder gewöhnen Sie sich daran, dass Bobby Tables Ihre Daten jeden Dienstag zerstört . Parametrisiertes SQL ist der beste Weg, um sich vor SQL-Injection zu schützen. Sie müssen keine "SQL-Injection-Checks" durchführen, wenn Sie eine vorbereitete Anweisung verwenden. Sie sind extrem einfach zu implementieren (und machen den Code meiner Meinung nach VIEL einfacher zu lesen), schützen vor verschiedenen Besonderheiten der String-Verkettung und der SQL-Injection und das Beste ist, dass Sie das Rad nicht neu erfinden müssen, um ihn zu implementieren .
Siyual