2012-08-07 43 views
9

PHP有什麼方法可以執行靜態代碼分析並檢測對register_globals主動性的依賴嗎?手動檢查文件並查找尚未初始化的變量並從中推斷出這些可能依賴它,這是相對直接的,但我需要爲數百個腳本執行此操作,因此我正在尋找一種自動化解決方案。檢測註冊表全局變量的使用

我最後的手段是設置一個開發環境,關閉指令並嚴格報告錯誤並讓QA長時間播放,然後修復錯誤日誌捕獲的實例,但不能保證找到100 %的情況下,如果存在自動化解決方案,肯定不會很好地利用資源。

+0

這將是困難的,因爲你需要考慮變量和函數像'extract()' – 2012-08-07 14:56:16

+1

我在想「不」。正如@MikeB正確地指出的那樣,諸如'extract()'和變量變量(以及另外''GLOBALS')之類的東西會在作品中拋出一個巨大的扳手來進行任何不會真正執行代碼的代碼分析。是的,您可以嘗試在批處理中運行腳本,並查找有關使用未設置的變量的投訴,但始終有代碼被破壞的可能性(儘管我認爲在這種情況下仍需要修復)。不幸的是,手動方法可能是您唯一的選擇。享受... – DaveRandom 2012-08-07 15:11:40

+0

請注意,自PHP 5.4.0以來'register_globals'已被刪除。 – Florent 2012-08-07 15:16:08

回答

13

一個小腳本我砍死在一起,檢測簡單不確定的變量。你需要PHP-Parser此:

<?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"; 
} 

這只是一個非常基本的實現,我沒有進行廣泛的測試,但它應該爲腳本,不要去野外與$GLOBALS$$varVars工作。它的基本包括解決方案。

+5

只是一個FYI,在Apache環境中if(!preg_match('/ \。php $ /',$ file))繼續;'不是傻瓜證明。 Apache會執行任何與'/ \。php(\。| $)/'匹配的PHP腳本(並且在Windows上會有'i'修飾符)。這是一個鮮爲人知的阿帕奇奇怪事件,它在PHP動力站點的世界中佔據了許多安全漏洞。這看起來像它*可能*值得+1,但我不能說我知道它(特別是'PHPParser')是做什麼給你一個沒有感覺像我upvoting我不明白的東西瞭解足夠。 – DaveRandom 2012-08-07 15:40:14

0

這應該(從PHP手冊中的意見之一)工作:

if (ini_get('register_globals')) { 
    foreach ($GLOBALS as $int_temp_name => $int_temp_value) { 
     if (!in_array($int_temp_name, array (
       'GLOBALS', 
       '_FILES', 
       '_REQUEST', 
       '_COOKIE', 
       '_SERVER', 
       '_ENV', 
       '_SESSION', 
       ini_get('session.name'), 
       'int_temp_name', 
       'int_temp_value' 
      ))) { 
      unset ($GLOBALS[$int_temp_name]); 
     } 
    } 
} 
+0

這將如何靜態檢測使用全局變量? – 2012-08-07 15:32:25

+1

是的,或者你*可以*關閉'register_globals'。但是OP在詢問是否可以通過編程檢測腳本是否期望它被打開,而不是如何模仿它被關閉。另外,它並沒有捕捉到很多在各種情況下可能被打開的變量。那麼'$ argv'呢? '$ argc'呢?那麼'$ HTTP_RAW_POST_DATA'呢? – DaveRandom 2012-08-07 15:33:43

+0

是你2都是正確的,這會檢查它是否在使用而不是它的使用。 – 2012-08-07 15:36:55