PHP 用户提交数据之过滤、验证和转义
1、过滤输入
过滤输入是指转义或删除不安全的字符。在数据到达应用的存储层之前,一定要过滤输入数据,这是第一道防线。假如网站的评论框接受HTML,用户可以随意在评论中加入恶意的<script>
标签,如下所示:
<p> 这篇文章很有用! </p> <script>windows.location.href="http://www.huya.com";</script>
如果不过滤这种评论,用户刷新页面的时候就不知道会被定向到什么未知的网。
我们可以使用PHP提供的htmlentities函数过滤HTML,该函数会将所有HTML标签字符(&、<、>等)转化为对应的HTML实体,以便在应用存储层取出后安全渲染。但是有时候我们是允许用户输入某些HTML元素的,尤其是输入富文本的时候,比如图片、链接这些,但是htmlentities
不能验证HTML,检测不出输入字符串的字符集,故而无法实现这样的功能。
<?php $input = "<p><script>alert('Laravel学院');</script></p>"; echo htmlentities($input, ENT_QUOTES, 'UTF-8');
htmlentities
的第一个参数表示要处理的HTML字符串,第二个参数表示要转义单引号,第三个参数表示输入字符串的字符集编码。
与htmlentities
相对的是html_entity_decode
方法,该方法会将所有HTML实体转化为对应的HTML标签。
此外,PHP还提供了一个类似的内置函数htmlspecialchars
,该函数也是用于将HTML标签字符转化为HTML实体,只是能够转化的字符有限(参考官方文档:http://php.net/manual/zh/function.htmlspecialchars.php),如果要转化所有字符还是使用htmlentities
方法,值得一提的是和htmlentities
一样,htmlspecialchars
也有一个与之相对的方法htmlspecialchars_decode
。
如果想要直接将输入字符串中的所有HTML标签去掉,可以使用strip_tags
方法。
如果需要更加强大的过滤HTML功能,可以使用HTML Purifier库,这是一个很强健且安全的PHP库,专门用于使用指定规则过滤HTML输入。在Laravel中我们可以使用相应的扩展包来实现过滤功能:http://laravelacademy.org/post/3914.html
SQL查询
有时候应用必须根据输入数据构建SQL查询,这些数据可能来自HTTP请求的查询字符串,也可能来自HTTP请求的URI片段,一不小心,就有可能被不怀好意的人利用进行SQL注入攻击(拼接SQL语句对数据库进行破坏或者获取敏感信息)。很多初级的程序员可能会这么写代码:
$sql = sprintf( 'UPDATE users SET password = "%s" WHERE id = %s', $_POST['password'], $_GET['id'] );
这么做风险很大,比如某个人通过如下方式对HTTP发送请求:
POST /user?id=1 HTTP/1.1 Content-Length: 17 Content-Type: application/x-www-form-urlencoded password=abc”;--
这个HTTP请求会把每个用户的密码都设置为abc
,因为很多SQL数据库把—视作注释的开头,所以会忽略后续文本。
在SQL查询中一定不能使用未过滤的输入数据,如果要在SQL查询中使用输入数据,一定要使用PDO预处理语句(PDO是PHP内置的数据库抽象层,为不同的数据库驱动提供统一接口),PDO预处理语句是PDO提供的一个功能,可以用于过滤外部数据,然后把过滤后的数据嵌入SQL语句,避免出现上述SQL注入问题,此外预处理语句一次编译多次运行,可以有效减少对系统资源的占用,获取更高的执行效率。关于PDO后我们后续还会在数据库部分重点讨论。
值得注意的是,很多现代PHP框架都使用了MVC架构模式,将数据库的操作封装到了Model层,框架底层已经做好了对SQL注入的规避,只要我们使用模型类提供的方法执行对数据库的操作,基本上可以避免SQL注入风险。
我们以Laravel为例看看底层是如何规避SQL注入的,改写上面的update
语句,代码会是这样:
$id = $_GET['id']; $password = $_POST['password']; User::find($id)->update(['password'=>bcrypt($password)]);
由于模型类底层调用的是是查询构建器的方法,所以最终会调用Builder(Illuminate\Database\Query\Builder
)的update
方法:
public function update(array $values) { $bindings = array_values(array_merge($values, $this->getBindings())); $sql = $this->grammar->compileUpdate($this, $values); return $this->connection->update($sql, $this->cleanBindings($bindings)); }
这段代码传入参数是要更新的值,然后通过
$bindings
update `users` set `password` = ?, `updated_at` = ? where `id` = ?
然后最终将预处理sql语句和对应绑定关系传递给数据库去执行。
当然上面这些都是复制别人的,接下来的内容当然也差不多是抄的
当然要过滤用户提交的数据可以使用正则(只要正则写的好 什么都能验证,强大的正则)
不过PHP还是有自己的方法 就是参数比较难记 不过可以查文档呀
PHP有两个过滤的方法一个是 filter_var() 另一个是 filter_input()
fiter_var()函数
定义和用法
filter_var() 函数通过指定的过滤器过滤变量。
如果成功,则返回已过滤的数据,如果失败,则返回 false。
语法
filter_var(规定要过滤的变量, 规定要使用的过滤器的 ID, 规定包含标志/选项的数组。检查每个过滤器可能的标志和选项)
过滤器的ID
ID | 解释说明 |
---|---|
FILTER_CALLBACK | 调用用户自定义函数来过滤数据。 |
FILTER_SANITIZE_STRING | 去除标签,去除或编码特殊字符。 |
FILTER_SANITIZE_STRIPPED | "string" 过滤器的别名。 |
FILTER_SANITIZE_ENCODED | URL-encode 字符串,去除或编码特殊字符。 |
FILTER_SANITIZE_SPECIAL_CHARS | HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。 |
FILTER_SANITIZE_EMAIL | 删除所有字符,除了字母、数字以及 !#$%&'*+-/=?^_`{|}~@.[] |
FILTER_SANITIZE_URL | 删除所有字符,除了字母、数字以及 $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&= |
FILTER_SANITIZE_NUMBER_INT | 删除所有字符,除了数字和 +- |
FILTER_SANITIZE_NUMBER_FLOAT | 删除所有字符,除了数字、+- 以及 .,eE。 |
FILTER_SANITIZE_MAGIC_QUOTES | 应用 addslashes()。 |
FILTER_UNSAFE_RAW | 不进行任何过滤,去除或编码特殊字符。 |
FILTER_VALIDATE_INT | 在指定的范围以整数验证值。 |
FILTER_VALIDATE_BOOLEAN | 如果是 "1", "true", "on" 以及 "yes",则返回 true,如果是 "0", "false", "off", "no" 以及 "",则返回 false。否则返回 NULL。 |
FILTER_VALIDATE_FLOAT | 以浮点数验证值。 |
FILTER_VALIDATE_REGEXP | 根据 regexp,兼容 Perl 的正则表达式来验证值。 |
FILTER_VALIDATE_URL | 把值作为 URL 来验证。 |
FILTER_VALIDATE_EMAIL | 把值作为 e-mail 来验证。 |
FILTER_VALIDATE_IP | 把值作为 IP 地址来验证。 |
PHP filter_input() 函数
定义和用法
filter_input() 函数从脚本外部获取输入,并进行过滤。
本函数用于对来自非安全来源的变量进行验证,比如用户的输入。
本函数可从各种来源获取输入:
INPUT_GET
INPUT_POST
INPUT_COOKIE
INPUT_ENV
INPUT_SERVER
INPUT_SESSION (Not yet implemented)
INPUT_REQUEST (Not yet implemented)
如果成功,则返回被过滤的数据,如果失败,则返回 false,如果 variable 参数未设置,则返回 NULL。
filter_input(input_type, variable, filter, options)
这两个函数基本差不多 就是 filter_input() 函数比 filter_var() 函数多了一个 提交类型 过滤器ID都一样
使用方法
filet_var():
$var = '1581176417'; echo filter_var($var, FILTER_SANITIZE_STRING) . '</br>'; echo filter_var($var, FILTER_SANITIZE_STRIPPED ) . '</br>'; function convertSpace($string) { return $string.'生成的字符串'; }
filter_input()
if (!filter_input(INPUT_GET, 'email', FILTER_VALIDATE_EMAIL)) { echo "E-Mail is not valid"; } else { echo "E-Mail is valid"; }
调用用户自定义函数过滤数据
echo filter_var($var, FILTER_CALLBACK,['options'=>'convertSpace']) . '</br>'; //自定义的函数 function convertSpace($string) { return $string.'生成的字符串'; }
{{vo.time}} 回复