是否有某个功能全面的功能可以很好地用于清理用户对SQL注入和XSS攻击的输入,同时仍然允许某些类型的HTML标签?
select * from users where name='$name'
,则使用PDO还是MySQLi或MySQL都没有关系。您仍然处于危险之中。您必须使用参数化查询,或者,如果需要,请对数据使用转义机制,但这是不太可取的。
是否有某个功能全面的功能可以很好地用于清理用户对SQL注入和XSS攻击的输入,同时仍然允许某些类型的HTML标签?
select * from users where name='$name'
,则使用PDO还是MySQLi或MySQL都没有关系。您仍然处于危险之中。您必须使用参数化查询,或者,如果需要,请对数据使用转义机制,但这是不太可取的。
Answers:
常见的误解是可以过滤用户输入。PHP甚至有一个(现在已弃用的)“功能”,称为magic-quotes,它基于此思想。废话 忘记过滤(或清洁,或任何人称呼它)。
为避免出现问题,您应该做的事情很简单:每当将字符串嵌入外部代码中时,都必须根据该语言的规则对其进行转义。例如,如果您在针对MySQL的某些SQL中嵌入了字符串,则必须为此使用MySQL的函数对字符串进行转义(mysqli_real_escape_string
)。(或者,对于数据库,在可能的情况下,使用预备语句是更好的方法。)
另一个示例是HTML:如果将字符串嵌入HTML标记中,则必须使用对其进行转义htmlspecialchars
。这意味着每个单行echo
或print
语句应使用htmlspecialchars
。
第三个示例是shell命令:如果要将字符串(例如参数)嵌入到外部命令中,并使用调用它们exec
,则必须使用escapeshellcmd
和escapeshellarg
。
等等等等 ...
您需要主动过滤数据的唯一情况是接受预先格式化的输入。例如,如果您让用户发布HTML标记,那么您计划显示在网站上。但是,您应该明智地不惜一切代价避免这种情况,因为无论您对其进行多么好的过滤,它始终都是潜在的安全漏洞。
mysql_real_escape_string
已过时。如今,使用准备好的语句来防止SQL注入被认为是一种好习惯。因此,切换到MySQLi或PDO。
不要尝试通过清除输入数据来防止SQL注入。
相反,请勿在创建SQL代码时使用数据。使用使用绑定变量的预处理语句(即在模板查询中使用参数)。这是防止SQL注入的唯一方法。
请访问我的网站http://bobby-tables.com/了解更多有关防止SQL注入的信息。
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');
* $sanitize = array('alias');
*
* $validator = new FormValidator($validations, $required, $sanitize);
*
* if($validator->validate($_POST))
* {
* $_POST = $validator->sanitize($_POST);
* // now do your saving, $_POST has been sanitized.
* 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 sanitize just one element:
* $sanitized = new FormValidator()->sanitize('<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->sanitations = $sanitations;
$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);
}
/**
*
* Sanitizes an array of items according to the $this->sanitations
* sanitations will be standard of type string, but can also be specified.
* For ease of use, this syntax is accepted:
* $sanitations = array('fieldname', 'otherfieldname'=>'float');
*/
public function sanitize($items)
{
foreach($items as $key=>$val)
{
if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
$items[$key] = self::sanitizeItem($val, $this->validations[$key]);
}
return($items);
}
/**
*
* Adds an error to the errors array.
*/
private function addError($field, $type='string')
{
$this->errors[$field] = $type;
}
/**
*
* Sanitize a single var according to $type.
* Allows for static calling to allow simple sanitization
*/
public static function sanitizeItem($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查询(例如,对于SQL Server,mysql_real_escape_string()是无用的)。您可能希望在适当的应用程序层(如ORM)自动处理此问题。另外,如上所述:要输出到html,请使用其他php专用功能,例如htmlspecialchars;)
要真正允许使用类似剥离的类和/或标签的HTML输入,取决于专用的xss验证程序包之一。不要写自己的正则表达式来解析HTML!
不,那里没有。
首先,SQL注入是一个输入过滤问题,而XSS是一个转义输出的问题-因此,您甚至都不会在代码生命周期中同时执行这两个操作。
基本经验法则
mysql_real_escape_string()
)strip_tags()
过滤掉不需要的HTMLhtmlspecialchars()
此处的第二和第三参数来转义所有其他输出。要解决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
}
但是,当然,这只会消除一种特定的攻击,因此请阅读所有其他答案。(是的,我知道上面的代码不是很好,但是它显示了特定的防御。)
$id = (int)$_GET['id']
而且$que = sprintf('SELECT ... WHERE id="%d"', $id)
也很好
使用PHP清理用户输入的方法:
$ mysqli-> set_charset(“ utf8”);手册
$ pdo =新的PDO('mysql:host = localhost; dbname = testdb; charset = UTF8',$ user,$ password);手册
$ pdo-> exec(“设置名称utf8”);手册
$ pdo =新的PDO( “ mysql:host = $ host; dbname = $ db”,$ user,$ pass, 数组( PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION, PDO :: MYSQL_ATTR_INIT_COMMAND =>“设置名称utf8” ) );手册
[在PHP 5.5.0中已弃用,在PHP 7.0.0中已删除]。mysql_set_charset('utf8')
$ 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 / *”]);
ctype_digit —检查数字字符;
$ value =(int)$ value;
$ value = intval($ value);
$ var = filter_var('0755',FILTER_VALIDATE_INT,$ options);
is_string()—查找变量的类型是否为字符串
$ email = filter_var($ email,FILTER_SANITIZE_EMAIL);更多预定义的过滤器
$ newstr = filter_var($ str,FILTER_SANITIZE_STRING);
$ search_html = filter_input(INPUT_GET,'search',FILTER_SANITIZE_SPECIAL_CHARS);
如果您使用的是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项目都应通过PHP数据对象(PDO)使用预先准备好的语句,这是最佳实践,可防止因引号引起的错误以及针对注入的全功能解决方案。这也是访问数据库的最灵活,最安全的方法。
查看(唯一的)PDO教程,了解有关PDO所需的几乎所有知识。(衷心感谢顶级SO杰出贡献者@YourCommonSense,感谢他在这一主题上的宝贵资源。)
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;
}
XSS-清理输出中的数据...除非您保证在将数据添加到数据库之前已对它们进行了正确的清理,否则需要在将其显示给用户之前对其进行清理,我们可以利用以下有用的PHP函数:
echo
或print
显示用户提供的值时,请使用htmlspecialchars
该数据,除非对数据进行了正确的安全清理并允许显示HTML。json_encode
是从PHP到Javascript提供用户提供的值的安全方法您是使用exec()
还是system()
函数调用外部外壳命令,还是要调用backtick
操作员?如果是这样,除了SQL Injection&XSS之外,您可能还需要解决在服务器上运行恶意命令的用户。escapeshellcmd
如果要转义整个命令或escapeshellarg
转义单个参数,则需要使用。
mb_encode_numericentity
的htmlspecialchars
链接中进行了讨论
只是想在输出转义的主题上添加它,如果您使用php DOMDocument进行html输出,它将在正确的上下文中自动转义。属性(value =“”)和<span>的内部文本不相等。为了安全防范XSS,请阅读以下内容: OWASP XSS预防备忘单
您应用于数据以使其可以安全地包含在SQL语句中的转换与您申请包含在HTML中的转换完全不同,不同于您申请包含在Javascript中的转换与您申请包含在LDIF中的转换完全不同。与您申请包含在CSS中的完全不同。
通过一切手段验证输入 -决定你是否应该接受它作进一步处理或告知这是不能接受的用户。但是,在数据将要离开PHP之前,请不要对数据的表示进行任何更改。
很久以前,有人试图发明一种适用于所有数据转义的全尺寸机制,而我们最终得到了“ magic_quotes ”,它不能正确地为所有输出目标转义数据,并导致安装不同需要使用不同的代码。
永远不要信任用户数据。
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 >
clean_input
?您为什么要去除斜线?
有过滤器扩展名(howto-link,manual),该扩展名对所有GPC变量都非常有效。尽管这不是万能的事情,您仍然必须使用它。