Wie kann ich Benutzereingaben mit PHP bereinigen?

1124

Gibt es irgendwo eine Catchall-Funktion, mit der Benutzereingaben für SQL-Injection- und XSS-Angriffe bereinigt werden können, während bestimmte Arten von HTML-Tags weiterhin zulässig sind?

Brent
quelle
42
Verwenden Sie heutzutage PDO oder MySQLi, um eine SQL-Injektion zu vermeiden.
Francisco Presencia
76
Die Verwendung von PDO oder MySQLi reicht nicht aus. Wenn Sie Ihre SQL-Anweisungen beispielsweise mit nicht vertrauenswürdigen Daten erstellen, select * from users where name='$name'spielt es keine Rolle, ob Sie PDO, MySQLi oder MySQL verwenden. Du bist immer noch in Gefahr. Sie müssen parametrisierte Abfragen verwenden oder, falls erforderlich, Escape-Mechanismen für Ihre Daten verwenden, dies ist jedoch weitaus weniger vorzuziehen.
Andy Lester
26
@AndyLester Wollen Sie damit sagen, dass jemand PDO ohne vorbereitete Anweisungen verwendet? :)
64
Ich sage, dass "PDO oder MySQLi verwenden" nicht ausreicht, um Anfängern zu erklären, wie man sie sicher verwendet. Sie und ich wissen, dass vorbereitete Aussagen wichtig sind, aber ich gehe nicht davon aus, dass jeder, der diese Frage liest, sie kennt. Deshalb habe ich die expliziten Anweisungen hinzugefügt.
Andy Lester
30
Andys Kommentar ist völlig gültig. Ich habe kürzlich meine MySQL-Website auf PDO umgestellt und dachte, ich sei jetzt irgendwie sicher vor Injektionsangriffen. Erst während des Prozesses wurde mir klar, dass einige meiner SQL-Anweisungen noch mithilfe von Benutzereingaben erstellt wurden. Ich habe das dann mit vorbereiteten Aussagen behoben. Für einen Anfänger ist es nicht ganz klar, dass es einen Unterschied gibt, da viele Experten den Kommentar zur Verwendung von PDO verwerfen, aber nicht angeben, dass vorbereitete Anweisungen erforderlich sind. Die Annahme ist, dass dies offensichtlich ist. Aber nicht für einen Anfänger.
GhostRider

Antworten:

1184

Es ist ein weit verbreitetes Missverständnis, dass Benutzereingaben gefiltert werden können. PHP hat sogar eine (inzwischen veraltete) "Funktion", die als magische Anführungszeichen bezeichnet wird und auf dieser Idee aufbaut. Das ist Unsinn. Vergessen Sie das Filtern (oder Reinigen oder wie auch immer die Leute es nennen).

Was Sie tun sollten, um Probleme zu vermeiden, ist ganz einfach: Wenn Sie eine Zeichenfolge in Fremdcode einbetten, müssen Sie sie gemäß den Regeln dieser Sprache maskieren. Wenn Sie beispielsweise eine Zeichenfolge in SQL-Targeting für MySQL einbetten, müssen Sie die Zeichenfolge zu diesem Zweck mit der MySQL-Funktion maskieren ( mysqli_real_escape_string). (Bei Datenbanken ist die Verwendung vorbereiteter Anweisungen nach Möglichkeit ein besserer Ansatz.)

Ein weiteres Beispiel ist HTML: Wenn Sie Zeichenfolgen in HTML-Markups einbetten, müssen Sie es mit maskieren htmlspecialchars. Dies bedeutet, dass jede einzelne echooder printAnweisung verwendet werden sollte htmlspecialchars.

Ein drittes Beispiel könnten Shell-Befehle sein: Wenn Sie Zeichenfolgen (z. B. Argumente) in externe Befehle einbetten und mit diesen aufrufen möchten exec, müssen Sie escapeshellcmdund verwenden escapeshellarg.

Und so weiter und so fort ...

Der einzige Fall, in dem Sie Daten aktiv filtern müssen, ist, wenn Sie vorformatierte Eingaben akzeptieren. Wenn Sie beispielsweise zulassen, dass Ihre Benutzer HTML-Markups veröffentlichen, die auf der Site angezeigt werden sollen. Sie sollten dies jedoch unbedingt vermeiden, da es immer eine potenzielle Sicherheitslücke darstellt, egal wie gut Sie es filtern.

