PHP程序漏洞产生的原因分析与防范方法说明


Posted in PHP onMarch 06, 2014

滥用include

1.漏洞原因:

Include是编写PHP网站中最常用的函数,并且支持相对路径。有很多PHP脚本直接把某输入变量作为Include的参数,造成任意引用脚本、绝对路径泄露等漏洞。看以下代码:

...
$includepage=$_GET["includepage"];
include($includepage);
...

很明显,我们只需要提交不同的Includepage变量就可以获得想要的页面。如果提交一个不存在的页面,就可以使PHP脚本发生错误而泄露实际绝对路径(这个问题的解决办法在下面的文章有说明)。

2.漏洞解决:

这个漏洞的解决很简单,就是先判断页面是否存在再进行Include。或者更严格地,使用数组对可Include的文件作出规定。看以下代码:

$pagelist=array("test1.php","test2.php","test3.php"); //这里规定可进行include的文件 
if(isset($_GET["includepage"])) //判断是否有$includepage 
{ 
$includepage=$_GET["includepage"]; 
foreach($pagelist as $prepage) 
{ 
if($includepage==$prepage) //检查文件是否在允许列表中 
{ 
include($prepage); 
$checkfind=true; 
break; 
} 
} 
if($checkfind==true){ unset($checkfind); } 
else{ die("无效引用页!"); } 
}

这样就可以很好地解决问题了。

小提示:有此问题的函数还有:require(),require_once(),include_once(),readfile()等,在编写的时候也要注意。

未对输入变量进行过滤

1.漏洞原因:

这 个漏洞早在ASP中出现过,当时造成的注入漏洞不计其数。但由于PHP在当时的影响力较小,所以没有太多的人能够注意这点。对于PHP来说,这个漏洞 的影响性比ASP更大,因为有比较多的PHP脚本使用到文本型数据库。当然也存在SQL语句的注入问题。举个比较经典的例子,首先是数据库的:

$id=$_GET["id"]; 
$query="SELECT * FROM my_table where id='".$id."'"; //很经典的SQL注入漏洞 
$result=mysql_query($query);

这里很明显我们可以用注入来获得数据库的其它内容了。这里就不再详细叙述,和ASP注入一样的,大家可以看看以前的黑防。然后我们看文本数据库的问题:
$text1=$_POST["text1"]; 
$text2=$_POST["text2"]; 
$text3=$_POST["text3"]; 
$fd=fopen("test.php","a"); 
fwrite($fd,"\r\n$text1&line;$text2&line;$text3"); 
fclose($fd);

文本的漏洞可以说是更加严重。倘若我们的提交的变量中插入一段很小的PHP代码,就可以另这个文本数据库test.php变成PHP后门。甚至插入上传代码,让我们可以上传一个完善的PHP后门。接着提升权限,服务器就是你的了。

2.漏洞解决:

这个漏洞的解决方法其实很简单,就是严格对全部提交的变量进行过滤。对一些敏感的字符进行替换。我们可以借助PHP提供的htmlspecialchars()函数来替换HTML的内容。这里给出一段例子:

//构造过滤函数 
function flt_tags($text) 
{ 
$badwords=array("操","fuck"); //词汇过滤列表 
$text=rtrim($text); 
foreach($badwords as $badword) //这里进行词汇的过滤 
{ 
if(stristr($text,$badword)==true){ die("错误:你提交的内容含有敏感字眼,请不要提交敏感内容。"); } 
} 
$text=htmlspecialchars($text); //HTML替换 
//这两行把回车替换为 
$text=str_replace("\r"," ",$text); 
$text=str_replace("\n","",$text); 
$text=str_replace("&line;","│",$text); //文本数据库分隔符"&line;"替换为全角的"│" 
$text=preg_replace("/\s{ 2 }/"," ",$text); //空格替换 中国网管联盟
$text=preg_replace("/\t/"," ",$text); //还是空格替换 
if(get_magic_quotes_gpc()){ $text=stripslashes($text); } //如果magic_quotes开启,则进行\'的替换 
return $text; 
} 
$text1=$_POST["text1"]; 
$text2=$_POST["text2"]; 
$text3=$_POST["text3"]; 
//过滤全部输入 
$text1=flt_tags($text1); 
$text2=flt_tags($text2); 
$text3=flt_tags($text3); 
$fd=fopen("test.php","a"); 
fwrite($fd,"\r\n$text1&line;$text2&line;$text3"); 
fclose($fd);

