Какой самый лучший метод для дезинфекции ввода пользователя с помощью PHP? - PullRequest
1049 голосов
/ 25 сентября 2008

Есть ли где-нибудь функция catchall, которая хорошо работает для санации пользовательского ввода для SQL-инъекций и XSS-атак, но при этом допускает определенные типы html-тегов?

Ответы [ 19 ]

1125 голосов
/ 25 сентября 2008

Это распространенное заблуждение, что пользовательский ввод может быть отфильтрован. В PHP даже есть (теперь устаревшая) «функция», называемая магическими кавычками, которая основывается на этой идее. Это чепуха. Забудьте про фильтрацию (или очистку, или как там ее называют).

Что вы должны сделать, чтобы избежать проблем, довольно просто: всякий раз, когда вы встраиваете строку в чужой код, вы должны избегать ее в соответствии с правилами этого языка. Например, если вы встраиваете строку в какой-то SQL, ориентированный на MySql, вы должны экранировать строку с помощью функции MySql для этой цели (mysqli_real_escape_string). (Или, в случае баз данных, использование подготовленных заявлений является лучшим подходом, когда это возможно)

Другим примером является HTML: если вы встраиваете строки в разметку HTML, вы должны экранировать ее с помощью htmlspecialchars. Это означает, что каждый оператор echo или print должен использовать htmlspecialchars.

Третьим примером могут быть команды оболочки: если вы собираетесь встраивать строки (например, аргументы) во внешние команды и вызывать их с помощью exec, то вы должны использовать escapeshellcmd и escapeshellarg.

И так далее, и так далее ...

В случае only , когда вам нужно активно фильтровать данные, вы принимаете предварительно отформатированный ввод. Например. если вы позволите своим пользователям размещать HTML-разметку, которую вы планируете показывать на сайте. Однако вам следует избегать этого любой ценой, поскольку независимо от того, насколько хорошо вы его фильтруете, это всегда будет потенциальной дырой в безопасности.

198 голосов
/ 09 октября 2008

Не пытайтесь предотвратить внедрение SQL путем очистки входных данных.

Вместо этого не позволяет использовать данные при создании кода SQL . Используйте подготовленные операторы (то есть, используя параметры в шаблонном запросе), которые используют связанные переменные. Это единственный способ гарантировать защиту от SQL-инъекций.

Пожалуйста, посетите мой сайт http://bobby -tables.com / для получения дополнительной информации о предотвращении внедрения SQL.

75 голосов
/ 25 сентября 2008

Нет. Вы не можете в общем фильтровать данные без контекста того, для чего они нужны. Иногда вам нужно принять SQL-запрос в качестве входных данных, а иногда вам нужно принять HTML-код в качестве входных данных.

Вам необходимо отфильтровать ввод в белый список - убедитесь, что данные соответствуют некоторой спецификации того, что вы ожидаете. Затем вам нужно экранировать его перед использованием, в зависимости от контекста, в котором вы его используете.

Процесс экранирования данных для SQL - для предотвращения внедрения SQL - сильно отличается от процесса экранирования данных для (X) HTML для предотвращения XSS.

47 голосов
/ 25 сентября 2008

PHP теперь имеет новые приятные функции filter_input, которые, например, освобождают вас от поиска «конечного регулярного выражения электронной почты» теперь, когда есть встроенный тип FILTER_VALIDATE_EMAIL

Мой собственный класс фильтра (использующий javascript для выделения ошибочных полей) может быть инициирован либо запросом ajax, либо обычной записью в форме. (см. пример ниже)

