用PHP清理用户输入的最佳方法是什么?
是否有某个功能全面的工具可以很好地用于清理用户对SQL注入和XSS攻击的输入,同时仍然允许某些类型的html标签?
解决方案
不可以。如果没有数据用途的任何上下文,就不能一般地过滤数据。有时我们希望将SQL查询作为输入,而有时又希望将HTML作为输入。
我们需要过滤白名单上的输入-确保数据符合我们所期望的某种规格。然后,我们需要在使用它之前对其进行转义,具体取决于我们所使用的上下文。
为SQL转义数据以防止SQL注入的过程与为(X)HTML转义数据以防止XSS的过程非常不同。
有过滤器扩展名(howto-link,手动),可与所有GPC变量很好地配合使用。尽管这不是万能的事情,我们仍然必须使用它。
要解决XSS问题,请看一下HTML Purifier。它是相当可配置的,并且具有良好的记录。
至于SQL注入攻击,请确保检查用户输入,然后通过mysql_real_escape_string()运行它。但是,该函数不会克服所有注入攻击,因此,在将数据转储到查询字符串之前,请务必检查数据,这一点很重要。
更好的解决方案是使用准备好的语句。 PDO库和mysqli扩展支持这些。
不,那里没有。
首先,SQL注入是一个输入过滤问题,而XSS是一个转义输出的问题,因此我们甚至都不会在代码生命周期中同时执行这两个操作。
基本经验法则
- 对于SQL查询,请绑定参数(与PDO一样),或者使用驱动程序本机转义函数来查询变量(例如" mysql_real_escape_string()")
- 使用
strip_tags()
过滤掉不需要的HTML - 使用htmlspecialchars()转义所有其他输出,并注意此处的第二和第三参数。
常见的误解是可以过滤用户输入。 PHP甚至有一个(现在已弃用的)"功能",称为"魔术引号",它基于此思想。废话忘记过滤(或者清洗,或者任何人称呼它)。
为避免出现问题,我们应该做的事情很简单:每当将字符串嵌入外部代码中时,都必须根据该语言的规则对其进行转义。例如,如果我们将字符串嵌入某些以MySql为目标的SQL中,则必须为此使用MySql的函数对字符串进行转义(" mysqli_real_escape_string")。 (或者,对于数据库,在可能的情况下,使用准备好的语句是更好的方法)
另一个例子是HTML:如果将字符串嵌入HTML标记中,则必须使用htmlspecialchars
对其进行转义。这意味着每个" echo"或者" print"语句都应使用" htmlspecialchars"。
第三个示例可能是shell命令:如果要将字符串(例如参数)嵌入到外部命令中,并使用exec调用它们,则必须使用escapeshellcmd和escapeshellarg。
等等等等 ...
我们需要主动过滤数据的唯一情况是,如果我们接受预格式化的输入。例如。如果我们让用户发布HTML标记,则计划显示在网站上。但是,我们应该明智地不惜一切代价避免这种情况,因为无论我们对其进行多么好的过滤,它始终都是潜在的安全漏洞。
PHP现在有了新的漂亮的filter_input函数,例如,由于内置了FILTER_VALIDATE_EMAIL类型,因此使我们不必再寻找"最终的电子邮件正则表达式"
要真正允许使用类似剥离的类和/或者标签的HTML输入,取决于专用的xss验证程序包之一。不要写自己的正则表达式来解析HTML!
不要尝试通过清除输入数据来防止SQL注入。
而是,不允许在创建SQL代码时使用数据。使用使用绑定变量的Prepared Statements(即在模板查询中使用参数)。这是防止SQL注入的唯一方法。
请访问我的网站http://bobby-tables.com/了解有关防止SQL注入的更多信息。
在特定情况下(例如/ mypage?id = 53
)并且在WHERE子句中使用id的一种技巧可以确保id绝对是整数,例如:
/** * 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; } }
但是,当然,这只会消除一种特定的攻击,因此请阅读所有其他答案。 (是的,我知道上面的代码不是很好,但是它显示了特定的防御。)
代码数量不匹配