经过一番替换和过滤后,你就可以安全地把数据写入文本或数据库了。

管理员判断不完全

1.漏洞原因:

我们用PHP写脚本,通常要涉及管理员的权限问题。而一些脚本仅仅对管理员权限作出"是"判断,而往往忽略了"否"判断。在PHP配置文件中 register_globals打开的情况下(4.2.0以后版本默认关闭,但有不少人为了方便而打开它,这是极度危险的行为),就会出现提交变量冒充 管理员的情况。我们看一下的例子代码:

$cookiesign="admincookiesign"; //判断是否Admin的cookie变量 
$adminsign=$_COOKIE["sign"]; //获取用户的cookie变量 
if($adminsign==$cookiesign) 
{ 
$admin=true; 
} 
if($admin){ echo "现在是管理员状态。"; }

看上去好像很安全的样子,呵呵。现在我们假设PHP配置文件中register_globals为打开状态。我们提交这样一个地址“test.php? admin=true”,结果看到了吗?我们虽然没有正确的Cookie,但由于register_globals为打开状态,使得我们提交的admin 变量自动注册为true。而且脚本缺少“否”判断,就使得我们顺利地通过admin=true取得管理员的权限了。这个问题存在于大部分网站和论坛当中。

2.漏洞解决:

解决这个问题,我们只需要在脚本中加入对管理员的“否”判断即可。我们仍然假设PHP配置文件中register_globals为打开状态。看一下的代码:

$cookiesign="admincookiesign"; //判断是否Admin的cookie变量 
$adminsign=$_COOKIE["sign"]; //获取用户的cookie变量 
if($adminsign==$cookiesign) 
{ 
$admin=true; 
} 
else 
{ 
$admin=false; 
} 
if($admin){ echo "现在是管理员状态。"; }

这 样,就算攻击者在没有正确Cookie的情况下提交了admin=true的变量,脚本在以后的判断中也会把$admin设置为False。这样就解 决了部分的问题。但由于$admin是变量,倘若在以后的其他脚本引用中出现了漏洞使得$admin被重新赋值就会引发新的危机。因此,我们应该使用常量 来存放管理员权限的判定。使用Define()语句定义一个admin常量来记录管理员权限,在此以后若配重新赋值就会出错,达到保护的目的。看以下代 码:
$cookiesign="admincookiesign"; //判断是否Admin的cookie变量 
$adminsign=$_COOKIE["sign"]; //获取用户的cookie变量 
if($adminsign==$cookiesign) 
{ 
define(admin,true); 
} 
else 
{ 
define(admin,false); 
} 
if(admin){ echo "现在是管理员状态。"; }

值得注意的是,我们使用了Define语句,所以在调用Admin常量时前面不要习惯性的加变量符号$,而应该使用Admin和!admin。

文本数据库暴露

1.漏洞原因:

前面已经说过,由于文本数据库具有很大的灵活性,不需要任何外部支持。加上PHP对文件的处理能力十分强,因此文本数据库在PHP脚本中的应用甚广。甚至有几个很好的论坛程序就是使用文本数据库的。但有得必有失,文本数据库的安全性也是比其他数据库要低的。

2.漏洞解决:

文 本数据库作为一个普通的文件,它可以被下载,就像MDB一样。所以我们要用保护MDB的办法来保护文本数据库。把文本数据库的后缀名改为.PHP。并 在数据库的第一行加入。这样文本数据库就会作为一个PHP文件,并且在第一行退出执行。也就是返回一个空页面,从而达到保护文本数据库的目的。

错误路径泄露

1.漏洞原因:

PHP遇到错误时,就会给出出错脚本的位置、行数和原因,例如:

Notice: Use of undefined constant test - assumed 'test' in D:\interpub\bigfly\test.php on line 3

有很多人说,这并没有什么大不了。但泄露了实际路径的后果是不堪设想的,对于某些入侵者,这个信息可是非常重要,而事实上现在有很多的服务器都存在这个问题。

有些网管干脆把PHP配置文件中的display_errors设置为Off来解决,但本人认为这个方法过于消极。有些时候,我们的确需要PHP返回错误的信息以便调试。而且在出错时也可能需要给用户一个交待,甚至导航到另一页面。

2.漏洞解决:

