Frage Was ist die beste Methode, um Benutzereingaben mit PHP zu bereinigen?


Gibt es irgendwo eine Catchall-Funktion, die gut funktioniert, um Benutzereingaben für SQL-Injection- und XSS-Angriffe zu bereinigen und trotzdem bestimmte Arten von HTML-Tags zuzulassen?


975
2017-09-24 20:20


Ursprung


Antworten:


Es ist ein häufiges Missverständnis, dass Benutzereingaben gefiltert werden können. PHP hat sogar ein (jetzt veraltetes) "Feature", genannt magische Zitate, das auf dieser Idee aufbaut. Das ist Unsinn. Vergessen Sie das Filtern (oder das Putzen oder wie auch immer die Leute es nennen).

Was Sie tun sollten, um Probleme zu vermeiden, ist ziemlich einfach: Wann immer Sie eine Zeichenkette in fremden Code einbetten, müssen Sie sie gemäß den Regeln dieser Sprache entziehen. Wenn Sie beispielsweise eine Zeichenfolge in ein SQL-Targeting für MySql einbetten, müssen Sie die Zeichenfolge mit der Funktion MySql für diesen Zweck (mysqli_real_escape_string). (Oder, im Fall von Datenbanken, die Verwendung von vorbereiteten Anweisungen ist ein besserer Ansatz, wenn möglich)

Ein anderes Beispiel ist HTML: Wenn Sie Strings in HTML Markup einbetten, müssen Sie es mit entschlüsseln htmlspecialchars. Dies bedeutet, dass jeder einzelne echo oder print Anweisung sollte verwenden htmlspecialchars.

Ein drittes Beispiel könnten Shell-Befehle sein: Wenn Sie Strings (z. B. Argumente) in externe Befehle einbetten und mit ihnen aufrufen möchten exec, dann musst du verwenden escapeshellcmd und escapeshellarg.

Und so weiter und so fort ...

Das nur Der Fall, in dem Sie aktiv Daten filtern müssen, ist, wenn Sie vorformatierte Eingaben akzeptieren. Z.B. Wenn Sie Ihren Benutzern das HTML-Markup bereitstellen lassen, das Sie auf der Website anzeigen möchten. Es sollte jedoch ratsam sein, dies um jeden Preis zu vermeiden, denn unabhängig davon, wie gut Sie es filtern, wird es immer ein potenzielles Sicherheitsloch sein.


1078
2017-09-24 22:30



Versuchen Sie nicht, die SQL-Injektion zu verhindern, indem Sie Eingabedaten bereinigen.

Stattdessen, Erlauben Sie nicht, dass Daten bei der Erstellung Ihres SQL-Codes verwendet werden. Verwenden Sie vorbereitete Anweisungen (d. H. Parameter in einer Vorlagenabfrage verwenden), die gebundene Variablen verwenden. Dies ist der einzige Weg, um gegen SQL-Injection garantiert zu werden.

Bitte sehen Sie meine Website http://bobby-tables.com/ Weitere Informationen zum Verhindern der SQL-Injektion


184
2017-10-09 06:28



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

Sie müssen die Eingabe auf einer Whitelist filtern - stellen Sie sicher, dass die Daten mit den Spezifikationen übereinstimmen, die Sie erwarten. Dann müssen Sie es, bevor Sie es verwenden, es zu entkommen, abhängig von dem Kontext, in dem Sie es verwenden.

Der Prozess des Entweichens von Daten für SQL - um SQL-Injection zu verhindern - unterscheidet sich erheblich von dem Prozess des Entweichens von Daten für (X) HTML, um XSS zu verhindern.


72
2017-09-24 20:24



PHP hat jetzt die neuen nice filter_input-Funktionen, die Sie beispielsweise davon befreien, den ultimativen E-Mail-Regex zu finden, da es jetzt einen eingebauten FILTER_VALIDATE_EMAIL-Typ gibt