troelskn
quelle
245
"Dies bedeutet, dass jede einzelne Echo- oder Druckanweisung htmlspecialchars verwenden sollte" - natürlich meinen Sie "jede ... Anweisung, die Benutzereingaben ausgibt"; htmlspecialchars () - ifying "echo 'Hallo Welt!';" wäre verrückt;)
Bobby Jack
10
Es gibt einen Fall, in dem Filterung meiner Meinung nach die richtige Lösung ist: UTF-8. Sie möchten nicht, dass Ihre gesamte Anwendung ungültige UTF-8-Sequenzen enthält (je nach Codepfad erhalten Sie möglicherweise unterschiedliche Fehlerbehebungen), und UTF-8 kann problemlos gefiltert (oder abgelehnt) werden.
Kornel
6
@jbyrd - nein, LIKE verwendet eine spezielle reguläre Ausdruckssprache. Sie müssen Ihre Eingabezeichenfolge zweimal maskieren - einmal für den regulären Ausdruck und einmal für die Codierung der MySQL-Zeichenfolge. Es ist Code in Code in Code.
Troelskn
6
In diesem Moment mysql_real_escape_stringist veraltet. Es wird heutzutage als gute Praxis angesehen, vorbereitete Anweisungen zu verwenden, um eine SQL-Injection zu verhindern. Wechseln Sie also entweder zu MySQLi oder PDO.
Marcel Korpel
4
Weil Sie die Angriffsfläche begrenzen. Wenn Sie frühzeitig (bei der Eingabe) bereinigen, müssen Sie sicher sein, dass die Anwendung keine weiteren Lücken aufweist, durch die fehlerhafte Daten gelangen können. Wenn Sie es dagegen zu spät tun, muss Ihre Ausgabefunktion nicht darauf vertrauen, dass sie sichere Daten erhält - sie geht einfach davon aus, dass alles unsicher ist.
Troelskn
217

Versuchen Sie nicht, die SQL-Injection durch Bereinigen der Eingabedaten zu verhindern.

Lassen Sie stattdessen nicht zu, dass Daten zum Erstellen Ihres SQL-Codes verwendet werden . Verwenden Sie vorbereitete Anweisungen (dh die Verwendung von Parametern in einer Vorlagenabfrage), die gebundene Variablen verwenden. Nur so kann eine Garantie gegen SQL-Injection gewährleistet werden.

Weitere Informationen zum Verhindern der SQL-Injection finden Sie auf meiner Website http://bobby-tables.com/ .

Andy Lester
quelle
18
Oder besuchen Sie die offizielle Dokumentation und erfahren Sie mehr über gU und vorbereitete Erklärungen. Winzige Lernkurve, aber wenn Sie SQL ziemlich gut kennen, können Sie sich problemlos anpassen.
Ein Codierer
2
Für den speziellen Fall von SQL Injection ist dies die richtige Antwort!
Scott Arciszewski
4
Beachten Sie, dass vorbereitete Anweisungen keine Sicherheit hinzufügen, parametrisierte Abfragen jedoch. Sie sind einfach sehr einfach zusammen in PHP zu verwenden.
Basic
Es ist nicht der einzige garantierte Weg. Hex die Eingabe und Unhex in Abfrage wird auch verhindern. Auch Hex-Angriffe sind nicht möglich, wenn Sie Hexing rechts verwenden.
Ramon Bakker
Was ist, wenn Sie etwas Spezielles eingeben, z. B. E-Mail-Adressen oder Benutzernamen?
Abraham Brookes
79

Nein. Sie können Daten nicht generisch filtern, ohne einen Kontext dafür zu haben, wofür sie bestimmt sind. Manchmal möchten Sie eine SQL-Abfrage als Eingabe verwenden, und manchmal möchten Sie HTML als Eingabe verwenden.

Sie müssen Eingaben in einer Whitelist filtern - stellen Sie sicher, dass die Daten einer Spezifikation Ihrer Erwartungen entsprechen. Dann müssen Sie es maskieren, bevor Sie es verwenden, abhängig vom Kontext, in dem Sie es verwenden.

