[PHP] 06 - Security: Error, Exception and Filter

时间:2024-11-15 17:36:01

前言


Ref: PHP 发送电子邮件

Ref: PHP Secure E-mails

PHP发邮件部分在此系列中略。

这里展开”安全“相关的部分。

有啥区别? 

Ref: PHP异常与错误处理机制

PHP错误:是属于php程序自身的问题,一般是由非法的语法,环境问题导致的,使得编译器无法通过检查,甚至无法运行的情况。平时遇到的warming、notice都是错误,只是级别不同而已。

PHP异常:一般是业务逻辑上出现的不合预期、与正常流程不同的状况,不是语法错误。

PHP异常处理机制借鉴了java  c++等,但是PHP的异常处理机制是不健全的。异常处理机制目的是将 程序正常执行的代码  与 出现异常如何处理的代码分离。

PHP是无法自动捕获异常的(绝大多数),只有主动抛出异常并捕捉。也就是说,对于异常,是可预见的。

错误处理


Ref: http://www.runoob.com/php/php-error.html

一、die() 函数

<?php
if(!file_exists("welcome.txt"))
{
die("文件不存在"); // 返回可控的错误信息
}
else
{
$file=fopen("welcome.txt","r");
}
?>

二、专用错误处理函数

创建了一个专用函数,可以在 PHP 中发生错误时调用该函数。

可以接受最多五个参数(可选的:file, line-number 和 error context):

参数 描述
error_level 必需。为用户定义的错误规定错误报告级别。必须是一个数字。参见下面的表格:错误报告级别。
error_message 必需。为用户定义的错误规定错误消息。
error_file 可选。规定错误发生的文件名。
error_line 可选。规定错误发生的行号。
error_context 可选。规定一个数组,包含了当错误发生时在用的每个变量以及它们的值。
  • 综合例子:通过 E-Mail 发送错误消息

在下面的例子中,如果特定的错误发生,我们将发送带有错误消息的电子邮件,并结束脚本:

<?php
// 错误处理函数
function customError($errno, $errstr) // (3) this is the error process.
{
echo "<b>Error:</b> [$errno] $errstr<br>";
echo "已通知网站管理员";
error_log("Error: [$errno] $errstr",1, // (4) 可以通过使用 error_log() 函数,您可以向指定的文件或远程目的地发送错误记录
"someone@example.com","From: webmaster@example.com");
} // 设置错误处理函数
set_error_handler("customError",E_USER_WARNING);      // (2) this is the type, thus customError will be run to handle the error process. // 触发错误
$test=2;
if ($test>1)  // Jeff: 其实算异常,因为是逻辑错误,非系统错误
{
trigger_error("变量值必须小于等于 1",E_USER_WARNING);  // (1) trigger this error and it is a E_USER_WARNING type.
}
?>

异常处理


Ref: http://www.runoob.com/php/php-exception.html

一、抛出异常

  • 生成一个异常

不符合逻辑就抛出异常,走特殊通道。

<?php
// 创建一个有异常处理的函数
function checkNum($number)
{
if($number>1)
{
throw new Exception("Value must be 1 or below");
}
return true;
} // 触发异常
checkNum(2);
?>

异常信息显示:

Fatal error: Uncaught exception 'Exception' with message 'Value must be 1 or below' in /www/runoob/test/test.php:7 Stack trace: #0 /www/runoob/test/test.php(13): checkNum(2) #1 {main} thrown in /www/runoob/test/test.php on line 7
  • Try、throw 和 catch
<?php
// 创建一个有异常处理的函数
function checkNum($number)
{
if($number>1)
{
throw new Exception("变量值必须小于等于 1");
}
return true;
} // 在 try 块 触发异常
try
{
checkNum(2);
// 如果抛出异常,以下文本不会输出
echo '如果输出该内容,说明 $number 变量';
}
catch(Exception $e)
{
echo 'Message: ' .$e->getMessage();
}
?>

二、自定义的 Exception 类

  • 创建 customException 类
<?php
class customException extends Exception
{
public function errorMessage()
{
// 错误信息
$errorMsg = '错误行号 '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b> 不是一个合法的 E-Mail 地址';
return $errorMsg;
}
}

