Каков наилучший метод очистки пользовательского ввода с помощью PHP?

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

18 ответов


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

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

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

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

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

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


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

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

пожалуйста, смотрите мой веб-сайт http://bobby-tables.com/ Для больше о предотвращать впрыску SQL.


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

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

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


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

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

/**
 *  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 server, например). Вероятно, вы хотите автоматически обрабатывать это на соответствующем уровне приложения, например ORM. Кроме того, как упоминалось выше: для вывода в html используйте другие выделенные функции php, такие как htmlspecialchars ;)

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


нет, нет.

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

Основные правила

  • для SQL-запроса, параметры привязки (как с PDO) или использовать функцию экранирования драйвера для переменных запроса (например,mysql_real_escape_string())
  • использовать strip_tags() для фильтрации нежелательных HTML-код
  • Escape все остальные выходные данные с htmlspecialchars() и помните о 2-м и 3-м параметрах здесь.

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

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

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


PHP 5.2 представил filter_var


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

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

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


то, что вы описываете здесь-это два отдельных вопроса:

  1. очистка / фильтрация входных данных пользователя.
  2. экранирование выход.

1) пользовательский ввод всегда должен считаться плохим.

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

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

оба вопроса слишком велики, чтобы войти в один пост, но есть много сообщений, которые идут более подробно:

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

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


методы дезинфекции пользовательского ввода с помощью 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"
      )
      );
      руководство
    • mysql_set_charset('utf8')
      [устарело в PHP 5.5.0, удалено в PHP 7.0.0].
  • использовать secure наборы символов:

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

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

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

      $pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF8', $user, $password);explicit set the character set
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);disable emulating prepared statements to prevent fallback to emulating statements that MySQL can't prepare natively (to prevent injection) $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);
      $value = (int) $value;
      $value = intval($value);
      $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);
      $newstr = filter_var($str, FILTER_SANITIZE_STRING);
      несколько предопределенных фильтров
    • filter_input () - получает определенную внешнюю переменную по имени и дополнительно фильтрует ее:
      $search_html = filter_input(INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • функции preg_match() - Выполните соответствие регулярному выражению;
    • напишите свою собственную функцию проверки.

Если вы используете PostgreSQL, вход с PHP может быть экранирован с помощью pg_escape_string ()

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

из документации (http://php.net/manual/es/function.pg-escape-string.php):

pg_escape_string () экранирует строку для запроса базы данных. Он возвращает экранированную строку в формате PostgreSQL без кавычек.


самый простой способ избежать ошибок в дезинфекции ввода и экранирования данных-использовать PHP framework, например Symfony, Нетте etc. или часть этой структуры (механизм шаблонов, уровень базы данных, ORM).

шаблонизатор как веточка или Latte имеет выход экранирование по умолчанию - вам не нужно решать вручную, если вы правильно экранировали свой вывод в зависимости от контекста (HTML или Javascript часть веб-страницы).

база автоматически дезинфицируя входные данные, вы не должны использовать переменные $_POST, $_GET или $_SESSION напрямую, а через такой механизм, как маршрутизация, обработка сеанса и т. д.

и для слоя базы данных (модели) существуют рамки ORM, такие как доктрина или обертки вокруг PDO, такие как база данных Nette.

вы можете прочитать больше об этом здесь - Что такое программный фреймворк?


нет функции catchall, потому что есть несколько проблем, которые необходимо решить.

  1. SQL-инъекций - сегодня, как правило, каждый проект PHP должен использовать подготовленные операторы через объекты данных PHP (PDO) как правило, предотвращение ошибки от случайной цитаты, а также полнофункциональное решение против инъекции. Это также самый гибкий и безопасный способ доступа к вашей базе данных.

    проверить (единственный правильный) PDO учебник для почти всего, что вам нужно знать о PDO. (Искренняя благодарность top so contributor, @YourCommonSense, за этот отличный ресурс по этому вопросу.)

  2. XSS-дезинфицировать данные по пути...

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

    • в других случаях, когда мы вообще не хотим принимать HTML/Javascript, я нашел эту простую функцию полезной (и прошел несколько аудитов против XSS):

      /* 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-санировать данные по пути из... если вы не гарантируете, что данные были должным образом санированы, прежде чем добавлять их в свою базу данных, вам нужно будет санировать их перед отображением пользователю, мы можем использовать эти полезные функции PHP:

    • когда вы называете echo или print для отображения пользовательских значений, используйте htmlspecialchars если данные не были должным образом санированы безопасно и разрешено отображать HTML.
    • json_encode безопасный путь обеспечить пользовательские значения от PHP до Javascript
  4. вы вызываете команды внешней оболочки с помощью exec() или system() функции, или backtick оператор? если это так, в дополнение к SQL Injection & XSS у вас может быть дополнительная проблема для решения,пользователи, выполняющие вредоносные команды на вашем сервере. Вам нужно использовать escapeshellcmd если вы хотите избежать вся команда или escapeshellarg чтобы избежать отдельных аргументов.


просто хотел добавить, что по вопросу вывода экранирования, если вы используете php DOMDocument, чтобы сделать ваш вывод html, он автоматически экранируется в правильном контексте. Атрибут (value="") и внутренний текст не равны. Чтобы быть в безопасности от XSS, прочитайте это: OWASP XSS профилактика шпаргалка


вы никогда не санировать ввод.

вы всегда дезинфицируете выход.

преобразования применяются к данным, чтобы сделать его безопасным для включения в SQL-оператор, полностью отличаются от тех, которые применяются для включения в HTML полностью отличаются от тех, которые претендуют на включение в JavaScript являются полностью отличаются от тех, которые претендуют на включение в ldif полностью отличаются от тех, которые применяются для включения в CSS полностью отличаются от тех, кого вы подать заявку на включение в Email....

непременно проверка ввода - решите, должны ли вы принять его для дальнейшей обработки или сказать пользователю, что это неприемлемо. Но не применяйте никаких изменений к представлению данных, пока он не покинет Землю PHP.

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


существует расширение фильтра (howto-link, руководство), который работает довольно хорошо со всеми переменными GPC. Это не волшебная вещь, хотя вам все равно придется ее использовать.


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

лучший базовый метод для дезинфекции пользовательского ввода с помощью PHP:


    function sanitizeString($var)
    {
        $var = stripslashes($var);
        $var = strip_tags($var);
        $var = htmlentities($var);
        return $var;
    }

    function sanitizeMySQL($connection, $var)
    {
        $var = $connection->real_escape_string($var);
        $var = sanitizeString($var);
        return $var;
    }