Der Prozess des Escape-Datenverkehrs für SQL - um die SQL-Injection zu verhindern - unterscheidet sich stark vom Prozess des Escape-Daten für (X) HTML, um XSS zu verhindern.

Daniel Papasian
quelle
52

PHP hat jetzt die neuen netten filter_input-Funktionen, die Sie zum Beispiel davon befreien, den ultimativen E-Mail-Regex zu finden, da es einen integrierten FILTER_VALIDATE_EMAIL-Typ gibt

Meine eigene Filterklasse (verwendet JavaScript, um fehlerhafte Felder hervorzuheben) kann entweder durch eine Ajax-Anfrage oder einen normalen Formularbeitrag initiiert werden. (siehe das Beispiel unten)

/**
 *  Pork.FormValidator
 *  Validates arrays or properties by setting up simple arrays. 
 *  Note that some of the regexes are for dutch input!
 *  Example:
 * 
 *  $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
 *  $required = array('name', 'email', 'alias', 'pwd');
 *  $sanitize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanitize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanitize($_POST);
 *      // now do your saving, $_POST has been sanitized.
 *      die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
 *  }
 *  else
 *  {
 *      die($validator->getScript());
 *  }   
 *  
 * To validate just one element:
 * $validated = new FormValidator()->validate('blah@bla.', 'email');
 * 
 * To sanitize just one element:
 * $sanitized = new FormValidator()->sanitize('<b>blah</b>', 'string');
 * 
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2008
 * @version 1.0
 * @access public
 */
class FormValidator
{
    public static $regexes = Array(
            'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
            'amount' => "^[-]?[0-9]+\$",
            'number' => "^[-]?[0-9,]+\$",
            'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
            'not_empty' => "[a-z0-9A-Z]+",
            'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
            'phone' => "^[0-9]{10,11}\$",
            'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
            'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
            'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
            '2digitopt' => "^\d+(\,\d{2})?\$",
            '2digitforce' => "^\d+\,\d\d\$",
            'anything' => "^[\d\D]{1,}\$"
    );
    private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;


    public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
    {
        $this->validations = $validations;
        $this->sanitations = $sanitations;
        $this->mandatories = $mandatories;
        $this->errors = array();
        $this->corrects = array();
    }

    /**
     * Validates an array of items (if needed) and returns true or false
     *
     */
    public function validate($items)
    {
        $this->fields = $items;
        $havefailures = false;
        foreach($items as $key=>$val)
        {
            if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false) 
            {
                $this->corrects[] = $key;
                continue;
            }
            $result = self::validateItem($val, $this->validations[$key]);
            if($result === false) {
                $havefailures = true;
                $this->addError($key, $this->validations[$key]);
            }
            else
            {
                $this->corrects[] = $key;
            }
        }

        return(!$havefailures);
    }

    /**
     *
     *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
     */
    public function getScript() {
        if(!empty($this->errors))
        {
            $errors = array();
            foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }

            $output = '$$('.implode(',', $errors).').addClass("unvalidated");'; 
            $output .= "new FormValidator().showMessage();";
        }
        if(!empty($this->corrects))
        {
            $corrects = array();
            foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
            $output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';   
        }
        $output = "<script type='text/javascript'>{$output} </script>";
        return($output);
    }


    /**
     *
     * Sanitizes an array of items according to the $this->sanitations
     * sanitations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanitations = array('fieldname', 'otherfieldname'=>'float');
     */
    public function sanitize($items)
    {
        foreach($items as $key=>$val)
        {
            if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
            $items[$key] = self::sanitizeItem($val, $this->validations[$key]);
        }
        return($items);
    }


    /**
     *
     * Adds an error to the errors array.
     */ 
    private function addError($field, $type='string')
    {
        $this->errors[$field] = $type;
    }

    /**
     *
     * Sanitize a single var according to $type.
     * Allows for static calling to allow simple sanitization
     */
    public static function sanitizeItem($var, $type)
    {
        $flags = NULL;
        switch($type)
        {
            case 'url':
                $filter = FILTER_SANITIZE_URL;
            break;
            case 'int':
                $filter = FILTER_SANITIZE_NUMBER_INT;
            break;
            case 'float':
                $filter = FILTER_SANITIZE_NUMBER_FLOAT;
                $flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
            break;
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_SANITIZE_EMAIL;
            break;
            case 'string':
            default:
                $filter = FILTER_SANITIZE_STRING;
                $flags = FILTER_FLAG_NO_ENCODE_QUOTES;
            break;

        }
        $output = filter_var($var, $filter, $flags);        
        return($output);
    }

    /** 
     *
     * Validates a single var according to $type.
     * Allows for static calling to allow simple validation.
     *
     */
    public static function validateItem($var, $type)
    {
        if(array_key_exists($type, self::$regexes))
        {
            $returnval =  filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
            return($returnval);
        }
        $filter = false;
        switch($type)
        {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
            break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
            break;
            case 'boolean':
                $filter = FILTER_VALIDATE_BOOLEAN;
            break;
            case 'ip':
                $filter = FILTER_VALIDATE_IP;
            break;
            case 'url':
                $filter = FILTER_VALIDATE_URL;
            break;
        }
        return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
    }       



}

