Search code examples
phpif-statementstrpos

How to get the value of if condition that make the condition TRUE


I'm trying to get the value of if condition that make the condition TRUE, I have the following example:

(code.php)

<?php

$a = 'USERNAME';
$b = 'LASTNAME';
if(substr($a, 0, strlen("<SCRIPT"))=== "<SCRIPT Abdalla $a " ) {
    }

?>

in the above example, I'm trying to get the value <SCRIPT without double quotations. I tried some steps, but it still get the double quotation with the value (<SCRIPT). I plan then to assign the value <SCRIPT to another variable. Next code shows my code to get the result, where it still not working properly:

(test.php)


<?php

$file='';
$handle = fopen('code.php','r+');
if ($handle) {
    while (($buffer=fgets($handle, 4096)) !== false) {
        $file.=$buffer."\n";
    }
    if (!feof($handle)) {
       die('error');
    }
    fclose($handle);
}

//take note of the use of single quotes to wrap this regex, as we do not want PHP parser to eval the $ in the regex string
preg_match_all('/\s*?(\$[\S]+?)\s*?\=(\"?[\S]+?\"?);/',$file,$matches, PREG_SET_ORDER);
$varval=array();
foreach($matches as $match){
    $tmp=$match[2];
    if(substr($tmp,0,1)=='"' && substr($tmp,-1,1)=='"'){    
        //evaluate variables in string. we can evaluate with the current object as the variable should be declared first as of proper PHP syntax
        $tmp=substr($tmp, 1,-1); //remove quotes
        $tmp=replaceFromObject($tmp, $varval);
    }
    $varval[$match[1]]=$tmp; //we do not need to check if exists, because we should replace with the latest value.
}

/* 
The below stores all quoted text and replace them with our own special variable %var0%, %var1%, %var2%.... %varN%. 
This is because there could be cases like if("test == 3" == $a) or even worse if("if(test=3)" == $a), which will make regex/parsing tricky if we do not substitute these quoted text out just like a parser/compiler would.

Note: Something I didn't do. We should really first scan the whole text using preg_match to find if there are any text using this format of variables as well, to prevent collisions! 
If there are, we can set a loop to check and append some character to our own special variable like %varrN%, %varrrN%, %varrrrN%... 
until we find no collisions and it is safe to use. I will leave this simple regex exercise for you to do on your own. Create the regex to find the file for %varN%...
*/
preg_match_all("/\"([\s\S]*?)\"/", $file, $matches, PREG_SET_ORDER);
$stringvars=array();
$key="%var";
$count=0;
foreach($matches as $match){
    if(!in_array($match[1], $stringvars)){
        $stringvars[$key.$count.'%']=$match[1];
        $file=preg_replace("/\"".preg_quote($match[1])."\"/", $key.$count.'%', $file); //take note of the change to preg_quote
        $count++;
    }
}


// now we parse the whole text for if(subject anycomparator value)
preg_match_all("/if\s*?\(([\s\S]*?)([\=|\>|\<][\=]{0,2})\s*?([\S\s]*?)\s*?\)/", $file, $matches,PREG_SET_ORDER);
$conditionals=array();
foreach($matches as $match){
    $conditionals[]=array(
        'subject'=>replaceFromObject(replaceFromObject(trim($match[1]),$stringvars),$varval), 
        //the order does matter, we replace the string first, then the variables, as there might be variables in the double quoted strings which we should evaluate
        'comparator'=>$match[2],
        'value'=>replaceFromObject(replaceFromObject(trim($match[3]),$stringvars),$varval),
    );
}

foreach ($conditionals as $c){
   // echo htmlspecialchars($c['subject']).' ';    
   // echo htmlspecialchars($c['comparator']).' ';        
    echo htmlspecialchars($c['value']); 
    echo "<br/>";
}


/* now this function can be used to replace both the quoted strings AND the variables */
function replaceFromObject($s, $obj){
    foreach($obj as $key=>$value){
        $s=preg_replace('/'.preg_quote($key).'/', $value, $s); //take note of the preg_quote as the string may contain regex keywords
    }
    return $s;
}


?>

