首页 > 代码库 > [label][PHP-Security]PHP Security Program
[label][PHP-Security]PHP Security Program
本文是通过阅读http://www.nowamagic.net/中的PHP安全变成专题,同时结合个人的一点点开发经验组合而成的。
如果你需要看原文,可以直接访问http://www.nowamagic.net/进去看原文,写的很好,第一次你看不懂的话,建议你看第二次,文章的质量都很高。
PHP通过超级全局数组如$_GET, $_POST, 及$_COOKIE清楚地表示了用户数据的来源。一个严格的命名体系能保证你在程序代码的任何部分知道所有数据的来源,这也是我一直所示范和强调的。
1. register_globals,公用变量会自动建立 , 当register_globals开启时,任何使用未初始化变量的行为几乎就意味着安全漏洞。这个是php.ini配置文件中的一个设置,该设置在PHP5.3.0版本就已经不建议开启使用,并且在PHP5.4.0版本中已经被移除掉;
在php.ini中将register_globals设置为on之后,那么就可以往你的PHP脚本注入(inject)各种各样的变量,例如:来自HTML表单form的各种变量-get传参和post传参。
综上所述(this couple with the fact that......),当PHP不需要变量的初始化意味着会更加容易写安全性低下的代码。
这是一个艰难的选择,但是PHP社区决定将register_globals默认设置为不开启(disable)。打开的时候,大家使用变量时无法确定它们来自于哪里,只能猜测假设。
脚本内部自己定义的变量将会和用户发送的请求数据混乱在一起,但是我们可以通过禁用(disable)register_globals来改变这个问题。
让我们通过一个滥用register_globals例子来做展示:
<?php//Example #1 Example misuse with register_globals = on//define $authorized = true only if user is authenticatedif(authenticated_user()){ $authorized = true;}
// Because we didn‘t first initialize $authorized as false, this might be
// defined through register_globals, like for GET auth.php?authorized=1
// So, anyone can be seen as authenticated!
if($authorized){
include "hightly/sensitive/data.php";
}?>
最好的代码就是,在使用一个变量之前,我们必须要先声明,在上面的代码中,我们可以应该首先将$authorized = false ,这样子无论register_globals on or off都已经会影响到我们的代码。
因为即使通过GET auth.php?authorized=1也影响不到我们的默认设置$authorized = false 。
<?php//Example #2 Example use of sessions with register_globals on or off//We wouldn‘t knoe where $username came from but to know $_SESSION // is for session data , so We can use $_SESSIONif(isset($_SESSION[‘username‘])){ echo ‘hello <b>{$_SESSION[‘username‘]}</b>‘; }else{ echo ‘hello Guest‘; echo ‘Would you like to login?‘; }?>
2.不要暴露数据库访问权限
假设db.inc为数据库的用户名和密码配置文件,解决方案有:
1)使用Apache的rewrite module,拒绝对.inc资源的请求。
<Files ~ "\.inc$">
Order allow,deny
Deny from all
</Files>
2)可以将db.inc改名为db.inc.php,这样子即使访问到该脚本,也不会产生输出,从而不会被看到。
3)直接将该配置文件保存在网站根目录以为的包含目录中,注意应该要让服务器具有对该文件的读取权限,这样子就不会有可以访问到这个文件的URL。
注意:所有位于网站根目录下的资源都有相应的URL,如果Apache中没有定义对.inc等等后缀的文件的处理方式类型,那么在对这一类文件进行访问是,会以普通文件的类型进行返回(默认类型),这样子文件内容就会暴露。
3.错误显示配置
<?php//设置报错级别,关闭错误显示,错误日志记录开启,错误日志记录保存路径 ini_set(‘error_reporting‘ , E_ALL | E_STRICT); ini_set(‘display_errors‘ , ‘off‘); ini_set(‘log_errors‘ , ‘on‘); // Start error logging ini_set(‘error_log‘ , ‘/usr/local/apache/logs/error_log‘); //Saving path//通过set_error_handler()函数来设置自己的错误处理函数 , mixed set_error_handler(callable $error_handler [,int $error_types = E_ALL | E_STRICT]) set_error_handler(‘my_error_handler‘); function my_error_handler(......){......}?>
4.过滤用户输入: 识别输入,过滤输入,区分已过滤及被污染数据
输入是指所有源自外部的数据。例如,所有发自客户端的是输入,但客户端并不是唯一的外部数据源,其他如数据库和RSS推送等也是外部数据源。
PHP中,就是使用$_GET和$_POST这两个超级公用数组来存放用户输入数据。
$_SEVER数组中的很多元素是用客户端所操纵的,难以确认哪些元素组成了输入,最好的方法是把整个数组看成输入。最好也把$_SESSION和数据库也看成为输入来处理。
<?php//防止跨目录//Example #1$file_name = $_POST[‘name‘];str_replace( ‘..‘ , ‘.‘ , $file_name);//这种做法是造成了其他漏洞的,当输入有3个以上点的时候,那么则一样会发生目录跳转$file_name_2 = ‘.../.../etc/passwd‘;echo str_replace( ‘..‘ , ‘.‘ ,$file_name_2); //输出结果为: ../../etc/passwd//要想杜绝出现上面这种跨目录访问的情况,那么应该使用循环一直到没有‘..‘$file_name_3 = $_POST[‘name‘];while(strpos($file_name_3 , ‘..‘) !== false){//只要还存在跳转目录,那么就一直代替 $file_name_3 = str_replace(‘..‘ , ‘.‘ , $file_name_3); }?>
<?php//好心办坏事的问题,那就是任何试图纠正非法数据的举动都可能斗志潜在错误并允许非法数据通过//试图纠正非法数据的行为是错误的,只做一个更安全的检查才是更加安全的选择//例子:客户希望用户名前后有空格则不能登录,结果修改时对用户登录程序进行了更改//用trim()函数把输入的用户名前后的空格去掉了(这就是纠正非法数据的行为),同时//在注册时还是允许前后有空格,这是一个很明显的问题//register if(strpos($_POST[‘register_name‘] , ‘ ‘)){ //strpos(),如果有查找的字符,那么就返回该字符的位置,没有则返回false echo ‘username can not have space!‘; }else{ echo ‘register success.‘; }//loginif(strpost($_POST[‘login_name‘] , ‘ ‘)){ echo ‘Invalid username.‘;}else{ echo ‘login success.‘;}?>
1. trim()Note: Possible gotcha: removing middle characters
Because trim() trims characters from the beginning and end of a string, it may be confusing when characters are (or are not) removed from the middle. trim(‘abc‘, ‘bad‘) removes both ‘a‘ and ‘b‘ because it trims ‘a‘ thus moving ‘b‘ to the beginning to also be trimmed. So, this is why it "works" whereas trim(‘abc‘, ‘b‘) seemingly does not.
http://hk2.php.net/manual/en/function.trim.php
2. ctype_alnum()
bool ctype_alnum ( string
$text
)Checks if all of the characters in the provided string,
text
, are alphanumeric.http://hk2.php.net/manual/en/function.ctype-alnum.php
除了把过滤作为一个检查过程之外,还可以使用白名单方法——假定检查的数据是非法的,除非能证明是合法的。
最后一步是使用一个命名约定或其他方法来区分已过滤的数据和被污染的数据。
一个简单的方法就是,把所有已经过滤了的数据存放入一个叫$clean的变量中,同时使用两个步骤来防止被污染数据的注入:
1)经常初始化$clean为一个空数组;
2)加入检查及阻止来自外部数据源的变量名为clean。
举个例子:
<form action="process.php" method="post">Please select a color: <select name="color"> <option value="http://www.mamicode.com/red">red</option> <option value="http://www.mamicode.com/green">green</option> <option value="http://www.mamicode.com/blue">nlue</option> </select><input type="submit" name="submit" /></form> <?php//过滤数据// 初始化为一个空数组,防止包含被污染的数据,一旦证明$_POST[‘color‘]是red,green,blue中的一个是,就保存到$clean[‘color‘]变量中。$clean = array();switch($_POST[‘color‘]){ case ‘red‘: case ‘green‘: case ‘blue‘: $clean[‘color‘] = $_POST[‘color‘]; break;}?>
上面的这个例子对于过来一组已知的合法值的数据很有效,但是对于过滤一组由某些已知的合法字符组成的数据,那就没有什么帮助了。
例如:需要一个只能是由字母及数字组成的用户名
<?php$clean = array();if(ctype_alnum($_POST[‘username‘])){ $clean[‘username‘] = $_POST[‘username‘];}?>
5.转义,针对两面:一方面是针对客户端的输出一定要转义;一方面是针对参与数据库操作的数据一定要转义
5.1对客户端的输出进行转义
<?php $html = array(); //初始化为一个空数组,防止包含污染数据 $html[‘username‘] = htmlentities($clean[‘username‘] , ENT_QUOTES , ‘UTF-8‘);// htmlspecialchars()//引号转义的方式(第二参数),应该指定为ENT_QUOTES,转义单引号和双引号
//字符集(第三参数),字符集参数必须要和页面所使用的字符集想匹配echo $html[‘username‘];?>
5.2对参与数据库操作的数据进行转义
<?php$mysql = array();$mysql[‘username‘] = mysql_real_escape_string($clean[‘username‘]);$sql = "SELECT * FROM profile WHERE username = ‘{$mysql[‘username‘]}‘";$result = mysql_query($sql);?>
6.用户传送数据的方式:
6.1通过URL(如GET传送数据方式)
如果你在GET方式提交的表单中的action中试图使用请求串,它会被表单中的数据取代。
如果你指定了一个非常的请求方式,或者请求方式没有写,浏览器默认以GET方式提交数据。
6.2通过一个请求的内容(如POST数据方式)
6.3通过HTTP头部信息(如cookie)
7.URL语义攻击
8.
[label][PHP-Security]PHP Security Program