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

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

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

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

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

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

И так далее …

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

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

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

Дополнительную информацию о предотвращении SQL-инъекции см. На моем веб-сайте http://bobby-tables.com/ .

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

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

Нет, нет.

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

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

  • Для SQL-запроса, свяжите параметры (как с PDO) или используйте собственную функцию экранирования для переменных запроса (например, mysql_real_escape_string() )
  • Используйте strip_tags() для фильтрации нежелательного HTML-кода.
  • htmlspecialchars() все остальные выходные данные с помощью htmlspecialchars() и 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 } 

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

Здесь вы описываете два отдельных вопроса:

  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 () реализует строку для запроса базы данных. Он возвращает escape-строку в формате PostgreSQL без кавычек.

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

Самый простой способ избежать ошибок при дезинфекции входных данных и экранировании данных – это использовать фреймворк PHP, такой как Symphony , Nette и т. Д. Или часть этой структуры (механизм шаблонов, уровень базы данных, ORM).

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

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

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

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

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

Нет никакой общей функции, потому что есть несколько проблем, которые нужно решить.

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

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

  2. XSS – Sanitize data на пути в …

    • 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 – Sanitize data на выходе … если вы не гарантируете, что данные были надлежащим образом дезинфицированы до того, как вы добавите их в свою базу данных, вам нужно будет дезинформировать его перед отображением его вашему пользователю, мы можем использовать эти полезные функции PHP:

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

Лучший метод 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; }