I want a way to print the value that make the if condition be TRUE, which is (

  • Note: I know the condition here is not TRUE, so that I want to get the value that make the condition TRUE which is <SCRIPT
  • The purpose is I want to know what is the value that make the condition TRUE, then later will use this value to do test cases on the code based on this value.

Solution

  • Your goal seems oddly specific, but here's a go at addressing your question.

    1. You should not be using file_get_contents to read the actual source code of the php file. You will be getting the generated html equivalent instead. If you are getting the source code, it means you have your allow_url_fopen set to true. This opens your web application up to potential pretty damaging script injection possibilities, unless you know what you are doing and are careful and consistent with your code. You should try using fgets instead. This code will basically do the same as your file_get_contents.
    $file='';
    $handle = fopen('code.php','r+');
    if ($handle) {
        while (($buffer=fgets($handle, 4096)) !== false) {
            $file.=$buffer."\n";
        }
        if (!feof($handle)) {
           die('error');
        }
        fclose($handle);
    }
    
    1. There is a bug in your code. if(strpos($value, 'if') != false) will render position 0 as false, which should be true in your case as if is at position 0. The comparator should be strict if(strpos($value, 'if') !== false).
    2. It is returning the quotation because that is what your code is telling it to. $pos1 = strpos($value, '==') + 2 is pointing to 2 characters after the first =, which is technically your last = in your ===. In fact, with your current code, you would be getting = "<SCRIPT" as the result.

    Now for the Solution

    To solve it just for your case, you can just adjust the position to substr with. After making the amendments mentioned above (especially 2), you will get your condition without the quotes by changing these 2 lines

    $pos1 =  strpos($value, '===') + 5; // 5 includes the 3 equal signs, a space and the quote
    $pos2 =  strrpos($value, ')') - 2; // 2 includes the space and the quote
    

    Although this will get the result you want, it might not work for all use cases, as sometimes you might add a space, sometimes you might not. A more robust solution will be to use regex. In your case, it will be something along this line:

    foreach ($lines as $line) {
        if(preg_match("/if\s*?\([\s\S]*?\=\=\s*?[\"|']([\S\s]*?)[\"|']\s*?\)/", $line, $matches)){
            echo htmlspecialchars($matches[1]);     
        }
    }
    

    This regex string /if\s*?\([\s\S]*?\=\=\s*?[\"|']([\S\s]*?)[\"|']\s*?\)/ does what your code aims to do, but in a more robust manner = finds a string with if possibly a space, a (, some text, a comparator == and whatever is in between the quotes - either ' or ".

    To have it even more robust to pick up other conditions, strict equal, < or > or <=, >=, you can do something like this.

    $conditionals=array();
    foreach ($lines as $line) {
        if(preg_match("/if\s*?\([\s\S]*?([\=|\>|\<][\=]{0,2})\s*?[\"|']([\S\s]*?)[\"|']\s*?\)/", $line, $matches)){
            $conditionals[]=array(
                'comparator'=>$matches[1],
                'value'=>$matches[2]
            );
        }
    }
    
    foreach ($conditionals as $c){
        echo htmlspecialchars($c['comparator']).' ';        
        echo htmlspecialchars($c['value']); 
        echo "<br/>";
    }
    

    This will work for a file of code.php that might look like this:

    //code.php
    <?php
    
    $a = 'Data';
    if(substr($a, 0, strlen("<SCRIPT"))=== "<SCRIPT" ) {
       echo TRUE;
    }
    
    
    if(substr($a, 0, strlen("<SCRIPT"))== "equal" ) {
       echo TRUE;
    }
    
    if(substr($a, 0, strlen("<SCRIPT"))<= "lesser or equal" ) {
       echo TRUE;
    }
    
    if(substr($a, 0, strlen("<SCRIPT"))>= "greater or equal" ) {
       echo TRUE;
    }
    
    if(substr($a, 0, strlen("<SCRIPT"))< "lesser" ) {
       echo TRUE;
    }
    
    if(substr($a, 0, strlen("<SCRIPT"))>"greater" ) {
       echo TRUE;
    }
    

    and will return

    === <SCRIPT
    == equal
    <= lesser or equal
    >= greater or equal
    < lesser
    > greater
    

    Edit to provide even more robust code for non-quoted values... Another edit to capture variable values and dump them back in. Another edit for single quotes variables and space between variables

    Note: The order of capturing strings or variables first matters, as if we are not careful we might be caught in a viscous cycle -> there is string in the variable, there is variable in the string, there is a string in the variable in the string of a variable, etc...

    The idea is we should capture the variable first, evaluate whatever variables within these variables (if they are a double quoted string), and we do not need to worry about that string-var inception problem.

    We then capture the strings -> and then replace the strings THEN replace the variables.

    More Notes What we should really also check for: Since PHP only evaluates variables in double quotes, we should check if the string was enclosed in double and single quotes before making the decision to evaluate. I have done it for the variables. It could be easily translated to the strings as well - by capturing the quotes as well, then testing the quotes if it is single or double (or any at all). I will leave this to you as a regex exercise.

    The other note is that I did this on purpose. :p The current regex for capturing variables works for $a=3;, $a =3; but not $a = 3; or $a = 3 ; or $a= 3 ;, so on. It is easy to add it in, I left it this way so you have a chance to practice your regex skills by adding this simple conditions in. (Edited to add this in) Hope this helps..

    $file='';
    $handle = fopen('code.php','r+');
    if ($handle) {
        while (($buffer=fgets($handle, 4096)) !== false) {
            $file.=$buffer."\n";
        }
        if (!feof($handle)) {
           die('error');
        }
        fclose($handle);
    }
    
    //take note of the use of single quotes to wrap this regex, as we do not want PHP parser to eval the $ in the regex string
    preg_match_all('/\s*?(\$[\S]+?)\s*?\=\s*?(\"?[\S]+?\"?);/',$file,$matches, PREG_SET_ORDER);
    $varval=array();
    foreach($matches as $match){
        $tmp=trim($match[2]);
        if(substr($tmp,0,1)=='"' && substr($tmp,-1,1)=='"'){    
            //evaluate variables in string. we can evaluate with the current object as the variable should be declared first as of proper PHP syntax
            $tmp=substr($tmp, 1,-1); //remove quotes
            $tmp=replaceFromObject($tmp, $varval);
        }else if(substr($tmp,0,1)=='\'' && substr($tmp,-1,1)=='\''){ // remove single quotes
            $tmp=substr($tmp, 1,-1);
            //no substitution of variables in single quotes just as PHP syntax
        }
        $varval[$match[1]]=$tmp; //we do not need to check if exists, because we should replace with the latest value.
    }
    
    
    /* 
    The below stores all quoted text and replace them with our own special variable %var0%, %var1%, %var2%.... %varN%. 
    This is because there could be cases like if("test == 3" == $a) or even worse if("if(test=3)" == $a), which will make regex/parsing tricky if we do not substitute these quoted text out just like a parser/compiler would.
    
    Note: Something I didn't do. We should really first scan the whole text using preg_match to find if there are any text using this format of variables as well, to prevent collisions! 
    If there are, we can set a loop to check and append some character to our own special variable like %varrN%, %varrrN%, %varrrrN%... 
    until we find no collisions and it is safe to use. I will leave this simple regex exercise for you to do on your own. Create the regex to find the file for %varN%...
    */
    preg_match_all("/\"([\s\S]*?)\"/", $file, $matches, PREG_SET_ORDER);
    $stringvars=array();
    $key="%var";
    $count=0;
    foreach($matches as $match){
        if(!in_array($match[1], $stringvars)){
            $stringvars[$key.$count.'%']=$match[1];
            $file=preg_replace("/\"".preg_quote($match[1])."\"/", $key.$count.'%', $file); //take note of the change to preg_quote
            $count++;
        }
    }
    
    
    // now we parse the whole text for if(subject anycomparator value)
    preg_match_all("/if\s*?\(([\s\S]*?)([\=|\>|\<][\=]{0,2})\s*?([\S\s]*?)\s*?\)/", $file, $matches,PREG_SET_ORDER);
    $conditionals=array();
    foreach($matches as $match){
        $conditionals[]=array(
            'subject'=>replaceFromObject(replaceFromObject(trim($match[1]),$stringvars),$varval), 
            //the order does matter, we replace the string first, then the variables, as there might be variables in the double quoted strings which we should evaluate
            'comparator'=>$match[2],
            'value'=>replaceFromObject(replaceFromObject(trim($match[3]),$stringvars),$varval),
        );
    }
    
    foreach ($conditionals as $c){
        echo htmlspecialchars($c['subject']).' ';    
        echo htmlspecialchars($c['comparator']).' ';        
        echo htmlspecialchars($c['value']); 
        echo "<br/>";
    }
    
    
    /* now this function can be used to replace both the quoted strings AND the variables */
    function replaceFromObject($s, $obj){
        foreach($obj as $key=>$value){
            $s=preg_replace('/'.preg_quote($key).'/', $value, $s); //take note of the preg_quote as the string may contain regex keywords
        }
        return $s;
    }