Denken Sie natürlich daran, dass Sie Ihre SQL-Abfrage auch ausführen müssen, je nachdem, welche Art von Datenbank Sie verwenden (mysql_real_escape_string () ist beispielsweise für einen SQL-Server nutzlos). Sie möchten dies wahrscheinlich automatisch auf Ihrer entsprechenden Anwendungsebene wie einem ORM behandeln. Wie oben erwähnt: Verwenden Sie für die Ausgabe in HTML die anderen PHP-Funktionen wie HTMLspecialchars;)

Um HTML-Eingaben mit ähnlich entfernten Klassen und / oder Tags wirklich zuzulassen, ist eines der dedizierten xss-Validierungspakete erforderlich. SCHREIBEN SIE IHRE EIGENEN REGEXES NICHT, UM HTML ZU PARSEEN!

SchizoDuckie
quelle
18
Dies scheint ein praktisches Skript zum Überprüfen von Eingaben zu sein, ist jedoch für die Frage völlig irrelevant.
rjmunro
43

Nein, da ist kein.

Zuallererst ist die SQL-Injection ein Problem bei der Eingabefilterung, und XSS ist ein Problem, bei dem die Ausgabe nicht funktioniert. Sie würden diese beiden Vorgänge also nicht einmal gleichzeitig im Codelebenszyklus ausführen.

Grundregeln

  • Bei SQL - Abfrage, bind Parametern (wie bei PDO) bzw. eine Treiber-native Entkommen Funktion für Abfragevariablen (wie beispielsweise verwenden mysql_real_escape_string())
  • Verwenden Sie strip_tags()diese Option, um unerwünschtes HTML herauszufiltern
  • Entfliehen Sie allen anderen Ausgaben mit htmlspecialchars()und beachten Sie hier den 2. und 3. Parameter.
