我如何可以消毒用户输入的与PHP?
-
02-07-2019 - |
题
是否有一个通用功能的地方,作好消毒的用户输入SQL注和XSS攻击,同时仍允许某些类型的HTML tags?
解决方案
一种常见的误解是可以过滤用户输入。 PHP甚至还有一个(现已弃用的)<!>“功能<!>”,称为魔术引号,它建立在这个想法之上。这是胡说八道。忘记过滤(或清理,或任何人称之为)。
为了避免出现问题,你应该做的很简单:每当你在国外代码中嵌入一个字符串时,你必须根据该语言的规则来逃避它。例如,如果在某些SQL目标MySql中嵌入字符串,则必须为此目的使用MySql函数转义字符串(mysqli_real_escape_string
)。 (或者,对于数据库,在可能的情况下使用预处理语句是更好的方法)
另一个例子是HTML:如果在HTML标记中嵌入字符串,则必须使用 <= > 。这意味着每个htmlspecialchars
或echo
语句都应使用print
。
第三个例子可能是shell命令:如果要将字符串(例如参数)嵌入到外部命令中,并使用 exec
,那么你必须使用 escapeshellcmd
和 escapeshellarg
。
依此类推......
仅情况下,您需要主动过滤数据,如果您接受预先格式化的输入。例如。如果您允许用户发布HTML标记,那么您计划在网站上显示该标记。但是,你应该不惜一切代价避免这种情况,因为无论你如何过滤它,它都将是一个潜在的安全漏洞。
其他提示
不要试图通过清理输入数据来阻止SQL注入。
相反,不允许在创建SQL代码时使用数据。使用使用绑定变量的Prepared Statements(即使用模板查询中的参数)。这是防止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查询转义,具体取决于您使用的数据库类型(例如,mysql_real_escape_string()对于SQL Server无用)。您可能希望在适当的应用程序层(如ORM)自动处理此问题。另外,如上所述:输出到html使用其他php专用函数,如htmlspecialchars;)
真正允许带有类似剥离类和/或标记的HTML输入取决于其中一个专用的xss验证包。不要将自己的规则写成PARSE HTML!
没有,没有。
首先,SQL注射是一个输入的过滤问题,并XSS是一个输出逃避的一个-所以你甚至不会执行这两种操作在同一时间代码的生命周期。
基本规则的拇指
- 为SQL query,bind参数(如公设辩护人)或者使用一个驱动程序的当地逃离函数进行查询的变量(如
mysql_real_escape_string()
) - 使用
strip_tags()
过滤出的不需要HTML - 逃离的所有其他产出与
htmlspecialchars()
并铭记第2次和第3次参数在这里。
要解决XSS问题,请查看 HTML Purifier 。它相当可配置,并且有良好的记录。
对于SQL注入攻击,请确保检查用户输入,然后通过mysql_real_escape_string()运行它。但是,该函数不会破坏所有注入攻击,因此在将数据转储到查询字符串之前检查数据非常重要。
更好的解决方案是使用预准备语句。 PDO库和mysqli扩展程序支持这些。
PHP 5.2引入了 filter_var 函数。
它支持大量的SANITIZE,VALIDATE过滤器。
在您拥有类似/mypage?id=53
的页面并且在WHERE子句中使用id的特定情况下,可以提供帮助的一个技巧是确保id绝对是一个整数,如下所示:
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 and PHP.
设置charset明确的:
$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" ) );
手册
[deprecated在PHP5.5.0,拆除在PHP7.0.0].mysql_set_charset('utf8')
使用安全的字符集:
- 选择utf8,latin1、ascii..,不要使用脆弱字符集big5,cp932,gb2312,gbk,sjis.
使用空间化功能:
- )的预准备的发言:
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute(); 公设辩护人::报价() -地方行情围绕输入string(如需要)和逃脱的特殊字符的内输入字,使用一个引述的风格适当的基础驱动程序:
$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");公设辩护准备的发言:vs)的预备发言支持多个数据库驱动程序和命名参数:
$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[deprecated在PHP5.5.0,拆除在PHP7.0.0].- mysqli_real_escape_string 逃命中的特殊字符串中使用在SQL声明,考虑到目前的charset的连接。但是建议使用准备好的发言,因为它们不是简单逃脱串发言提出了一个完整的查询执行计划,包括其表和索引将使用,这是一个优化的方式。
- 使用单一的报价('')周围的变量内部查询。
- )的预准备的发言:
检查的变量包含什么你期待的用于:
- 如果你期待一个整数,使用:
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,可以使用pg_escape_string()
来转义PHP的输入 $username = pg_escape_string($_POST['username']);
来自文档( http://php.net/manual /es/function.pg-escape-string.php ):
pg_escape_string()转义用于查询数据库的字符串。它以PostgreSQL格式返回一个没有引号的转义字符串。
有没有包罗万象的功能,因为有很多问题有待解决。
SQL注射 -今天,一般来说,每PHP项目应该使用 准备好的发言via PHP数据对象(公设辩护人) 作为最佳做法, 防止的错误是从一个流浪的报价以及一个全功能的解决方案针对注射.它也是最灵活和安全的方式来访问你的数据库。
检查了 (唯一适当的)公设辩护人的教程 对于几乎所有你需要知道的关于公设辩护人.(诚挚的谢意到顶所以贡献者,@YourCommonSense,对于这一巨大资源的问题。)
XSS-清理数据的方式...
HTML净化器 已经有很长一段时间并且仍在积极进行更新。你可以用它来消毒的恶意的输入,同时仍然允许慷慨的&配置白名单的标签。伟大的作品的许多所见即所得的编辑人员,但它可能是沉重的一些使用情况。
在其他情况下,在这里我们不想接受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; }
XSS-清理数据的出路... 除非你保证的数据是正确的消毒之前,将其添加到数据库,则需要清理它面前显示它给你的用户,我们可以利用这些有用的PHP职能:
- 当你打电话
echo
或print
以显示用户提供的价值观,使用htmlspecialchars
除非数据是正确的消毒安全的并且是允许显示HTML。 json_encode
是一种安全的方式提供用户提供的值从PHP Javascript
- 当你打电话
你打电话外壳中使用的命令
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 &
" (double quote) becomes "
' (single quote) becomes '
< (less than) becomes <
> (greater than) becomes >
只是想在输出转义的主题上添加它,如果你使用php DOMDocument来制作你的html输出,它将在正确的上下文中自动转义。属性(值=“&quot;”)和&lt; span&gt;的内部文本不平等。 为了安全抵御XSS,请阅读: OWASP XSS预防备忘单
您永远不会清理输入。
您始终清理输出。
您应用于数据以使其安全包含在SQL语句中的转换与您申请包含在HTML中的转换完全不同,与您申请包含在Javascript中的转换完全不同,与您申请包含的转换完全不同在LDIF中完全不同于你在CSS中包含的那些与你在电子邮件中包含的那些完全不同....
无论如何验证输入 - 决定是否接受它进行进一步处理或告诉用户这是不可接受的。但是,在数据即将离开PHP之前,不要对数据的表示应用任何更改。
很久以前有人试图发明一种适用于转发数据的单一尺寸的机制,我们最终得到了“ magic_quotes &quot;它没有正确地转义所有输出目标的数据,导致不同的安装需要不同的代码才能工作。
有过滤器扩展( howto-link ,手册),适用于所有GPC变量。但这并不是一件神奇的事情,你仍然需要使用它。
我可以看到php过滤器清理特殊特殊字符派上用场。
喜欢:
$a=fliter_var(我可以看到php过滤器清理特殊特殊字符派上用场。
喜欢:
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);
}
然而,通过股票,我认为它可能会更好,因为查看c代码,它只会过滤“ '\&lt; &GT; &安培;和\ 0所以我可以看到这是一个很好的消毒方式。但是,更改源代码以包含其他字符,如/ {} []。 ; `会强化编码(enc [''])行的这个功能:
<*>POST['a'],FILTER_SANITIZE_SPECIAL_CHARS);
然而,通过股票,我认为它可能会更好,因为查看c代码,它只会过滤“ '\&lt; &GT; &安培;和\ 0所以我可以看到这是一个很好的消毒方式。但是,更改源代码以包含其他字符,如/ {} []。 ; `会强化编码(enc [''])行的这个功能:
<*>使用PHP清理用户输入的最佳BASIC方法:
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;
}