目录
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)
   检查是否是只包含类是“\n\r\t”之类的字 符控制字符

(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时,单引号转换成 '
(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:不知来源的隐藏文件

开发安全的Web程序的更多相关文章

  1. [Ruby on Rails系列]3、初试Rails:使用Rails开发第一个Web程序

    本系列前两部分已经介绍了如何配置Ruby on Rails开发环境,现在终于进入正题啦! Part1.开发前的准备 本次的主要任务是开发第一个Rails程序.需要特别指出的是,本次我选用了一个(Paa ...

  2. 使用MyEclipse开发第一个Web程序

    MyEclipse环境配置 首先,安装一个MyEclipse,然后进行一些相关的环境配置(Window->Preferences): 比如字体.Formatter等. 也可以从Eclipse中导 ...

  3. Web程序员开发App系列 - 开发我的第一个App,源码下载

    Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...

  4. Web程序员开发App系列 - 调试Android和IOS手机代码(补图)

    Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...

  5. Web程序员开发App系列 - 申请苹果开发者账号

    Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...

  6. Web程序员开发App系列 - 认识HBuilder

    Web程序员开发App系列 Web程序员开发App系列 - 认识HBuilder Web程序员开发App系列 - 申请苹果开发者账号 Web程序员开发App系列 - 调试Android和iOS手机代码 ...

  7. ASP.NET 5系列教程 (五):在Visual Studio 2015中使用Grunt、Bower开发Web程序

    基于Visual Studio 2015,你可以: 方便的管理前端包,如jQuery, Bootstrap, 或Angular. 自动运行任务,如LESS.JavaScript压缩.JSLint.Ja ...

  8. 使用Node.js的socket.io模块开发实时web程序

    首发:个人博客,更新&纠错&回复 今天的思维漫游如下:从.net的windows程序开发,摸到nodejs的桌面程序开发,又熟悉了一下nodejs,对“异步”的理解有了上上周对操作系统 ...

  9. 关于基于.net的WEB程序开发所需要的一些技术归纳

    前提: 最近公司里有一个同事,年龄比我大几岁,但是由于是转行来做开发的,许多的关于.net开发技术不是很入行,所以总是会问我一些东西,基于自己以前的一些 经验,总是会愿意给他讲一些总结性的东西,希望他 ...

随机推荐

  1. RabbitMQ 原文译03--发布和订阅

    发布/订阅 在之前的案例中我们创建了一个工作队列,这个工作队列的实现思想就是一个把每一个任务平均分配给每一个执行者,在这个篇文章我们会做一些不一样的东西,把一个消息发送给多个消费者,这种模式就被称作& ...

  2. MD5、拼音检索和邮件发送

    MD5算法 MD5算法是一种散列(hash)算法(摘要算法,指纹算法),不是一种加密算法(易错) l  为了防止用户偷懒,算两次MD5值,或者加上一个固定的字符串 MD5算法理论上是不可逆的,因此攻击 ...

  3. SQLite 入门教程(一)基本控制台(终端)命令

    一.基本简介 SQLite 是一个自持的(self-contained).无服务器的.零配置的.事务型的关系型数据库引擎.因为他很小,所以也可以作为嵌入式数据库内建在你的应用程序中.SQLite 被应 ...

  4. springmvc(五)----异常处理

    总结

  5. [leetcode] 406. Queue Reconstruction by Height

    https://leetcode.com/contest/6/problems/queue-reconstruction-by-height/ 分析:每个表示成(a,b)的形式,其实找第一个,就是b为 ...

  6. vbscript multiple line syntax

    Vbscript 如何将输出内容换行? ' VbCrLf represetns Carriage return–linefeed combination, for more information s ...

  7. 九度OJ 1104 整除问题

    题目地址:http://ac.jobdu.com/problem.php?pid=1104 题目描述: 给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除. 输入: 两个整数n(2 ...

  8. leetcode Maximal Rectangle 单调栈

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4052721.html 题目链接:leetcode Maximal Rectangle 单调栈 ...

  9. W3C盒子与IE盒子模型

    盒模型: 内容(content).填充(padding).边界(margin). 边框(border) IE的content部分把 border 和 padding计算了进去 例:一个盒子的 marg ...

  10. jquery ajax php 无刷新上传文件 带 遮罩 进度条 效果的哟

    在很多项目中都会叫用户上传东西这些的,自从接触了jquery 和ajax之后就不管做什么,首先都会想到这个,我这个人呢?是比较重视客户体验的,这次我这边负责的是后台板块,然后就有一块是要求用户上传照片 ...