Peter Bailey
quelle
1
Sie verwenden also nur strip_tags () oder htmlspecialchars (), wenn Sie wissen, dass die Eingabe HTML enthält, das Sie entfernen bzw. maskieren möchten - Sie verwenden es nicht für Sicherheitszwecke, oder? Was macht es beim Binden für Dinge wie Bobby Tables? "Robert '); DROP TABLE Students; -" Entgeht es nur den Zitaten?
Robert Mark Bram
2
Wenn Sie Benutzerdaten haben, die in eine Datenbank gelangen und später auf Webseiten angezeigt werden, werden diese normalerweise nicht viel mehr gelesen als geschrieben? Für mich ist es sinnvoller, es einmal (als Eingabe) zu filtern, bevor Sie es speichern, anstatt es jedes Mal filtern zu müssen, wenn Sie es anzeigen. Vermisse ich etwas oder haben einige Leute für unnötigen Leistungsaufwand in dieser und der akzeptierten Antwort gestimmt?
jbo5112
2
Beste Antwort für mich. Es ist kurz und spricht die Frage gut an, wenn Sie mich fragen. Ist es möglich, PHP irgendwie über $ _POST oder $ _GET mit einer Injektion anzugreifen, oder ist dies unmöglich?
Jo Smo
oh ja, die Arrays $ post und $ get akzeptieren alle Zeichen, aber einige dieser Zeichen können gegen Sie verwendet werden, wenn das Zeichen auf der veröffentlichten PHP-Seite aufgelistet werden darf. Wenn Sie sich der Kapselung von Zeichen (wie "," und ") nicht entziehen, kann dies einen Angriffsvektor öffnen. Das" Zeichen "wird häufig übersehen und kann zur Bildung von Befehlszeilen-Ausführungs-Hacks verwendet werden. Durch Hygiene wird das Hacken von Benutzereingaben verhindert. aber wird Ihnen nicht bei Webanwendungs-Firewall-Hacks helfen.
drtechno
22

Schauen Sie sich HTML Purifier an , um das XSS-Problem zu beheben . Es ist ziemlich konfigurierbar und hat eine anständige Erfolgsbilanz.

Stellen Sie bei den SQL-Injection-Angriffen sicher, dass Sie die Benutzereingaben überprüfen und dann über mysql_real_escape_string () ausführen. Die Funktion kann jedoch nicht alle Injection-Angriffe abwehren. Daher ist es wichtig, dass Sie die Daten überprüfen, bevor Sie sie in Ihre Abfragezeichenfolge kopieren.

Eine bessere Lösung besteht darin, vorbereitete Anweisungen zu verwenden. Die PDO-Bibliothek und die mysqli-Erweiterung unterstützen diese.

jasonbar
quelle
Es gibt keinen "besten Weg", um so etwas wie das Bereinigen von Eingaben zu tun. Verwenden Sie eine Bibliothek, HTML-Reiniger ist gut. Diese Bibliotheken wurden schon oft angeschlagen. Es ist also viel kugelsicherer als alles, was Sie sich vorstellen können
paan
Das Problem mit WordPress ist, dass es nicht unbedingt ein PHP-SQL-Injection-Angriff ist, der Datenbankverletzungen verursacht. Fehlprogrammierte Plugins, die Daten speichern, bei denen eine XML-Abfrage Geheimnisse preisgibt, sind problematischer.
Drtechno
17

Ein Trick, der unter bestimmten Umständen hilfreich sein kann, wenn Sie eine Seite wie haben /mypage?id=53und die ID in einer WHERE-Klausel verwenden, besteht darin, sicherzustellen, dass die ID definitiv eine Ganzzahl ist, wie folgt:

if (isset($_GET['id'])) {
  $id = $_GET['id'];
  settype($id, 'integer');
  $result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
  # now use the result
}

Aber das schneidet natürlich nur einen bestimmten Angriff aus, also lesen Sie alle anderen Antworten. (Und ja, ich weiß, dass der obige Code nicht großartig ist, aber er zeigt die spezifische Verteidigung.)

Hamish Downer
quelle
11
Ich benutze stattdessen $ id = intval ($ id) :)
Duc Tran
Das Umwandeln einer Ganzzahl ist ein guter Weg, um sicherzustellen, dass nur numerische Daten eingefügt werden.
Test
1
$id = (int)$_GET['id']und $que = sprintf('SELECT ... WHERE id="%d"', $id)ist auch gut
vladkras
16

