Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting use of register globals

Is there any way in PHP to do static code analysis and detect reliance on the register_globals initiative? It's relatively straightforward to manually examine a file and look for variables which have not been initialized and infer from that that these may be relying on it, but I need to do this for many hundreds of scripts, so I'm looking for an automated solution.

My last resort is setting up a dev environment with the directive turned off and strict error reporting and letting QA play around for a long while, then fix the instances that the error log catches, but this is not guaranteed to find 100% of the cases, and certainly not a good use of resources if an automated solution exists.

like image 551
FtDRbwLXw6 Avatar asked Aug 07 '12 14:08

FtDRbwLXw6


1 Answers

A small script I just hacked together to detect simple undefined variables. You'll need PHP-Parser for this:

<?php

error_reporting(E_ALL);

$dir = './foo';

require_once './lib/bootstrap.php';

class Scope {
    protected $stack;
    protected $pos;

    public function __construct() {
        $this->stack = array();
        $this->pos = -1;
    }

    public function addVar($name) {
        $this->stack[$this->pos][$name] = true;
    }

    public function hasVar($name) {
        return isset($this->stack[$this->pos][$name]);
    }

    public function pushScope() {
        $this->stack[++$this->pos] = array();
    }

    public function popScope() {
        --$this->pos;
    }
}

class UndefinedVariableVisitor extends PHPParser_NodeVisitorAbstract {
    protected $scope;
    protected $parser;
    protected $traverser;

    public function __construct(Scope $scope, PHPParser_Parser $parser, PHPParser_NodeTraverser $traverser) {
        $this->scope = $scope;
        $this->parser = $parser;
        $this->traverser = $traverser;
    }

    public function enterNode(PHPParser_Node $node) {
        if (($node instanceof PHPParser_Node_Expr_Assign || $node instanceof PHPParser_Node_Expr_AssignRef)
            && $node->var instanceof PHPParser_Node_Expr_Variable
            && is_string($node->var->name)
        ) {
            $this->scope->addVar($node->var->name);
        } elseif ($node instanceof PHPParser_Node_Stmt_Global || $node instanceof PHPParser_Node_Stmt_Static) {
            foreach ($node->vars as $var) {
                if (is_string($var->name)) {
                    $this->scope->addVar($var->name);
                }
            }
        } elseif ($node instanceof PHPParser_Node_Expr_Variable && is_string($node->name)) {
            if (!$this->scope->hasVar($node->name)) {
                echo 'Undefined variable $' . $node->name . ' on line ' . $node->getLine() . "\n";
            }
        } elseif ($node instanceof PHPParser_Node_Stmt_Function || $node instanceof PHPParser_Node_Stmt_ClassMethod) {
            $this->scope->pushScope();

            // params are always available
            foreach ($node->params as $param) {
                $this->scope->addVar($param->name);
            }

            // methods always have $this
            if ($node instanceof PHPParser_Node_Stmt_ClassMethod) {
                $this->scope->addVar('this');
            }
        } elseif ($node instanceof PHPParser_Node_Expr_Include && $node->expr instanceof PHPParser_Node_Scalar_String) {
            $file = $node->expr->value;
            $code = file_get_contents($file);
            $stmts = $this->parser->parse($code);

            // for includes within the file
            $cwd = getcwd();
            chdir(dirname($file));

            $this->traverser->traverse($stmts);

            chdir($cwd);
        }
    }

    public function leaveNode(PHPParser_Node $node) {
        if ($node instanceof PHPParser_Node_Stmt_Function || $node instanceof PHPParser_Node_Stmt_ClassMethod) {
            $this->scope->popScope();
        }
    }
}

$parser = new PHPParser_Parser(new PHPParser_Lexer());

$scope = new Scope;

$traverser = new PHPParser_NodeTraverser;
$traverser->addVisitor(new UndefinedVariableVisitor($scope, $parser, $traverser));

foreach (new RecursiveIteratorIterator(
             new RecursiveDirectoryIterator($dir),
             RecursiveIteratorIterator::LEAVES_ONLY)
         as $file
) {
    if (!preg_match('/\.php$/', $file)) continue;

    echo 'Checking ' . $file . ':', "\n";

    $code = file_get_contents($file);
    $stmts = $parser->parse($code);

    // for includes within the file
    $cwd = getcwd();
    chdir(dirname($file));

    $scope->pushScope();
    $traverser->traverse($stmts);
    $scope->popScope();

    chdir($cwd);

    echo "\n";
}

It's just a very basic implementation and I did not test it extensively, but it should work for scripts that don't go wild with $GLOBALS and $$varVars. It does basic include resolution.

like image 118
NikiC Avatar answered Oct 06 '22 00:10

NikiC



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!