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 相关文章推荐
我的论坛源代码(六)
Oct 09 PHP
php之对抗Web扫描器的脚本技巧
Oct 01 PHP
header中Content-Disposition的作用与使用方法
Jun 13 PHP
php防止sql注入示例分析和几种常见攻击正则表达式
Jan 12 PHP
php解析url的三个示例
Jan 20 PHP
php使用百度翻译api示例分享
Jan 31 PHP
基于PHP的简单采集数据入库程序【续篇】
Jul 30 PHP
使用PHP生成图片的缩略图的方法
Aug 18 PHP
PHP页面跳转操作实例分析(header方法)
Sep 28 PHP
PHP 爬取网页的主要方法
Jul 13 PHP
PHP获取MySQL执行sql语句的查询时间方法
Aug 21 PHP
PHP Trait代码复用类与多继承实现方法详解
Jun 17 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
php ftp文件上传函数(基础版)
2010/06/03 PHP
PHP中实现汉字转区位码应用源码实例解析
2010/06/14 PHP
thinkphp区间查询、统计查询与SQL直接查询实例分析
2014/11/24 PHP
学习php设计模式 php实现合成模式(composite)
2015/12/08 PHP
浅谈PHPANALYSIS提取关键字
2019/03/08 PHP
基于laravel where的高级使用方法
2019/10/10 PHP
JQuery Study Notes 学习笔记(一)
2010/08/04 Javascript
JavaScript高级程序设计 阅读笔记(二十一) JavaScript中的XML
2012/09/14 Javascript
js获取select标签的值且兼容IE与firefox
2013/12/30 Javascript
node.js中的url.format方法使用说明
2014/12/10 Javascript
js实现的下拉框二级联动效果
2016/04/30 Javascript
浅谈JSON.stringify()和JOSN.parse()方法的不同
2016/08/29 Javascript
vue如何从接口请求数据
2017/06/22 Javascript
vue仿淘宝订单状态的tab切换效果
2020/06/23 Javascript
React通过父组件传递类名给子组件的实现方法
2017/11/13 Javascript
解决使用Vue.js显示数据的时,页面闪现原始代码的问题
2018/02/11 Javascript
nodejs实现超简单生成二维码的方法
2018/03/17 NodeJs
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
用js编写留言板
2020/03/17 Javascript
python利用Guetzli批量压缩图片
2017/03/23 Python
浅谈django model的get和filter方法的区别(必看篇)
2017/05/23 Python
python基础while循环及if判断的实例讲解
2017/08/25 Python
Python3.4实现远程控制电脑开关机
2018/02/22 Python
Python使用Tkinter实现滚动抽奖器效果
2020/01/06 Python
Python3以GitHub为例来实现模拟登录和爬取的实例讲解
2020/07/30 Python
html5指南-5.使用web storage存储键值对的数据
2013/01/07 HTML / CSS
基于 HTML5 WebGL 实现的医疗物流系统
2019/10/08 HTML / CSS
Html5写一个简单的俄罗斯方块小游戏
2019/12/03 HTML / CSS
Booking.com英国官网:全球酒店在线预订网站
2018/04/21 全球购物
致跳远、跳高运动员广播稿
2014/01/09 职场文书
养殖项目策划书范文
2014/01/13 职场文书
爽歪歪广告词
2014/03/20 职场文书
药学职务聘任书
2014/03/29 职场文书
大学国际贸易专业自荐信
2014/06/05 职场文书
Mac环境Nginx配置和访问本地静态资源的实现
2021/03/31 Servers
十大最强电系宝可梦,阿尔宙斯电系之一,第七被称为雷神
2022/03/18 日漫