Methoden zur Bereinigung von Benutzereingaben mit PHP:

  • Verwenden Sie moderne Versionen von MySQL und PHP.

  • Zeichensatz explizit festlegen:

    • $ mysqli-> set_charset ("utf8");
      Handbuch
    • $ pdo = neues PDO ('mysql: host = localhost; Datenbankname = testdb; Zeichensatz = UTF8', $ user, $ password);
      Handbuch
    • $ pdo-> exec ("set names utf8");
      Handbuch
    • $ pdo = neues gU (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      Array (
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION,
      PDO :: MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      Handbuch
    • mysql_set_charset ('utf8')
      [veraltet in PHP 5.5.0, entfernt in PHP 7.0.0].
  • Verwenden Sie sichere Zeichensätze:

    • Wählen Sie utf8, latin1, ascii .., verwenden Sie keine anfälligen Zeichensätze big5, cp932, gb2312, gbk, sjis.
  • Verwenden Sie die räumliche Funktion:

    • Von MySQLi vorbereitete Anweisungen:
      $ stmt = $ mysqli-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> execute ();
    • PDO :: quote () - Platziert Anführungszeichen um die Eingabezeichenfolge (falls erforderlich) und maskiert Sonderzeichen in der Eingabezeichenfolge unter Verwendung eines Anführungsstils, der dem zugrunde liegenden Treiber entspricht:

      $ pdo = neues PDO ('mysql: host = localhost; Datenbankname = testdb; Zeichensatz = UTF8', $ user, $ password); expliziten Satz den Zeichensatz
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); Deaktivieren Sie die Emulation vorbereiteter Anweisungen, um ein Zurückgreifen auf die Emulation von Anweisungen zu verhindern, die MySQL nicht nativ vorbereiten kann (um eine Injektion zu verhindern).
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); entgeht nicht nur dem Literal, sondern zitiert es auch (in einfachen Anführungszeichen) $ stmt = $ pdo-> query ("SELECT * FROM test WHERE name = $ var LIMIT 1");

    • PDO-vorbereitete Anweisungen : vs MySQLi-vorbereitete Anweisungen unterstützen mehr Datenbanktreiber und benannte Parameter:

      $ pdo = neues PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); expliziten Satz den Zeichensatz
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); Deaktivieren Sie die Emulation vorbereiteter Anweisungen, um ein Zurückgreifen auf die Emulation von Anweisungen zu verhindern, die MySQL nicht nativ vorbereiten kann (um eine Injektion zu verhindern). $ stmt = $ pdo-> prepare ('SELECT * FROM test WHERE name =? LIMIT 1'); $ stmt-> execute (["'OR 1 = 1 / *"]);

    • mysql_real_escape_string [veraltet in PHP 5.5.0, entfernt in PHP 7.0.0].
    • mysqli_real_escape_string Entgeht Sonderzeichen in einer Zeichenfolge zur Verwendung in einer SQL-Anweisung unter Berücksichtigung des aktuellen Zeichensatzes der Verbindung. Es wird jedoch empfohlen, vorbereitete Anweisungen zu verwenden, da es sich nicht nur um maskierte Zeichenfolgen handelt. Eine Anweisung enthält einen vollständigen Plan für die Ausführung von Abfragen, einschließlich der verwendeten Tabellen und Indizes. Dies ist eine optimierte Methode.
    • Verwenden Sie einfache Anführungszeichen ('') um Ihre Variablen in Ihrer Abfrage.
  • Überprüfen Sie, ob die Variable das enthält, was Sie erwarten:

    • Wenn Sie eine Ganzzahl erwarten, verwenden Sie:
      ctype_digit - Auf numerische Zeichen prüfen; 
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ options);
    • Für Saiten verwenden Sie:
      is_string () - Finden Sie heraus, ob der Typ einer Variablen ein String ist

      Verwenden Sie die Filterfunktion filter_var () - Filter eine Variable mit einem vorgegebenen Filter:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL);
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      mehr vordefinierte Filter
    • filter_input () - Ruft eine bestimmte externe Variable nach Namen ab und filtert sie optional:
      $ search_html = filter_input (INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - Führt eine Übereinstimmung mit regulären Ausdrücken durch.
    • Schreiben Sie Ihre eigene Validierungsfunktion.
Mark Martin
quelle
11

Was Sie hier beschreiben, sind zwei verschiedene Probleme:

  1. Bereinigen / Filtern von Benutzereingabedaten.
  2. Ausgabe entgehen.

1) Benutzereingaben sollten immer als schlecht angenommen werden.

Die Verwendung vorbereiteter Anweisungen oder / und das Filtern mit mysql_real_escape_string ist definitiv ein Muss. PHP hat auch filter_input eingebaut, was ein guter Anfang ist.

2) Dies ist ein großes Thema und hängt vom Kontext der ausgegebenen Daten ab. Für HTML gibt es Lösungen wie htmlpurifier. Als Faustregel gilt immer alles, was Sie ausgeben.

Beide Themen sind viel zu groß, um in einem einzigen Beitrag behandelt zu werden, aber es gibt viele Beiträge, die detaillierter behandelt werden:

Methoden PHP-Ausgabe

Sicherere PHP-Ausgabe

Andrew
quelle
9

