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

StackOverflow https://stackoverflow.com/questions/129677

Вопрос

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

Это было полезно?

Решение

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

Что делать, чтобы избежать проблем, довольно просто:всякий раз, когда вы встраиваете строку во внешний код, вы должны экранировать ее в соответствии с правилами этого языка.Например, если вы встраиваете строку в какой-либо 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, либо сообщением обычной формы.(см. пример ниже)

/**
 *  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!

Нет, нет.

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

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

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

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

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

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

PHP 5.2 представил filter_var функция.

Он поддерживает множество фильтров SANITIZE, VALIDATE.

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

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

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

Методы очистки пользовательского ввода с помощью 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].
  • Используйте безопасные кодировки:

    • Выберите 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);явно установить набор символов
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);отключить эмуляцию подготовленных операторов, чтобы предотвратить возврат к эмуляции операторов, которые MySQL не может подготовить изначально (чтобы предотвратить внедрение)
      $var = $pdo->quote("' OR 1=1 /*");не только экранирует литерал, но и цитирует его (в символах одинарных кавычек) $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);явно установить набор символов
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);отключить эмуляцию подготовленных операторов, чтобы предотвратить возврат к эмуляции операторов, которые MySQL не может подготовить изначально (чтобы предотвратить внедрение) $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() — Выполнить сопоставление регулярного выражения;
    • Напишите свою собственную функцию проверки.

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

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

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

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

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

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

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

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

Если вы используете 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-фреймворк, например Симфония, Нетте и т. д.или часть этой структуры (система шаблонов, уровень базы данных, ORM).

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

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

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

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

Здесь нет универсальной функции, поскольку необходимо решить множество проблем.

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

    Проверить (Единственное правильное) Учебное пособие по PDO практически все, что вам нужно знать о PDO.(Искренне благодарим ведущего участника SO, @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-инъекций и XSS у вас могут возникнуть дополнительные проблемы: пользователи, выполняющие вредоносные команды на вашем сервере.Вам нужно использовать escapeshellcmd если вы хотите избежать всей команды ИЛИ escapeshellarg чтобы избежать отдельных аргументов.

Никогда не доверяйте пользовательским данным.

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

А trim() Функция удаляет пробелы и другие предопределенные символы с обеих сторон строки.

А stripslashes() функция удаляет обратную косую черту

А htmlspecialchars() функция преобразует некоторые предопределенные символы в объекты HTML.

Предопределенные символы:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;

Просто хотел добавить, что по поводу экранирования вывода: если вы используете php DOMDocument для вывода HTML-вывода, он автоматически экранируется в правильном контексте.Атрибут (value="") и внутренний текст <span> не равны.Чтобы быть в безопасности от XSS, прочитайте это:Памятка OWASP по предотвращению XSS

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

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

Преобразования, которые вы применяете к данным, чтобы сделать их безопасными для включения в оператор SQL, полностью отличаются от тех, которые вы применяете для включения в HTML, полностью отличаются от тех, которые вы применяете для включения в Javascript, полностью отличаются от тех, которые вы применяете для включения в LDIF. совершенно отличаются от тех, которые вы подаете для включения в CSS, полностью отличаются от тех, которые вы подаете для включения в электронное письмо....

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

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

Есть расширение фильтра (ссылка на инструкции, руководство), который очень хорошо работает со всеми переменными GPC.Однако это не волшебное средство, вам все равно придется его использовать.

Я вижу, что php-фильтр для очистки специальных символов может пригодиться.

нравиться:

    $a=fliter_var($_POST['a'],FILTER_SANITIZE_SPECIAL_CHARS);

Тем не менее, я думаю, что это могло бы быть лучше, потому что, глядя на код C, он фильтрует только " ' \ < > & и \0, так что я вижу, что это хороший способ очистки.Однако изменение исходного кода включает в себя другие символы, такие как / { } [ ] .;` усилит эту функцию в строке кодирования (enc['']):

    void php_filter_special_chars(PHP_INPUT_FILTER_PARAM_DECL)
{
unsigned char enc[256] = {0};

php_filter_strip(value, flags);

/* encodes ' " < > & \0 to numerical entities */
enc['\''] = enc['"'] = enc['<'] = enc['>'] = enc['&'] = enc[0] = 1;

/* if strip low is not set, then we encode them as &#xx; */
memset(enc, 1, 32);

if (flags & FILTER_FLAG_ENCODE_HIGH) {
    memset(enc + 127, 1, sizeof(enc) - 127);
}

php_filter_encode_html(value, enc);
}

Лучший BASIC-метод для очистки пользовательского ввода с помощью 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;
    }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top