-----------------------------------------------------------------------
$email = "someone@example...com"; try
{
// 检测邮箱
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
{
// 如果是个不合法的邮箱地址,抛出异常
throw new customException($email);
}
} catch (customException $e)
{
  // display custom message
  echo $e->errorMessage();
}
?>
  • 多个异常

可以为一段脚本使用多个异常,来检测多种情况。

<?php
class customException extends Exception
{
public function errorMessage()
{
// 错误信息
$errorMsg = '错误行号 '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b> 不是一个合法的 E-Mail 地址';
return $errorMsg;
}
} $email = "someone@example.com"; try
{
// 检测邮箱
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
{
// 如果是个不合法的邮箱地址,抛出异常
throw new customException($email);
}
// 检测 "example" 是否在邮箱地址中
if(strpos($email, "example") !== FALSE)
{
throw new Exception("$email 是 example 邮箱");
}
}
catch (customException $e)
{
echo $e->errorMessage();
}
catch(Exception $e)
{
echo $e->getMessage();
}
?>
  • 重新抛出异常

在一个 "catch" 代码块中再次抛出异常。

<?php
class customException extends Exception
{
public function errorMessage()
{
// 错误信息
$errorMsg = $this->getMessage().' 不是一个合法的 E-Mail 地址。';
return $errorMsg;
}
} ------------------------------------------------------------------------------- $email = "someone@example.com"; try
{
try
{
// 检测 "example" 是否在邮箱地址中
if(strpos($email, "example") !== FALSE)
{
// 如果是个不合法的邮箱地址,抛出异常
throw new Exception($email);
}
}
catch(Exception $e)
{
// 重新抛出异常
throw new customException($email);  // 普通异常的处理其实跳转到了自定义异常
}
}
catch (customException $e)
{
// 显示自定义信息
echo $e->errorMessage();
}
?>

三、顶层异常处理器

set_exception_handler() 函数可设置处理所有未捕获异常的用户定义函数。

也就是”忘了写try...catch“,哈哈。

<?php
function myException($exception)
{
echo "<b>Exception:</b> " , $exception->getMessage();
} set_exception_handler('myException'); throw new Exception('Uncaught Exception occurred');
?>

PHP 过滤器


Ref: http://www.runoob.com/php/php-filter.html

Ref: http://www.runoob.com/php/php-filter-advanced.html

类似于node里的assert:NodeJS 断言的使用

应该始终对外部数据进行过滤!

输入过滤是最重要的应用程序安全课题之一。

什么是外部数据?

  • 来自表单的输入数据
  • Cookies
  • Web services data
  • 服务器变量
  • 数据库查询结果

一、Filter 函数

filter_has_var     — 检测接收指定类型的变量是否存在,例如通过post传递的username变量是否存在
filter_id — 返回与某个特定名称的过滤器相关联的id
filter_input_array — 获取一系列外部变量,并且可以通过过滤器处理它们
filter_input — 通过名称获取特定的外部变量,并且可以通过过滤器处理它
filter_list — 返回所支持的过滤器列表
filter_var_array — 获取多个变量并且过滤它们
filter_var — 使用特定的过滤器过滤一个变量

二、常见验证示范

Ref: 使用 PHP 过滤器(Filter)进行严格表单验证

  • [1] 类型 $type
filter_input(int $type, string $variable_name [, int $filter = FILTER_DEFAULT] [, mixed $options])

参数 $type 可以是 INPUT_GET,INPUT_POST,INPUT_COOKIE,INPUT_SERVER 或 INPUT_ENV

参数 $filter 的类型可以参见 php 手册:http://php.net/manual/zh/filter.filters.php

  • [2] 何时用

使用 Filter 可以对表单必填域验证、数字验证、email 验证、下拉菜单验证、单选按钮验证、复选框验证等。

使用 Filter 可以节省很多正则,例如验证 email、INT、bool ,并且 filter_has_var 函数比 isset 函数要更快。

(1) 验证必填域