Wenn Sie PostgreSQL verwenden, kann die Eingabe von PHP mit pg_escape_string () maskiert werden.

 $username = pg_escape_string($_POST['username']);

Aus der Dokumentation ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () maskiert eine Zeichenfolge zum Abfragen der Datenbank. Es wird eine maskierte Zeichenfolge im PostgreSQL-Format ohne Anführungszeichen zurückgegeben.

Alejandro Silva
quelle
1
pg_escape_literal () ist die empfohlene Funktion für PostgreSQL.
kryptisch ツ
8

Es gibt keine Catchall-Funktion, da mehrere Probleme zu lösen sind.

  1. SQL Injection - Heutzutage sollte im Allgemeinen jedes PHP-Projekt vorbereitete Anweisungen über PHP Data Objects (PDO) als Best Practice verwenden, um einen Fehler durch ein streunendes Zitat sowie eine umfassende Lösung gegen Injection zu vermeiden . Dies ist auch die flexibelste und sicherste Möglichkeit, auf Ihre Datenbank zuzugreifen.

    Im (einzig richtigen) PDO-Tutorial finden Sie so ziemlich alles, was Sie über PDO wissen müssen. (Herzlichen Dank an den Top-SO-Mitarbeiter @YourCommonSense für diese großartige Ressource zu diesem Thema.)

  2. XSS - Daten auf dem Weg in ...

    • HTML Purifier gibt es schon lange und es wird immer noch aktiv aktualisiert. Sie können damit böswillige Eingaben bereinigen und gleichzeitig eine großzügige und konfigurierbare Whitelist mit Tags zulassen. Funktioniert hervorragend mit vielen WYSIWYG-Editoren, kann jedoch in einigen Anwendungsfällen sehr schwer sein.

    • In anderen Fällen, in denen wir HTML / Javascript überhaupt nicht akzeptieren möchten, fand ich diese einfache Funktion nützlich (und habe mehrere Audits gegen XSS bestanden):

      /* Prevent XSS input */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }

  3. XSS - Bereinigen von Daten auf dem Weg nach draußen ... Wenn Sie nicht garantieren, dass die Daten ordnungsgemäß bereinigt wurden, bevor Sie sie Ihrer Datenbank hinzufügen, müssen Sie sie bereinigen, bevor Sie sie Ihrem Benutzer anzeigen. Wir können diese nützlichen PHP-Funktionen nutzen:

    • Wenn Sie vom Benutzer bereitgestellte Werte aufrufen echooder printanzeigen, verwenden Sie diese htmlspecialcharsOption , es sei denn, die Daten wurden ordnungsgemäß bereinigt und können HTML anzeigen.
    • json_encode ist eine sichere Möglichkeit, vom Benutzer bereitgestellte Werte von PHP bis Javascript bereitzustellen
  4. Rufen Sie externe Shell-Befehle mit exec()oder system()Funktionen oder an den backtickOperator auf? In diesem Fall müssen Sie zusätzlich zu SQL Injection & XSS möglicherweise zusätzliche Probleme beheben, wenn Benutzer böswillige Befehle auf Ihrem Server ausführen . Sie müssen verwenden, escapeshellcmdwenn Sie den gesamten Befehl oder escapeshellargeinzelne Argumente maskieren möchten.

Webaholik
quelle
Könnte stattdessen mb_encode_numericentity verwendet werden? Da es alles verschlüsselt?
Drtechno
@drtechno - mb_encode_numericentitywird in dem htmlspecialcharsLink auf # 3 XSS
webaholik
5

Der einfachste Weg, um Fehler bei der Bereinigung von Eingaben und beim Entkommen von Daten zu vermeiden, ist die Verwendung eines PHP-Frameworks wie Symfony , Nette usw. oder eines Teils dieses Frameworks (Template-Engine, Datenbankschicht, ORM).

Bei Vorlagen-Engines wie Twig oder Latte wird die Ausgabe standardmäßig ausgeblendet. Sie müssen nicht manuell lösen, wenn Sie Ihre Ausgabe je nach Kontext ordnungsgemäß ausgeblendet haben (HTML- oder Javascript-Teil der Webseite).

