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

前言


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_GETINPUT_POSTINPUT_COOKIEINPUT_SERVERINPUT_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 过滤器参考手册 来查看过滤器的具体应用。

 

posted @ 2018-06-29 19:09  郝壹贰叁  阅读(200)  评论(0编辑  收藏  举报