zoukankan      html  css  js  c++  java
  • 开发安全的Web程序

    目录
    0x1:什么是安全的Web应用程序
    0x2:过滤输入的数据
    0x3:转义输出的数据
    0x4:Register Globals
    0x5:magic_quotes_gpc
    0x6:错误信息的报告
    0x7:文件的安全
    0x8:Session的安全
    0x9:虚拟主机

    你所开发的Web应用程序,可能是用于以下用途:
    (1)用在个人网站展示图片或文章
    (2)展示商品来吸引顾客购买
    (3)公司内部使用或对外开放浏览
    (4)大型的跨国际网站

    不管是哪一种,在花费大量时间和精力建立了Web站点之后,你当然会希望运行在站点的Web程序能够正常的运行。最重要的是不会受到黑客的攻击破坏。

    在没有任何的硬件或者软件方面的设置的情况下,想要完全地保障你的Web应用程序的安全是不可能的。

    但是只要记住两个最基本的验证法则:
    (1)过滤输入的数据(filter input)
    (2)转义输出的数据(escape output)

    使用者输入的数据一定要经过过滤,才能够赋值给变量、数据库、cookie、session内。即使输入的数据是自己设置的,而不是来自别人输入的,也不能相信不会有问题。

    要输出到屏幕、数据库和文件内的数据一定要经过转义(escape)。所谓转义就是将特殊字符进行转换,使隐藏在输出数据内的恶意代码被转义而失效。




    过滤输入的数据

    PHP提供下列超全局变量(super globals),让你轻松地存取使用者输入的数据:
    (1)$_GET:从HTTP请求而来
    (2)$_POST:从表单数据而来
    (3)$_COOKIE:从cookie数据而来
    (4)$_FILES:上传的文件数据
    (5)$_SERVER:服务器的数据
    (6)$_ENV:环境变量
    (7)$_REQUEST:GET/POST/COOKIE的结合

    过滤输入的数据可以使用常规表达式(regular expression)来验证


    例如:验证邮箱地址的合法性:

    preg_match("/^.+@.+..{2,3}$/", $_POST["email"]);


    数字数据的过滤:

    所用使用GET、POST、COOKIE传递给PHP程序文件的数据,最后一定是字符串的形式。如果需要的是整数但使用字符串来传递,不但没有效率而且危险。

    如果你确定输入的数据一定是数字,你可以使用数据类型转换的方式来将输入的数据转换成数字。

    例如:stringtoint.php

    <?php
    //整数
    $id=0;
    if(!empty($_GET["id"]))
    {
        $id=(int)$_GET["id"];
    }

    //浮点数
    $price=0;
    if(!empty($_GET["price"]))
    {
        $price=(float)$_GET["price"];
    }
    ?>

    另一种方式是使用intval函数来将数据转换为整数,使用floatval函数来将数据转换成浮点数。

    例如:val.php

    <?php
    //整数
    $id=0;
    if(!empty($_GET["id"]))
    {
        $id=intval($_GET["id"]);
    }

    //浮点数
    $price=0;
    if(!empty($_GET["price"]))
    {
        $price=floatval($_GET["price"]);
    }
    ?>

    使用上述两种方法,就可以确保输入的数据一定是数字。



    字符串数据的过滤函数:

    PHP提供ctype_系列的函数来验证字符串的内容

    (1)ctype_alnum -- Check for alphanumeric character(s)
       检测是否是只包含[A-Za-z0-9]

    (2)ctype_alpha -- Check for alphabetic character(s)
       检测是否是只包含[A-Za-z]

    (3)ctype_cntrl -- Check for control character(s)
       检查是否是只包含类是“ ”之类的字 符控制字符

    (4)ctype_digit -- Check for numeric character(s)
       检查时候是只包含数字字符的字符串(0-9)

    (5)ctype_graph -- Check for any printable character(s) except space
       检查是否是只包含有可以打印出来的字符(除了空格)的字符串

    (6)ctype_lower -- Check for lowercase character(s)
       检查是否所有的字符都是英文字母,并且都是小写的

    (7)ctype_print -- Check for printable character(s)
       检查是否是只包含有可以打印出来的字符的字符串

    (8)ctype_punct -- Check for any printable character which is not whitespace or an alphanumeric character
       检查是否是只包含非数字/字符/空格的可打印出来的字符

    (9)ctype_space -- Check for whitespace character(s)
       检查是否是只包含类是“ ”之类的字符和空格

    (10)ctype_upper -- Check for uppercase character(s)
       检查是否所有的字符都是英文字母,并且都是大写的

    (11)ctype_xdigit -- Check for character(s) representing a hexadecimal digit
       检查是否是16进制的字符串,只能包括 “0123456789abcdef”

    例如:instance.php

    <?php
    if(!ctype_alnum($_GET["username"]))
    {
        echo "只允许A-Z,a-z,0-9的字符串";
    }

    if(!ctype_alpha($_GET["name"]))
    {
        echo "只允许A-Z,a-z的字符";
    }

    if(!ctype_xdigit($_GET["rgb"]))
    {
        echo "只允许16进制数字的字符";
    }
    ?>

    例如:backlogin.php

    如果用户输入的账户只包含字母或数字,就将输入的数据保存在数组中

    <?php
    $clear=array();
    if(ctype_alnum($_POST["username"]))
    {
        $clear["username"]=$_POST["username"];
    }
    ?>


    HTML与PHP标签的过滤

    strip_tags函数可以用来去除输入字符串中的HTML与PHP标签。当使用者输入的字符串中包含HTML标签或者javascript代码时,使用strip_tags函数就可以将这些能够执行的HTML标签或者javascript程序代码过滤掉。

    例如:strip.php

    <?php
    function _INPUT($var_name)
    {
        if($_SERVER["REQUEST_METHOD"] == "GET")
        {
            return strip_tags($_GET[$var_name]);
        }

        if($_SERVER["REQUEST_METHOD"] == "POST")
        {
            return strip_tags($_POST[$var_name]);
        }
    }

    $username = _INPUT("username");
    ?>

    不管是从GET还是POST方式取得的数据,都先经过strip_tags函数来删除输入字符串中的HTML与PHP标签。


    文件路径的过滤

    传递给PHP程序文件的文件路径值,通常是用来指定要打开的文件。这个路径值也需要过滤,以避免黑客存取任意的文件。

    例如:openfile.php

    <?php
        $fp=fopen("/home/user/{$_GET['file']}", "r");
    ?>

    黑客可以使用下列URI来发动目录穿越攻击:

    openfile?file=../../etc/passwd

    PHP提供basename函数来删除文件路径中除了最后的文件名称以外的所有路径字符串。

    例如:basename.php

    <?php
        $_GET["file"] = basename($_GET["file"]);
        if(file_exists("/home/user/{$_GET['file']}"))
        {
            $fp=fopen("/home/user/{$_GET['file']}", "r");
        }
    ?>

    更好的方法是隐藏文件的名称不要让使用者知道,并且创建允许文件的名称列表以在程序中存取

    例如:filelist.php

    <?php
        //创建允许文件的名称列表
        $file_list=array();

        //将允许的文件以.lst的扩展名保存
        foreach(glob("*.lst") as $filename)
        {
            $file_list[md5($filename)]=$filename;
        }

        //URL中的文件名称存在于允许文件的名称列表内
        if(isset($file_list($_GET["file"])))
        {
            $fp=fopen($file_list[$_GET["file"]], "r");
        }
    ?>


    序列化字符串的过滤

    许多应用程序会使用GET、POST或者COOKIE方法来传递序列化字符串(serialized data)。序列化字符串是一种PHP的内部格式,用来传递复杂的变量类型,例如数组或者对象。

    序列化字符串的格式并没有内部检验的机制,因此几乎任何形态的输入数据都可能会被接受。特别设计的连续字符串有以下作用:
    (1)让PHP应用程序崩溃
    (2)消耗大量的内存
    (3)在某些PHP版本上能引发命令植入攻击

    解决的方法如下:

    (1)尽量不要依赖使用者可以存取的方法来传递序列化字符串
    (2)创建数据的checksum,在将序列化字符串传递给unserialize函数之前先检查checksum是否一致。

    例如:checksum.php

    <?php
    if(md5($_POST["serialize_data"]) == $_SESSION["checksum"])
    {
        $data = unserialize($_POST["serialize_data"]);
    }
    else
    {
        //生成警告信息
        trigger_error("可疑的序列化数据", E_USER_ERROR);
    }
    ?>




    转义输出的数据

    转义(escape)是将特定的字符加上特定的符号。

    例如,设置php.ini文件的magic_quotes_gpc=On,就会将输入字符串内的所有"'"、"""、"",以及NULL字符都加上一个""来转义。

    如果不是SQL表达式的字符串,你使用htmlspecialchars函数或htmlentities函数来转义。

    htmlspecialchars函数会将下列的特殊字符转换成HTML字符吗:

    (1)& 转换成 &amp;
    (2)当没有设置ENT_NOQUOTES时,双引号转换成 &quot;
    (3)当设置ENT_QUOTES时,单引号转换成 &#039;
    (4)"<" 转换成 &lt;
    (5)">" 转换成 &gt;

    htmlentities函数会将所有的特殊字符都转换成HTML字符吗。如果是SQL表达式的字符串,使用mysql_real_escape_string函数来转义。


    基本的转义程序:

    <?php
    $html=array();
    $html["username"]=htmlspecialchars($clean["username"], ENT_QUOTES);
    $html["username"]=htmlentities($clean["username"], ENT_QUOTES, "utf-8");
    echo "<p>访客:{$html["username"]}</p>";
    ?>

    转义SQL表达式的字符串:

    <?php
    $mysql=array();
    $mysql["username"]=mysql_real_escape_string($clean["username"]);
    $sql = "select * from member where username = '{$mysql['username']}'";
    $result = mysql_query($sql);
    ?>

    使用addslashes函数:

    addslashes函数会将字符串中的单引号、双引号、反斜杠以及NULL字符加上反斜杠:

    <?php
    $mysql=array();
    $mysql["username"] = addslashes($clean["username"]);
    $sql = "select * from member where username = '{$mysql['username']}'";
    $result = mysql_query($sql);
    ?>




    Register Globals

    在php.ini文件中设置register_globals=On的时候,就会打开register globals的功能。register globals被认为是PHP应用程序的最主要漏洞,原因如下:
    (1)任何输入的参数都会被转换成变量,例如在URI中设置:
    ?username=milantgh
    在PHP程序文件中,$username会被设置为milantgh
    $username="milantgh"
    (2)无法确定输入数据的来源,有优先权的来源会覆盖GET的数值,例如:cookie
    (3)未初始化的变量可能经由使用者输入的数据来植入,例如:over.php
    <?php
    //使用者经过check_user自定义函数的验证
    if(check_user())
    {
        $authorized = true;
    }

    if($authorized)
    {
        include "/home/user/data.php";
    }
    ?>

    如果使用者验证失败的话,$authorized变量就不会初始化数据,黑客可以使用GET方法来传递数据给$authorized变量:
    over.php?authorized=1 (1 == true)

    解决的方法如下:

    (1)在php.ini文件中设置register_globals=Off,从PHP 4.2.0开始这就是默认值
    (2)在php.ini文件中设置error_reporting=E_ALL,当使用未初始化的变量时就会收到警告信息
    (3)由于从GET得到的使用者输入一定是字符串,因此比较数据的类型就可以辨认变量数据的来源。将使用者输入的数据与布尔或整数进行数据类型的比对,永远会失败。例如:convert.php
    <?php
    //如果使用者经过check_user自定义函数的验证
    if(check_user())
    {
        $authorized = true;
    }

    if($authorized === true)
    {
        include "/home/user/data.php";
    }
    ?>

    隐藏Register Globals所发生的问题,例如:hidden.php
    <?php
    $var[] = "123";
    foreach($var as $v)
    {
        echo "$v=$v"."<br />";
    }
    ?>

    黑客可以使用GET方法来传递数据给$var数组:

    hidden.php?var[]=1&var[]=2

    这会将1和2写入$var数组中,PHP没有提供工具来检测这种赋值的问题。



    $_REQUEST变量

    $_REQUEST自动变量会从不同的输入来源中合并数据,因此与register globals一样容易遭受输入数据的攻击。$_REQUEST从不同的输入来源读取的顺序是由php.ini文件中的variables_order来决定的:
    variables_order = "EGPCS"

    EGPCS分别代表的数据输入来源如下:

    E:$_ENV环境变量
    G:$_GET变量
    P:$_POST变量
    C:$_COOKIE变量
    S:$_SERVER服务器变量

    数据的加载顺序是从左到右,新的数据会覆盖旧的数据,例如:

    $_GET["id"]=1,$_COOKIE["id"]=2,结果$_COOKIE["id"]的数据会覆盖$_GET["id"]的数据,所以,$_REQUEST["id"]=2。


    $_SERVER变量

    虽然$_SERVER自动变量是根据服务器所提供的数据而来,但是一样不能完全相信$_SERVER的数据不会有问题:
    (1)恶意的使用者可以在HTTP表头中插入javascript程序代码:
    Host:<script>alert("Hello milantgh");</script>
    (2)有些$_SERVER变量的数值是根据使用者的输入而来,例如:REQUEST_URI、PATH_INFO、QUERY_STRING
    (3)可能是使用匿名的代理服务器所伪造的IP地址




    magic_quotes_gpc

    如果将php.ini文件中的magic_quotes_gpc=On,那么PHP会自动将字符串中的单引号、双引号、反斜杠以及NULL字符都加上一个反斜杠来转义,来保护你的应用程序不会遭受黑客的攻击。

    使用magic_quotes_gpc=On有如下的缺点:
    (1)使用magic_quotes_gpc会让输入的处理变慢
    (2)使用数据类型来转换整数的输入会比较好
    (3)每个输入的数据都需要两倍的内存
    (4)如果在php.ini文件中被禁止就不能使用
    (5)还有其他的字符可能也需要转义

    例如:escape.php

    <?php
    //magic_quotes_gpc为On
    if(get_magic_quotes_gpc())
    {
        $data = array(&$_GET, &$_POST, &$_COOKIE);

        while(list($item, $val) == each($data))
        {
            foreach($val as $key=>$value)
            {
                if(!is_array($value))
                {
                    $data[$item][$key] = stripslashes($value);

                    continue;
                }

                $data[] = &$data[$item][$key];
            }
        }

        unset($data);
    }
    ?>




    错误信息的报告

    默认情况下PHP会在屏幕上打印所有的错误信息。这些错误信息会让使用者受到惊吓,甚至有时候会显示服务器的信息,例如:文件路径、未初始化的变量、函数参数、账号密码。

    但是,关掉错误报告又会让程序无法解决问题

    你可以将php.ini文件的display_errors=Off,使错误信息不会被显示在屏幕上:

    ini_set("display_errors", FALSE);

    你可以将php.ini文件的log_errors=Off,并且将error_log设置为指定的文件名称,将错误信息写入到自定义的文件中:

    ini_set("log_errors", TRUE);

    ini_set("error_log", "/user/log/error.log");

    或者写到系统的log文件中:

    ini_set("error_log", "syslog");





    文件的安全

    许多PHP应用程序需要用到各种公用文件(utility file)或者是配置文件(configuration file),如果这些文件放在Web应用程序的文件夹内,使用者就可以下载或者是查看文件的内容。

    因此,为了保障文件的安全,不要将不必要的文件放在Web应用程序的根目录内;使用.htaccess文件来限制文件或者文件夹的存取。



    Session的安全

    Session是Web应用程序用来跟踪使用者操作的常用工具,尤其是用在各个页面中识别使用者的身份。如果黑客可以存取到作用中的session,那么这个session的使用者的身份就会被黑客所盗用。

    Session固定攻击

    Session固定攻击是将Session id的数据设置成某个已知的数据。如果成功的话,黑客只要传递这个已知的Session id,就可以冒充目标用户的身份。

    攻击的方式最常见的就是让使用者单击黑客设置好的超级链接,在这个超级链接里面植入已知的Session id的数据:

    <a href="http://www.milantgh.com/?PHPSESSID=123">XSS脚本系列新书</a>

    如果使用者还没有建立Session,那么他的Session id就是123。

    要避免使用到黑客指定的Session id,登陆后应该立即调用session_regenerate_id函数来产生一个新的Session id:

    <?php
    session_start();
    //检查登陆的程序代码
    //使用者成功登陆
    if($login_OK)
    {
        //创建新的Session id
        session_regenerate_id();
    }
    ?>

    另一种保护session安全的技术,就是比较浏览器的表头字符串:

    <?php
    session_start();
    $session_code = @md5($_SERVER["HTTP_ACCEPT_CHARSET"].$_SERVER["HTTP_ACCEPT_ENCODING"].$_SERVER["HTTP_ACCEPT_LANGUAGE"].$_SERVER["HTTP_USER_AGENT"]);
    if(empty($_SESSION))
    {
        $_SESSION["session_code"]=$session_code;
    }
    else if($_SESSION["session_code"] != $session_code)
    {
        session_destroy();
    }
    ?>

    Session的保存

    默认PHP的Session是保存在文件内,这个文件放置在一般的temp文件夹中。

    这表示在系统上的任何使用者都可以看到作用中的Session,甚至修改Session的内容。

    解决的方式就是使用php.ini文件内的Session.save_path选项来另外指定保存Session文件的文件夹。



    虚拟主机

    大部分的PHP应用程序是在虚拟主机(shared hosting)的环境内执行,所有的使用者共同分享服务器的资源。这表明网站服务器可以读取主机内的所有文件。这就是我们常说的旁注入侵。

    PHP的解决方式是使用php.ini文件内的两个选项:
    (1)open_basedir:用来限制能够存取文件的文件夹。(这种做法相当有效率,而且也不复杂)
    (2)safe_mode:根据程序文件的使用者id或者用户组id来限制文件的存取。(这种做法速度慢,而且复杂,黑客可以轻易绕过)


    可预测的临时文件名称

    临时文件夹内的已知名称并且可以写入的文件,可以使用symlink函数来建立符号链接:

    例如:link.php

    <?php
    //清除旧的错误
    $fp=fopen("/temp/script_errors", "w");
    fclose($fp);
    ?>

    黑客可以使用symlink函数来建立符号链接:

    <?php
    symlink("/etc/passwd", "/temp/script_errors");
    ?>

    解决的方法如下:
    (1)不要使用可预测的文件名称,tmpfile函数可以用来建立一个临时文件,tempnam函数可以使用特殊的文件名称来建立文件
    (2)如果无法避免要使用已知的文件名称,你可以使用is_link函数来测试文件名称是否是符号链接。如果要打开文件来清除旧的错误,不如直接使用unlink函数来删除整个文件。


    隐藏表头的信息
    (1)将php.ini文件中的PHP识别表头功能禁止
    expose_php=Off
    (2)将http.conf文件中的Apache识别表头功能禁止:
    ServerSignature=Off


    系统异常的监测

    下列属于主机的异常行为:
    A:不正常断线
    B:不正常重新开机
    C:多余的网络连接
    D:过高的CPU使用率
    E:登陆文件丢失
    F:文件权限被修改
    G:不知来源的隐藏文件

  • 相关阅读:
    ASP.NET WEB API 自定义模型校验过滤器
    使用asp.net mvc部分视图渲染html
    .Net中的并行编程-7.基于BlockingCollection实现高性能异步队列
    python爬虫技术的选择
    优雅的处理异常
    解决asp.net动态压缩
    .Net中的并行编程-6.常用优化策略
    使用快捷键提升C#开发效率
    .Net中的并行编程-5.流水线模型实战
    .Net中的并行编程-4.实现高性能异步队列
  • 原文地址:https://www.cnblogs.com/milantgh/p/3764470.html
Copyright © 2011-2022 走看看