/**
 *  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;
    }       



}

Конечно, имейте в виду, что вам также нужно выполнять экранирование SQL-запроса в зависимости от того, какой тип БД вы используете (mysql_real_escape_string () бесполезен, например, для сервера SQL). Возможно, вы захотите обработать это автоматически на соответствующем прикладном уровне, таком как ORM. Также, как упоминалось выше: для вывода в html используйте другие специальные функции php, такие как htmlspecialchars;)

Для того, чтобы действительно разрешить ввод HTML с подобными раздетыми классами и / или тегами, зависит от одного из выделенных пакетов проверки xss. НЕ ПИШИТЕ СВОИ СОБЫТИЯ ДЛЯ ПАРТНЕРСТВА HTML!

43 голосов
/ 25 сентября 2008

Нет, нет.

Прежде всего, инъекция SQL - это проблема фильтрации ввода, а XSS - выход, выходящий за ее пределы, поэтому вы даже не выполняете эти две операции одновременно в жизненном цикле кода.

Основные правила большого пальца

  • Для запроса SQL связывайте параметры (как с PDO) или используйте встроенную в драйвер функцию экранирования для переменных запроса (например, mysql_real_escape_string())
  • Используйте strip_tags(), чтобы отфильтровать нежелательный HTML
  • Сбросьте все остальные выходные с помощью htmlspecialchars() и помните о 2-м и 3-м параметрах здесь.
22 голосов
/ 25 сентября 2008

Чтобы устранить проблему XSS, взгляните на Очиститель HTML . Он достаточно настраиваемый и имеет неплохой послужной список.

Что касается атак с использованием SQL-инъекций, убедитесь, что вы проверили ввод данных пользователем, а затем запустили его, хотя mysql_real_escape_string (). Однако эта функция не победит все атаки с использованием инъекций, поэтому важно проверить данные перед тем, как вывести их в строку запроса.

Лучшее решение - использовать подготовленные заявления. Библиотека PDO и расширение mysqli поддерживают их.

20 голосов
/ 15 октября 2012

В PHP 5.2 введена функция filter_var .

Поддерживает большое количество фильтров SANITIZE, VALIDATE.

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

16 голосов
/ 09 марта 2010

Одна хитрость, которая может помочь в определенных обстоятельствах, когда у вас есть страница типа /mypage?id=53 и вы используете идентификатор в предложении WHERE, это убедиться, что идентификатор определенно является целым числом, например:

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

Но, конечно, это исключает только одну конкретную атаку, поэтому прочитайте все остальные ответы. (И да, я знаю, что приведенный выше код не очень хорош, но он показывает конкретную защиту.)

11 голосов
/ 25 февраля 2018

Методы очистки входных данных с помощью PHP:

  • Используйте современные версии MySQL и PHP.

  • Установить кодировку явно:

    • $mysqli->set_charset("utf8");
      руководство
    • $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);
      руководство
    • $pdo->exec("set names utf8");
      руководство
    • $pdo = new PDO(
      "mysql:host=$host;dbname=$db", $user, $pass, 
      array(
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      руководство
    • <s>mysql_set_charset('utf8')</s>
      [устарело в PHP 5.5.0, удалено в PHP 7.0.0].
  • Использовать безопасные кодировки:

    • Выберите utf8, latin1, ascii .., не используйте уязвимые кодировки big5, cp932, gb2312, gbk, sjis.
  • Используйте пространственную функцию:

    • MySQLi подготовил операторы:
      $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); <br>$param = "' OR 1=1 /*";<br>$stmt->bind_param('s', $param);<br>$stmt->execute();
    • PDO :: quote () - помещает кавычки вокруг входной строки (если требуется) и экранирует специальные символы во входной строке, используя стиль цитирования, соответствующий базовому драйверу:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);<sup>explicit set the character set</sup><br>$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);<sup>disable emulating prepared statements to prevent  fallback to emulating statements that MySQL can't prepare natively (to prevent injection)</sup><br>$var  = $pdo->quote("' OR 1=1 /*");<sup>not only escapes the literal, but also quotes it (in single-quote ' characters)</sup>
      $stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
      
    • Подготовленные операторы PDO : против подготовленных MySQLi операторов поддерживается больше драйверов базы данных и именованных параметров:

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);<sup>explicit set the character set</sup><br>$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);<sup>disable emulating prepared statements to prevent  fallback to emulating statements that MySQL can't prepare natively (to prevent injection)</sup>
      $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
      $stmt->execute(["' OR 1=1 /*"]);
    • mysql_real_escape_string [устарело в PHP 5.5.0, удалено в PHP 7.0.0].
    • mysqli_real_escape_string Экранирует специальные символы в строке для использования в операторе SQL с учетом текущей кодировки соединения. Но рекомендуется использовать подготовленные операторы, поскольку они не являются просто экранированными строками, оператор предлагает полный план выполнения запроса, включая оптимизированные таблицы и индексы, которые он будет использовать.
    • Используйте одинарные кавычки ('') вокруг переменных внутри запроса.
  • Проверьте, что переменная содержит то, что вы ожидаете:

    • Если вы ожидаете целое число, используйте:
      ctype_digit — Check for numeric character(s);<br>$value = (int) $value;<br>$value = intval($value);<br>$var = filter_var('0755', FILTER_VALIDATE_INT, $options);
    • Для строк используйте:
      is_string() — Find whether the type of a variable is string

      Использовать Функция фильтра filter_var () - фильтрует переменную с помощью указанного фильтра:
      $email = filter_var($email, FILTER_SANITIZE_EMAIL);<br>$newstr = filter_var($str, FILTER_SANITIZE_STRING);
      больше предопределенных фильтров
    • filter_input () - получает определенную внешнюю переменную по имени и дополнительно фильтрует ее:
      $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - выполнить сопоставление с регулярным выражением;
    • Напишите свою собственную функцию проверки.
11 голосов
/ 16 июля 2012

То, что вы описываете здесь, это две отдельные проблемы:

  1. Санитарная обработка / фильтрация введенных пользователем данных.
  2. Выход на выход.

1) Пользовательский ввод всегда следует считать неверным.

Использование подготовленных операторов или / и фильтрации с помощью mysql_real_escape_string, безусловно, необходимо. В PHP также есть встроенный фильтр filter_input, с которого можно начать.

2) Это большая тема, и она зависит от контекста выводимых данных. Для HTML существуют такие решения, как htmlpurifier. как правило, всегда избегайте всего, что вы выводите.

Обе проблемы слишком велики, чтобы их можно было рассмотреть в одном посте, но есть много постов, в которых более подробно рассмотрено:

Методы вывода PHP

Более безопасный вывод PHP

...