PHP从4.1.0开始提供了自定义错误处理句柄的功能函数set_error_handler(),但很少数脚本编写者知道。在众多的PHP论坛中,我只看见很少一部分对此情况进行了处理。set_error_handler的使用方法如下:

string set_error_handler ( callback error_handler [, int error_types])

现在我们就用自定义的错误处理把实际路径过滤掉。

//admin为管理员的身份判定,true为管理员。 
//自定义的错误处理函数一定要有这4个输入变量$errno,$errstr,$errfile,$errline,否则无效。 
function my_error_handler($errno,$errstr,$errfile,$errline) 
{ 
//如果不是管理员就过滤实际路径 
if(!admin) 
{ 
$errfile=str_replace(getcwd(),"",$errfile); 
$errstr=str_replace(getcwd(),"",$errstr); 
} 
switch($errno) 
{ 
case E_ERROR: 
echo "ERROR: [ID $errno] $errstr (Line: $errline of $errfile) 
\n"; 
echo "程序已经停止运行,请联系管理员。"; 
//遇到Error级错误时退出脚本 
exit; 
break; 
case E_WARNING: 
echo "WARNING: [ID $errno] $errstr (Line: $errline of $errfile) 
\n"; 
break; 
default: 
//不显示Notice级的错误 
break; 
} 
} 
//把错误处理设置为my_error_handler函数 
set_error_handler("my_error_handler"); 
…

这样,就可以很好地解决安全和调试方便的矛盾了。而且你还可以花点心思,使错误提示更加美观以配合网站的风格。不过注意两点是:

(1)E_ERROR、 E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、 E_COMPILE_WARNING是不会被这个句柄处理的,也就是会用最原始的方式显示出来。不过出现这些错误都是编译或PHP内核出错,在通常情况下 不会发生。

(2)使用set_error_handler()后,error_reporting ()将会失效。也就是所有的错误(除上述的错误)都会交给自定义的函数处理。
其它有关于set_error_handler()的信息,大家可以参考PHP的官方手册。

POST漏洞

1.漏洞原因:

前面已经说过,依靠register_globals来注册变量是个不好的习惯。在一些留言本和论坛程序中,更要严格检查获得页面的方式和提交的时间间隔。以防止灌水式发帖和外部提交。我们看一下以下某留言本程序的代码:

... 
$text1=flt_tags($text1); 
$text2=flt_tags($text2); 
$text3=flt_tags($text3); 
$fd=fopen("data.php","a"); 
fwrite($fd,"\r\n$text1&line;$text2&line;$text3"); 
fclose($fd);

很 明显的,如果我们提交网址”post.php?text1=testhaha&text2=testhaha&text3= testhaha”。数据就会被正常写入文件中。此程序并没有检测变量的来源和浏览器获得页面的方式。如果我们向这个页面重复多次提交,就会起到洪水的作 用。现在也有一些软件利用这个漏洞来在论坛或留言本上发广告,这是可耻的行为(我朋友的留言本就在1星期内被灌了10多页,无奈)。

2.漏洞解决:

在 进行数据处理和保存前,首先判断浏览器的获得页面方式。使用$_SERVER["REQUEST_METHOD"]变量来获得浏览器的获得页面方式。 检查其是否为”POST”。在脚本中使用session来记录用户是否通过正常途径(即填写提交内容的页面)来提交数据。或使用$_SERVER ["HTTP_REFERER"]来检测,但不推荐这样做。因为部分浏览器没有设置REFERER,有部分防火墙也会屏蔽REFERER。另外,我们也要 对提交内容检查,看数据库中是否有重复内容。以留言本为例,使用Session进行判定:
填写浏览内容的页面中,我们在最前端加上:

$_SESSION["allowgbookpost"]=time(); //登记填写时的时间

在接受留言数据并保存的页面中我们在进行数据处理前我们也用Session进行以下处理:

if(strtoupper($_SERVER["REQUEST_METHOD"])!=”POST”){ die("错误:请勿在外部提交。"); } //检查页面获得方法是否为POST 
if(!isset($_SESSION["allowgbookpost"]) or (time()-$_SESSION["allowgbookpost"] < 10)){ die("错误:请勿在外部提交。"); } //检查留言填写时的时间 
if(isset($_SESSION["gbookposttime"]) and (time()-$_SESSION["gbookposttime"] < 120)){ die("错误:两次提交留言的间隔不得少于 2 分钟。"); } //检查留言间隔 
unset($_SESSION["allowgbookpost"]); //注销allowgbookpost变量以防止一次进入填写页面多次进行提交 
$_SESSION["gbookposttime"]=time(); //登记发送留言的时间,防止灌水或恶意攻击 
...