//检查$_POST['username']长度之前首先确保它存在
if(! (filter_has_var(INPUT_POST, 'username') && (strlen(filter_input(INPUT_POST, 'username')) > 0) )) {
echo '请输入用户名';
exit;
}

说明:filter_has_var 函数在接收到了变量时对变量的值进行验证,在该例中,

-- 如果接受到了 $_POST['username'] ,即对 $_POST['username'] 的值进行验证,

-- 如果没有接收到变量 $_POST['username'],例如该字段在表单中是单个的复选框,不勾选的话,处理的页面是接收不到该字段的信息的。

(2) 验证长度

//FILTER_SANITIZE_STRING 过滤器 会去除HTML标记、删除二进制非ASCII字符、并对与字符编码(&)
if(filter_has_var(INPUT_POST, 'country') && strlen(filter_input(INPUT_POST, 'country', FILTER_SANITIZE_STRING)) <=2) {
echo 'country长度不小于2个字符';
exit;
}

说明:参数 FILTER_SANITIZE_STRING 用于去除 HTML 标记、删除二进制非 ASCII 字符、并且对与字符编码(&)

(3) 验证邮箱

//验证邮箱
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if($email === false) {
echo '请输入正确的邮箱';
exit;
}

说明:使用参数 FILTER_VALIDATE_EMAIL 验证 email

(4) 验证整数

//验证整数,如果填写了年龄则进行验证
if(strlen(filter_input(INPUT_POST, 'age')) > 0) {
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);
if($age === false) {
echo '请输入正确的年龄';
exit;
}
}

说明:使用参数FILTER_VALIDATE_INT  验证整数

(5) 验证小数

//验证小数,如果填写了salary则进行验证
if(strlen(filter_input(INPUT_POST, 'salary')) > 0) {
$salary = filter_input(INPUT_POST, 'salary', FILTER_VALIDATE_FLOAT);
if($salary === false) {
echo '请输入正确的薪资';
exit;
}
}

说明:使用参数 FILTER_VALIDATE_FLOAT 验证浮点数

(6) 验证数组,复选框验证组

//确保$_POST['sports']存在且是一个数组
if(! (filter_has_var(INPUT_POST, 'sports') && filter_input(INPUT_POST, 'sports', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY))) {
echo '请选择一项运动';
exit;
} //验证复选框组
//array_intersect 计算数组的交集
if(array_intersect($_POST['sports'], array_values($sports)) != $_POST['sports']) {
echo '请选择正确的运动';
exit;
}

说明:使用参数 FILTER_DEFAULT 进行占位,使用参数 FILTER_REQUIRE_ARRAY 验证是否是数组

使用 array_intersect 函数计算数组的交集

(7) 验证单个复选框

//验证单个复选框
if(filter_has_var(INPUT_POST, 'single')) {
if($_POST['single'] == $value) {
$single = true;
} else {
$single = false;
echo '错误的提交';
exit;
}
}

(8) 验证下拉菜单

//验证下拉菜单
if(! (filter_has_var(INPUT_POST, 'food') && array_key_exists($_POST['food'], $choices))) {
echo '请选择喜欢的食物';
exit;
}

(9) 验证单选按钮

//验证性别
if(! (filter_has_var(INPUT_POST, 'sex') && in_array($_POST['sex'], $sex))) {
echo '请选择性别';
exit;
}

(10) 验证时间

//验证时间
if(filter_has_var(INPUT_POST, 'time')) {
foreach($_POST['time'] as $time) {
@list($year, $month, $day) = explode('-', $time);
if(! @checkdate($month, $day, $year)) {
echo '时间错误';
exit;
}
//时间段验证(略)
}
}

说明:使用 checkdate 函数验证是否是正确的时间

三、Validating 和 Sanitizing

Validating 过滤器

Sanitizing 过滤器

  • 选项 和 标志 

选项和标志用于向指定的过滤器添加额外的过滤选项。

不同的过滤器有不同的选项和标志。

在下面的实例中,我们用 filter_var() 和 "min_range" 以及 "max_range" 选项验证了一个整数:

