it-swarm.com.ru

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

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

1018
Brent

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

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

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

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

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

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

1108
troelskn

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

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

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

191
Andy Lester

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

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

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

73
Daniel Papasian

В PHP появились новые функции Nice 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('[email protected]', '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!

46
SchizoDuckie

Нет, нет.

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

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

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

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

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

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

21
jasonbar

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

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

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

19
dangel

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

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

15
Hamish Downer

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

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

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

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

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

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

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

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

10
Andrew

Методы для очистки пользовательского ввода с помощью PHP:

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

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

    • $ Mysqli-> set_charset ( "utf8");
      руководство
    • $ pdo = новый PDO ('mysql: Host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      руководство
    • $ pdo-> exec ("set names utf8");
      руководство
    • $ pdo = новый PDO (
       "mysql: Host = $ Host; dbname = $ db", $ user, $ pass, 
       массив (
       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 = новый 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 = новый 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 - проверять числовые символы;
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ options);
    • Для строк используйте:
      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 () - Выполнить сопоставление с регулярным выражением;
    • Напишите свою собственную функцию проверки.
8
Mark Martin

Если вы используете 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 без кавычек.

6
Alejandro Silva

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

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

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

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

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

5
Ondřej Šotek

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

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

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

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

    • HTML Purifier существует уже давно и до сих пор активно обновляется. Вы можете использовать его для дезинфекции вредоносного ввода, в то же время позволяя использовать щедрый и настраиваемый белый список тегов. Прекрасно работает со многими редакторами 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. Вы вызываете внешние команды Shell, используя exec() или system() functions, или оператору backtick ? Если это так, в дополнение к SQL-инъекции и XSS вам может потребоваться решить еще одну проблему: пользователи, выполняющие вредоносные команды на вашем сервере. Вам нужно использовать escapeshellcmd , если вы хотите экранировать всю команду OR escapeshellarg , чтобы экранировать отдельные аргументы.

3
webaholik

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

2
user138720

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

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

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

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

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

1
symcbean

Я вижу, что 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);
}
0
drtechno

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

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;
0
Erik Thiart

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

0
Till