数据处理及保存
PHP 相关文章推荐
php环境配置 php5 MySQL5 apache2 phpmyadmin安装与配置图文教程
Mar 16 PHP
php 防止单引号,双引号在接受页面转义
Jul 10 PHP
PHP 采集程序 常用函数
Dec 18 PHP
destoon之一键登录设置
Jun 21 PHP
php可应用于面包屑导航的递归寻找家谱树实现方法
Feb 02 PHP
php计算函数执行时间的方法
Mar 20 PHP
PHP+Javascript实现在线拍照功能实例
Jul 18 PHP
Zend Framework框架教程之Zend_Db_Table_Rowset用法实例分析
Mar 21 PHP
统计PHP目录中的文件数方法
Mar 05 PHP
PHP Cli 模式设置进程名称的方法
Jun 12 PHP
Laravel框架中缓存的使用方法分析
Sep 06 PHP
解决laravel groupBy 对查询结果进行分组出现的问题
Oct 09 PHP
PHP常用函数和常见疑难问题解答
Mar 05 #PHP
php获得url参数中具有&amp;的值的方法
Mar 05 #PHP
php网页标题中文乱码的有效解决方法
Mar 05 #PHP
php绘图中显示不出图片的原因及解决
Mar 05 #PHP
ThinkPHP验证码使用简明教程
Mar 05 #PHP
ThinkPHP分页类使用详解
Mar 05 #PHP
php统计文章排行示例
Mar 04 #PHP
You might like
新浪微博API开发简介之用户授权(PHP基础篇)
2011/09/25 PHP
写一段简单的PHP建立文件夹代码
2015/01/06 PHP
PHP使用http_build_query()构造URL字符串的方法
2016/04/02 PHP
laravel5创建service provider和facade的方法详解
2016/07/26 PHP
javascript 函数调用规则
2009/08/26 Javascript
jquery 学习之二 属性(html()与html(val))
2010/11/25 Javascript
setTimeout内不支持jquery的选择器的解决方案
2015/04/28 Javascript
JS拖动鼠标画出方框实现鼠标选区的方法
2015/08/05 Javascript
js多功能分页组件layPage使用方法详解
2016/05/19 Javascript
jQuery实现查找链接文字替换属性的方法
2016/06/27 Javascript
封装的dialog插件 基于bootstrap模态对话框的简单扩展
2016/08/10 Javascript
Web 开发中Ajax的Session 超时处理方法
2017/01/19 Javascript
如何写好你的JavaScript【推荐】
2017/03/02 Javascript
Angular 1.x个人使用的经验小结
2017/07/19 Javascript
jquery.rotate.js实现可选抽奖次数和中奖内容的转盘抽奖代码
2017/08/23 jQuery
Vue常用的几个指令附完整案例
2018/11/06 Javascript
Jquery的Ajax技术使用方法
2019/01/21 jQuery
Webpack按需加载打包chunk命名的方法
2019/09/22 Javascript
Layui数据表格之单元格编辑方式
2019/10/26 Javascript
JS实现音乐钢琴特效
2020/01/06 Javascript
教你安装python Django(图文)
2013/11/04 Python
python处理csv数据的方法
2015/03/11 Python
利用Python的Django框架中的ORM建立查询API
2015/04/20 Python
PyQt5每天必学之事件与信号
2018/04/20 Python
Python autoescape标签用法解析
2020/01/17 Python
Python3+selenium配置常见报错解决方案
2020/08/28 Python
如何基于Python和Flask编写Prometheus监控
2020/11/25 Python
就业表自我评价分享
2014/02/06 职场文书
中西医专业毕业生职业规划书
2014/02/24 职场文书
绿化工程实施方案
2014/03/17 职场文书
物业管理工作方案
2014/05/10 职场文书
尊师重教演讲稿
2014/09/04 职场文书
销售代理协议书
2014/09/30 职场文书
2014年保管员工作总结
2014/11/18 职场文书
加薪申请书应该这样写!
2019/07/04 职场文书
详解Python中下划线的5种含义
2021/07/15 Python