Das Framework bereinigt automatisch Eingaben und Sie sollten die Variablen $ _POST, $ _GET oder $ _SESSION nicht direkt verwenden, sondern über Mechanismen wie Routing, Sitzungsbehandlung usw.

Und für die Datenbankschicht (Modellschicht) gibt es ORM-Frameworks wie Doctrine oder Wrapper um PDO wie Nette Database.

Hier können Sie mehr darüber lesen - Was ist ein Software-Framework?

Ondřej Šotek
quelle
3

Ich wollte nur hinzufügen, dass beim Thema Escape-Ausgabe, wenn Sie PHP DOMDocument verwenden, um Ihre HTML-Ausgabe zu erstellen, diese automatisch im richtigen Kontext maskiert wird. Ein Attribut (value = "") und der innere Text eines <spans> sind nicht gleich. Um sich vor XSS zu schützen, lesen Sie Folgendes : OWASP XSS Prevention Cheat Sheet

user138720
quelle
2

Sie bereinigen niemals Eingaben.

Sie bereinigen immer die Ausgabe.

Die Transformationen, die Sie auf Daten anwenden, um die Aufnahme in eine SQL-Anweisung zu gewährleisten, unterscheiden sich grundlegend von denen, die Sie für die Aufnahme in HTML beantragen. Sie unterscheiden sich grundlegend von denen, die Sie für die Aufnahme in Javascript beantragen. Sie unterscheiden sich grundlegend von denen, die Sie für die Aufnahme in LDIF beantragen Es unterscheidet sich grundlegend von denen, die Sie für die Aufnahme in eine E-Mail beantragen.

Überprüfen Sie auf jeden Fall die Eingabe - entscheiden Sie, ob Sie sie zur weiteren Verarbeitung akzeptieren oder dem Benutzer mitteilen möchten, dass sie nicht akzeptabel ist. Nehmen Sie jedoch keine Änderungen an der Darstellung der Daten vor, bis sie das PHP-Land verlassen.

Vor langer Zeit hat jemand versucht, einen einheitlichen Mechanismus für das Entkommen von Daten zu erfinden, und am Ende haben wir " magic_quotes " erhalten, die nicht für alle Ausgabeziele ordnungsgemäß entgangen sind und zu einer unterschiedlichen Installation geführt haben, für die unterschiedliche Codes erforderlich sind.

symcbean
quelle
Ein Problem dabei ist, dass es nicht immer ein Datenbankangriff ist und alle Benutzereingaben vor dem System geschützt werden sollten. nicht nur ein Sprachtyp. Wenn Sie also auf Ihren Websites Ihre $ _POST-Daten auflisten, selbst wenn Sie eine Bindung verwenden, kann dies ausreichen, um Shell- oder sogar anderen PHP-Code auszuführen.
Drtechno
"Es ist nicht immer ein Datenbankangriff": "Die Transformationen, die Sie auf Daten anwenden, um die Aufnahme in eine SQL-Anweisung zu
gewährleisten,
"Alle Benutzereingaben sollten vor dem System geschützt werden": Nein, das System sollte vor Benutzereingaben geschützt werden.
Symcbean
Nun, mir gingen die Worte aus, aber ja, die Eingabe muss daran gehindert werden, den Systembetrieb zu beeinflussen. um dies zu klären ...
drtechno
Sowohl die Eingabe als auch die Ausgabe sollten bereinigt werden.
Tajni
1

Vertraue niemals Benutzerdaten.

function clean_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}

Die trim()Funktion entfernt Leerzeichen und andere vordefinierte Zeichen von beiden Seiten einer Zeichenfolge.

Die stripslashes()Funktion entfernt Backslashes

Die htmlspecialchars()Funktion konvertiert einige vordefinierte Zeichen in HTML-Entitäten.

Die vordefinierten Zeichen sind:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;
Erik Thiart
quelle
1
Wovor würde dies schützen? Ist das für XSS? Warum heißt es clean_inputdann? Warum sollten Sie Schrägstriche entfernen wollen?
Dharman
5
WARNUNG: Dies macht Benutzerdaten nicht auf magische Weise sicher. Diese Funktion beschädigt Ihre Daten unnötig, ohne sie vor irgendetwas zu schützen. BENUTZE ES NICHT!
Dharman
Ihre Aussage ist falsch.
Erik Thiart