<?php
$var=300; $int_options = array(
"options"=>array      # 选项必须放入一个名为 "options" 的相关数组中
(
"min_range"=>0,
"max_range"=>256
)

); ----------------------------------------------------------------------------- if(!filter_var($var, FILTER_VALIDATE_INT, $int_options))
{
echo("不是一个合法的整数");
}
else
{
echo("是个合法的整数");
}
?>
  • 验证输入
<?php
if(!filter_has_var(INPUT_GET, "email"))  # 检测是否存在 "GET" 类型的 "email" 输入变量
{
echo("没有 email 参数");
}
else
{
# 如果存在输入变量,检测它是否是有效的 e-mail 地址
if (!filter_input(INPUT_GET, "email", FILTER_VALIDATE_EMAIL))
{
echo "不是一个合法的 E-Mail";
}
else
{
echo "是一个合法的 E-Mail";
}
}
?>
  • 净化输入

从表单传来的 URL,处理一下。

<?php
if(!filter_has_var(INPUT_GET, "url"))
{
echo("没有 url 参数");
}
else
{
$url = filter_input(INPUT_GET, "url", FILTER_SANITIZE_URL);
echo $url;
}
?>

效果:http://www.ruåånoøøob.com/   ---->   [净化后] ---->  http://www.runoob.com/

  • 过滤多个输入

毕竟,表单通常由多个输入字段组成。

为了避免对 filter_var 或 filter_input 函数重复调用,我们可以使用 filter_var_array 或 the filter_input_array 函数。

使用 filter_input_array() 函数来过滤三个 GET 变量。接收到的 GET 变量是一个名字、一个年龄以及一个 e-mail 地址:

<?php
$filters = array
(
"name" => array
(
"filter"=>FILTER_SANITIZE_STRING
),
"age" => array
(
"filter"=>FILTER_VALIDATE_INT,
"options"=>array
(
"min_range"=>1,
"max_range"=>120
)
),
"email"=> FILTER_VALIDATE_EMAIL
); $result = filter_input_array(INPUT_GET, $filters); if (!$result["age"])
{
echo("年龄必须在 1 到 120 之间。<br>");
}
elseif(!$result["email"])
{
echo("E-Mail 不合法<br>");
}
else
{
echo("输入正确");
}
?>
  • 使用 Filter Callback - 自定义过滤器

这样,我们就拥有了数据过滤的完全控制权。

您可以创建自己的自定义函数,也可以使用已存在的 PHP 函数。

将您准备用到的过滤器的函数,按指定选项的规定方法进行规定。在关联数组中,带有名称 "options"。

在下面的实例中,我们使用了一个自定义的函数把所有 "_" 转换为 ".":

<?php
function convertSpace($string)
{
return str_replace("_", ".", $string);
} $string = "www_runoob_com!";
echo filter_var($string, FILTER_CALLBACK, array("options"=>"convertSpace"));
?>

Jeff: 这里关注下option的用法。

四、PHP 高级过滤器

  • 检测一个数字是否在一个范围内
<?php
$int = 122;
$min = 1;
$max = 200; if (filter_var($int, FILTER_VALIDATE_INT, array("options" => array("min_range"=>$min, "max_range"=>$max))) === false) {
echo("变量值不在合法范围内");
} else {
echo("变量值在合法范围内");
}
?>
  • 检测 IPv6 地址
<?php
$ip = "2001:0db8:85a3:08d3:1319:8a2e:0370:7334"; if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
echo("$ip 是一个 IPv6 地址");
} else {
echo("$ip 不是一个 IPv6 地址");
}
?>
  • 检测 URL - 必须包含 QUERY_STRING(查询字符串)
<?php
$url = "http://www.runoob.com"; if (!filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_QUERY_REQUIRED) === false) {
echo("$url 是一个合法的 URL");
} else {
echo("$url 不是一个合法的 URL");
}
?>
  • 移除 ASCII 值大于 127 的字符
<?php
$str = "<h1>Hello WorldÆØÅ!</h1>"; $newstr = filter_var($str, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH);
echo $newstr;
?>
  • PHP 过滤器参考手册

你也可以通过访问本站的 PHP 过滤器参考手册 来查看过滤器的具体应用。