Meine eigene Filterklasse (Javascript wird verwendet, um fehlerhafte Felder hervorzuheben) kann entweder durch eine Ajax-Anfrage oder eine normale Formularpost ausgelöst 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');
 *  $sanatize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanatize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanatize($_POST);
 *      // now do your saving, $_POST has been sanatized.
 *      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 sanatize just one element:
 * $sanatized = new FormValidator()->sanatize('<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->sanatations = $sanatations;
        $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);
    }


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


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

    /**
     *
     * Sanatize a single var according to $type.
     * Allows for static calling to allow simple sanatization
     */
    public static function sanatizeItem($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;
    }       



}

Beachten Sie natürlich, dass Sie Ihre SQL-Abfrage abhängig davon, welche Art von Datenbank Sie verwenden, auch maskieren müssen (mysql_real_escape_string () ist beispielsweise für einen SQL-Server nutzlos). Wahrscheinlich möchten Sie dies automatisch auf Ihrer geeigneten Anwendungsschicht wie ein ORM handhaben. Auch, wie oben erwähnt: für die Ausgabe in HTML verwenden Sie die anderen PHP-Funktionen wie htmlspecialchars;)

Das Erlauben von HTML-Eingaben mit ähnlich strikten Klassen und / oder Tags hängt von einem der dedizierten xss-Validierungspakete ab. Schreiben Sie nicht Ihre eigenen REGEXES zu PARSE HTML!


43
2017-09-24 23:12



Nein, da ist kein.

Erstens ist SQL Injection ein Eingabefilterproblem, und XSS ist eine Ausgabe, die einem entgeht - Sie würden diese beiden Vorgänge nicht einmal zur gleichen Zeit im Code-Lebenszyklus ausführen.

Grundlegende Faustregeln

  • Für die SQL-Abfrage, binden Sie Parameter (wie bei PDO) oder verwenden Sie eine treiber-native Escape-Funktion für Abfragevariablen (wie z mysql_real_escape_string())
  • Benutzen strip_tags() um unerwünschtes HTML herauszufiltern
  • Entkomme alle anderen Ausgaben mit htmlspecialchars() und beachten Sie die 2. und 3. Parameter hier.

39
2017-09-24 20:30



Um das XSS-Problem zu beheben, werfen Sie einen Blick auf HTML-Reiniger. Es ist ziemlich konfigurierbar und hat eine ordentliche Erfolgsbilanz.

Achten Sie bei den SQL-Injection-Angriffen darauf, dass Sie die Benutzereingaben überprüfen, und führen Sie sie dann über mysql_real_escape_string () aus. Die Funktion wird jedoch nicht alle Injektionsangriffe abwehren. Daher ist es wichtig, dass Sie die Daten überprüfen, bevor Sie sie in Ihre Abfragezeichenfolge ablegen.

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


20
2017-09-24 20:29



PHP 5.2 führte die Filter_var Funktion.

Es unterstützt eine große Anzahl von SANITIZE-, VALIDATE-Filtern.

http://php.net/manual/en/function.filter-var.php


18
2017-10-15 08:40



Ein Trick, der in den spezifischen Umständen helfen kann, in denen Sie eine Seite mögen /mypage?id=53 und Sie verwenden die ID in einer WHERE-Klausel, um sicherzustellen, dass die ID definitiv eine Ganzzahl ist, etwa so:

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

Aber natürlich wird nur eine bestimmte Attacke aussortiert. Lesen Sie also alle anderen Antworten. (Und ja, ich weiß, dass der obige Code nicht großartig ist, aber er zeigt die spezifische Verteidigung.)


15
2018-03-08 23:14



Was Sie hier beschreiben, ist zwei verschiedene Probleme:

  1. Desinfektion / Filterung von Benutzereingabedaten.
  2. Ausgabe verlassen.

1) Benutzereingaben sollten immer als schlecht angenommen werden.

Vorbereitete Anweisungen zu verwenden oder / und mit mysql_real_escape_string zu filtern, ist definitiv ein Muss. PHP hat auch filter_input eingebaut, was ein guter Start 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 da draußen. als Faustregel, entkomme immer allem, was du ausgibst.

Beide Probleme sind viel zu groß, um sie in einem einzigen Beitrag zu behandeln, aber es gibt viele Beiträge, die ausführlicher werden:

Methoden PHP-Ausgabe

Sicherere PHP-Ausgabe


10
2017-07-16 10:44