Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/classes/general/vuln_scanner.php |
<?php IncludeModuleLangFile(__FILE__); if (!defined("T_BAD_CHARACTER")) define("T_BAD_CHARACTER", 401); class CVariableDeclare { public $line = 0; public $start = 0; public $end = 0; public $comment = ''; public $tokens = array(); public $dependencies = array(); public $tainted_vars = array(); public $id = 0; function __construct($id, $line, $start, $end, $tokens, $comment, $dependencies, $tainted_vars) { $this->line = $line; $this->start = $start; $this->end = $end; $this->comment = $comment; $this->tokens = $tokens; $this->dependencies = $dependencies; $this->tainted_vars = $tainted_vars; $this->id = $id; } } class CVariable { public $declares = array(); public $have_user_input = false; public $secure = false; public $name = ''; public $requestInitialization = true; function __construct($name) { $this->name = $name; $this->have_user_input = false; $this->declares = array(); $this->secure = false; $this->requestInitialization = true; } public function newDeclare($id, $line, $start, $end, $tokens, $comment, $dependencies, $tainted_vars) { $this->declares[] = new CVariableDeclare($id, $line, $start, $end, $tokens, $comment, $dependencies, $tainted_vars); } } class CVuln { public $comment = ''; public $tokens = array(); public $dependencies = array(); public $tainted_vars = array(); public $name = ''; public $line = 0; public $filename = ''; public $traverse = ''; public $additional_text = ''; function __construct($filename, $line, $name, $tokens, $dependencies, $tainted_vars, $comment) { $this->tokens = $tokens; $this->filename = $filename; $this->line = $line; $this->name = $name; $this->dependencies = $dependencies; $this->tainted_vars = $tainted_vars; $this->comment = $comment; $this->traverse = ''; $this->additional_text = ''; } } class CVulnScanner { public $vuln_count = 0; public $arResult = array(); private $tokens = array(); private $variables = array(); private $template = ''; private $file_history = array(); private $current_file = ''; private $scan_functions = array(); private $tokens_type = array(); private $vuln_func = array(); private $sec_func = array(); private $v_userinput = array(); private $mp_mode = false; private $color = array(); private $securing_list = array(); private $tainted_vars = array(); private $comment = ''; private $braces = 0; private $dependency = array(); private $last_dependency = array(); private $dependencies = array(); private $dependency_line = 0; private $scanning_file = ''; private $init_functions = array(); private $search_xss = true; private $global_xss_ignore = false; function __construct($file_name, $arParams, $template = '.default', $component_template = '') { $this->scanning_file = $file_name; $this->source_functions = array(); $this->tokens_type = $arParams['TOKENS_TYPES']; $this->vuln_func = $arParams['VULN_FUNCTIONS']; $this->sec_func = $arParams['SECURING_FUNCTIONS']; $this->v_userinput = $arParams['USER_INPUTS']; $this->init_functions = $arParams['INIT_FUNCTIONS']; $this->securing_list = array(); $this->variables = array(); $this->mp_mode = $arParams['MP_mode']; $this->arParams = $arParams; $this->arResult = array(); $this->tainted_vars = array(); $this->template = $template; $this->comment = ''; $this->dependency = array(); $this->last_dependency = array(); $this->dependencies = array(); $this->dependency_line = 'syntax_error'; $this->braces = 0; $this->scan_functions = array_merge( $this->vuln_func['XSS'], $this->vuln_func['CODE'], $this->vuln_func['FILE_INCLUDE'], $this->vuln_func['EXEC'], $this->vuln_func['DATABASE'], $this->vuln_func['OTHER'] ); $this->color = $arParams['COLORS']; $this->file_history[] = $file_name; $this->current_file = end($this->file_history); $this->tokens = $this->tokenize(file_get_contents($file_name), $component_template); $this->vuln_count = 0; $this->search_xss = true; $this->global_xss_ignore = false; } private function getTokensInfo($tokens, $var_declare = true, $function = '') { $arResult = array(); $this->securing_list = array(); $braces = 0; $c_params = 1; $skip = false; $unsecure = false; $secure = false; $cur_brace = -1; for ($i = 0, $count = count($tokens); $i < $count; $i++) { if(is_array($tokens[$i])) { $token = $tokens[$i][0]; $token_value = $tokens[$i][1]; if($token === T_DOUBLE_COLON || $token === T_OBJECT_OPERATOR) return false; elseif($token === T_VARIABLE) { if($var_declare || $this->scan_functions[$function][0] === 0 || in_array($c_params, $this->scan_functions[$function][0])) { if( ( isset($tokens[$i - 1]) && is_array($tokens[$i - 1]) && in_array($tokens[$i - 1][0], $this->tokens_type['CASTS']) ) || (isset($tokens[$i + 1]) && in_array($tokens[$i + 1], $this->tokens_type['ARITHMETIC_STR'])) || (isset($tokens[$i - 1]) && in_array($tokens[$i - 1], $this->tokens_type['ARITHMETIC_STR'])) || ( isset($tokens[$i + 1]) && is_array($tokens[$i + 1]) && (in_array($tokens[$i + 1][0], $this->tokens_type['ARITHMETIC']) || in_array($tokens[$i + 1][0], $this->tokens_type['OPERATOR']) || in_array($tokens[$i + 1][0], $this->tokens_type['LOGICAL']) ) ) || ( isset($tokens[$i - 1]) && is_array($tokens[$i - 1]) && ( in_array($tokens[$i - 1][0], $this->tokens_type['ARITHMETIC']) || in_array($tokens[$i - 1][0], $this->tokens_type['OPERATOR']) || in_array($tokens[$i - 1][0], $this->tokens_type['LOGICAL']) ) ) ) { $skip = true; } else { if(!in_array($token_value, array_keys($arResult))) { /*if($var_declare) {*/ if(in_array($token_value, $this->v_userinput) && ($var_declare || !$secure || $unsecure)) { $arResult[$token_value]['have_user_input'] = true; $arResult[$token_value]['secure'] = $secure; $arResult[$token_value]['var_name'] = $token_value; $arResult[$token_value]['requestInitialization'] = true; } elseif(isset($this->variables[$val = $this->getVarName($tokens[$i])])) { if($this->variables[$val]->have_user_input && ($var_declare || !$this->variables[$val]->secure || $unsecure)) { $arResult[$token_value]['have_user_input'] = true; $arResult[$token_value]['secure'] = ($this->variables[$val]->secure && !$unsecure) ? true : $secure; $arResult[$token_value]['var_name'] = $val; $arResult[$token_value]['requestInitialization'] = $this->variables[$val]->requestInitialization; } //break; } elseif((isset($this->variables[$token_value]) && $this->variables[$token_value]->have_user_input) && ($var_declare || !$this->variables[$token_value]->secure || $unsecure)) { $arResult[$token_value]['have_user_input'] = true; $arResult[$token_value]['secure'] = ($this->variables[$token_value]->secure && !$unsecure) ? true : $secure; $arResult[$token_value]['var_name'] = $token_value; $arResult[$token_value]['requestInitialization'] = $this->variables[$token_value]->requestInitialization; //break; } /*} else { if(!$secure && (in_array($token_value, $this->v_userinput) || (isset($this->variables[$token_value]) && $this->variables[$token_value]->have_user_input && (!$this->variables[$token_value]->secure || $unsecure)))) { $arResult[$token_value]['have_user_input'] = true; //$arResult[]['secure'] = $secure; $arResult[$token_value]['var_name']=$token_value; } }*/ } } } } elseif($cur_brace === -1 && $token === T_STRING && in_array($token_value, $this->sec_func['INSTRING']) ) { $unsecure = true; $secure = false; $cur_brace = $braces; } elseif(!$unsecure && ($token === T_STRING && ( in_array($token_value, $this->sec_func['SECURES_ALL']) || in_array($token_value, $this->sec_func['STRING']) || ( isset($this->scan_functions[$function][1]) && is_array($this->scan_functions[$function][1]) && in_array($token_value, $this->scan_functions[$function][1]) ) )) || (in_array($token, $this->tokens_type['CASTS']) && $tokens[$i + 1] === '(') ) { $this->securing_list[] = $token_value; $secure = true; $cur_brace = $braces; $braces++; $i++; } elseif($token === T_ISSET || ($token === T_STRING && substr($token_value, 0, 3) === 'is_')) { $skip = true; } } elseif($braces === 1 && $tokens[$i] === ', ') { $c_params++; $skip = false; } elseif($tokens[$i] === '(') { $braces++; } elseif($tokens[$i] === ')') { $braces--; if($cur_brace === $braces) { $cur_brace = -1; $unsecure = false; $secure = false; } } if($skip) { while (($i + 1 < $count) && !($tokens[$i + 1] === ', ')) { if($tokens[$i + 1] === ')') $braces--; $i++; } $skip = false; } } if(!empty($arResult)) { $secure = true; foreach ($arResult as $res) { if($res['secure'] === false) { $secure = false; break; } } $requestInitialization = false; foreach ($arResult as $res) { if($res['requestInitialization'] === true) { $requestInitialization = true; break; } } return array($secure, $arResult, $requestInitialization); } return false; } private function dependencyHave($tokens, $type) { if (!is_array($tokens)) { return false; } for ($i = 1, $tokens_count = count($tokens) - 1; $i < $tokens_count; $i++) { if(is_array($tokens[$i])) { switch ($type) { case 'XSS': if($tokens[$i][0] === T_STRING && $tokens[$i - 1] !== '!' && $tokens[$i][1] === 'check_bitrix_sessid') return true; break; case '!XSS': if($tokens[$i][0] === T_STRING && $tokens[$i - 1] === '!' && $tokens[$i][1] === 'check_bitrix_sessid') return true; break; } } } return false; } public function process() { $this->objects = array(); for ($i = 0, $tokens_count = count($this->tokens); $i < $tokens_count; $i++) { if((time() - $this->arParams['time_start']) >= $this->arParams['time_out']) return false; if(is_array($this->tokens[$i])) { $token = $this->tokens[$i][0]; $token_value = $this->tokens[$i][1]; $cur_line = $this->tokens[$i][2]; if($token === T_VARIABLE) { if($this->tokens[$i + 1] === '=' || (is_array($this->tokens[$i + 1]) && in_array($this->tokens[$i + 1][0], $this->tokens_type['ASSIGNMENT']))) { if ( is_array($this->tokens[$i + 2]) && ($this->tokens[$i + 2][0] === T_NEW) && ($className = $this->getClassName($i + 3)) ) { $this->objects[$token_value] = ltrim(strtoupper($className), '\\'); $i += $this->getBraceEnd($this->tokens, $i); } if(!(isset($this->tokens[$i + 2]) && is_array($this->tokens[$i + 2]) && $this->tokens[$i + 2][0] === T_ARRAY)) $this->addVariable($this->tokens[$i], $i, $cur_line, $i, $this->getBraceEnd($this->tokens, $i), ''); else $i += $this->getBraceEnd($this->tokens, $i); } } elseif($token === T_STRING && in_array($token_value, $this->init_functions)) { if($this->tokens[$i + 1] === '(' && is_array($this->tokens[$i + 2]) && $this->tokens[$i + 3] === ')') if(isset($this->variables[$val = $this->getVarName($this->tokens[$i + 2])])) unset($this->variables[$val]); } elseif( in_array($token, $this->tokens_type['FUNCTIONS']) || ($this->search_xss && !$this->global_xss_ignore && in_array($token, $this->tokens_type['XSS'])) || in_array($token, $this->tokens_type['INCLUDES']) ) { if(in_array($token, $this->tokens_type['INCLUDES'])) { $component_template = ''; //$additional_tokens=array(); if($token == T_INCLUDE_COMPONENT) { if(!$this->mp_mode) //not done yet { $component_name = ''; if($this->tokens[$i + 1] === '(' && $this->tokens[$i + 2][0] === T_CONSTANT_ENCAPSED_STRING) { //if(!empty(substr($this->tokens[$i+2][1], 1, -1))) $component_name = substr($this->tokens[$i + 2][1], 1, -1); } $component_name = strtolower($component_name); if(empty($component_name)) // || strpos($component_name, 'bitrix:') === 0) continue; $component_name = "/".str_replace(":", "/", $component_name); if(is_file($this->arParams['doc_root_path'].'/bitrix/components'.$component_name.'/component.php')) $inc_file = $this->arParams['doc_root_path'].'/bitrix/components'.$component_name.'/component.php'; else continue; if( $this->current_file === $inc_file || in_array($inc_file, $this->file_history) ) { // Recursion detected continue; } if($this->tokens[$i + 1] === '(' && $this->tokens[$i + 4][0] === T_CONSTANT_ENCAPSED_STRING) $component_template = substr($this->tokens[$i + 4][1], 1, -1); //$additional_tokens=array(array(T_VARIABLE, '$arParams', 0), '=', array(T_CONSTANT_ENCAPSED_STRING, ' ', 0), ';', array(T_VARIABLE, '$arResult', 0), '=', array(T_CONSTANT_ENCAPSED_STRING, ' ', 0), ';'); $scanner = new CVulnScanner($inc_file, $this->arParams, $this->template, $component_template); $result = $scanner->process(); if($result !== false) { if(!empty($scanner->arResult)) $this->arResult = array_merge($this->arResult, $scanner->arResult); $this->vuln_count += $scanner->vuln_count; } else { return false; } unset($scanner); continue; } else { continue; } } //including result_modifier.php elseif($token == T_INCLUDE_RESULT_MODIFIER) { if(!$this->mp_mode) { $skip = 1; $component_path = array_pop(explode('bitrix/components', dirname($this->current_file))); $component_template = (!empty($this->tokens[$i][3]) ? $this->tokens[$i][3] : '.default'); if(is_file($this->arParams['doc_root_path'].'/bitrix/templates/'.$this->template.'/components'.$component_path.'/'.$component_template.'/result_modifier.php')) $inc_file = $this->arParams['doc_root_path'].'/bitrix/templates/'.$this->template.'/components'.$component_path.'/'.$component_template.'/result_modifier.php'; elseif(is_file($this->arParams['doc_root_path'].'/bitrix/templates/'.$component_template.'/components'.$component_path.'/.default/result_modifier.php')) $inc_file = $this->arParams['doc_root_path'].'/bitrix/templates/'.$component_template.'/components'.$component_path.'/.default/result_modifier.php'; else $inc_file = dirname($this->current_file).'/templates/'.$component_template.'/result_modifier.php'; unset($component_path); } else { continue; } } //component template including elseif($token == T_INCLUDE_COMPONENTTEMPLATE) { if(!$this->mp_mode) { $template_name = 'template'; $component_template = (!empty($this->tokens[$i][3]) ? $this->tokens[$i][3] : '.default'); $skip = 3; if($this->tokens[$i + 1] === '(' && $this->tokens[$i + 2][0] === T_CONSTANT_ENCAPSED_STRING) { $tmp = substr($this->tokens[$i + 2][1], 1, -1); if(!empty($tmp)) $template_name = $tmp; unset($tmp); } elseif($this->tokens[$i + 1] === '(' && $this->tokens[$i + 2] === ')') { $skip = 2; } $component_path = array_pop(explode('bitrix/components', dirname($this->current_file))); if(is_file($this->arParams['doc_root_path'].'/bitrix/templates/'.$this->template.'/components'.$component_path.'/'.$component_template.'/'.$template_name.'.php')) $inc_file = $this->arParams['doc_root_path'].'/bitrix/templates/'.$this->template.'/components'.$component_path.'/'.$component_template.'/'.$template_name.'.php'; elseif(is_file($this->arParams['doc_root_path'].'/bitrix/templates/.default/components'.$component_path.'/'.$component_template.'/'.$template_name.'.php')) $inc_file = $this->arParams['doc_root_path'].'/bitrix/templates/.default/components'.$component_path.'/'.$component_template.'/'.$template_name.'.php'; else $inc_file = dirname($this->current_file).'/templates/'.$component_template.'/'.$template_name.'.php'; unset($component_path); } else { continue; } } // include('xxx') elseif((($this->tokens[$i + 1] === '(' && $this->tokens[$i + 2][0] === T_CONSTANT_ENCAPSED_STRING && $this->tokens[$i + 3] === ')') // include 'xxx' || (is_array($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === T_CONSTANT_ENCAPSED_STRING && $this->tokens[$i + 2] === ';')) ) { if($this->tokens[$i + 1] === '(') { $inc_file = substr($this->tokens[$i + 2][1], 1, -1); $skip = 5; } else { $inc_file = substr($this->tokens[$i + 1][1], 1, -1); $skip = 3; } } else { $inc_file = $this->getTokensValue( $this->current_file, array_slice($this->tokens, $i + 1, $c = $this->getBraceEnd($this->tokens, $i + 1) + 1)); $skip = $c + 1; } $try_file = $inc_file; if(!is_file($try_file)) // && strpos($try_file, $this->arParams['path'] !== 0)) { $try_file = dirname($this->current_file).'/'.$inc_file; $try_file = str_replace('//', '/', $try_file); } //not including bitrix core, too hard:( if(is_file($try_file) && !preg_match($this->arParams['PREG_FOR_SKIP_INCLUDE'], $inc_file)) { if( $this->current_file !== realpath($try_file) // Circle including && !in_array(realpath($try_file), $this->file_history) ) { if($file = file_get_contents($try_file)) { $inc_tokens = $this->tokenize($file, $component_template); $this->tokens = array_merge(array_slice($this->tokens, 0, $i + $skip), $inc_tokens, array(array(T_INCLUDE_END, 0, $inc_file)), array_slice($this->tokens, $i + $skip)); $tokens_count = count($this->tokens); $this->current_file = & realpath($try_file); $this->file_history[] = $try_file; $this->comment = str_replace(realpath(trim($this->arParams['path'])), '', realpath(trim($try_file))).' '; } } } unset($try_file); } //////TAINT ANALYSIS////////// if(isset($this->scan_functions[$token_value]) && !(($this->tokens[$i + 1] === '(' && $this->tokens[$i + 2] === ')') || $this->tokens[$i + 1] === ';') //skip function with empty parameter list ) { //check if query function is NOT belong to CHTTP object if ( $token_value === 'query' && (is_array($this->tokens[$i-2]) && $this->tokens[$i-2][0] === T_VARIABLE) && isset($this->objects[$this->tokens[$i-2][1]]) && $this->objects[$this->tokens[$i-2][1]] === 'CHTTP' ) { $result = false; } elseif($this->tokens[$i + 1] === '(') { $result = $this->getTokensInfo(array_slice($this->tokens, $i + 2, $this->getBraceEnd($this->tokens, $i + 2) - 1), false, $token_value); } else { $result = $this->getTokensInfo(array_slice($this->tokens, $i + 1, $this->getBraceEnd($this->tokens, $i + 1)), false, $token_value); } if($result !== false) { if($this->tokens[$i + 1] === '(') $result = $this->getTokensInfo(array_slice($this->tokens, $i + 2, $this->getBraceEnd($this->tokens, $i + 2) - 1), false, $token_value); else $result = $this->getTokensInfo(array_slice($this->tokens, $i + 1, $this->getBraceEnd($this->tokens, $i + 1)), false, $token_value); $tainted_vars = array(); foreach ($result[1] as $res) { if(!$res['secure'] && !isset($tainted_vars[$res['var_name']])) { if($res['requestInitialization'] || !array_key_exists($token_value, $this->vuln_func['XSS'])) { $tainted_vars[$res['var_name']] = '<div class="checklist-vulnscan-code">'; $tainted_vars[$res['var_name']].= $this->traverseVar($res['var_name'], $i); $tainted_vars[$res['var_name']].= '</div>'; $this->vuln_count++; } } } if(!empty($tainted_vars)) { //little hack for $DB->Query, TODO: Fix this later! if($token_value === 'query') $pos = $i - 2; else $pos = $i; $vuln = new CVuln( $this->scanning_file, $cur_line, $token_value, array_slice($this->tokens, $pos, $this->getBraceEnd($this->tokens, $pos)), $this->dependencies, $tainted_vars, $this->comment ); $this->arResult[] = $vuln; } } } } elseif(in_array($token, $this->tokens_type['FLOW_CONTROL'])) { $c = 1; while (($i + $c) < count($this->tokens) && $this->tokens[$i + $c] !== '{') $c++; $this->dependency = array_slice($this->tokens, $i, $c); $this->dependency_line = $cur_line; } elseif($token === T_INCLUDE_END) { array_pop($this->file_history); $this->current_file = & end($this->file_history); $this->comment = basename($this->current_file) == basename($this->scanning_file) ? '' : basename($this->current_file); } //dependency check if($token === T_EXIT && count($this->dependencies) === 1 && $this->dependencyHave(end($this->dependencies), '!XSS')) $this->global_xss_ignore = true; //end of dependency check } else { if($this->tokens[$i] === '{' && ($this->tokens[$i - 1] === ')' || $this->tokens[$i - 1] === ':' || (is_array($this->tokens[$i - 1]) && $this->tokens[$i - 1][0] === T_ELSE)) ) { if(count($this->dependency) > 2 && $this->dependency[0][0] === T_ELSE && $this->dependency[1][0] !== T_IF ) { $this->dependency = $this->last_dependency; $this->dependency[] = array(T_ELSE, 'else', $this->dependency[0][2]); } $this->dependencies[$this->dependency_line] = $this->dependency; if($this->dependencyHave($this->dependency, 'XSS')) $this->search_xss = false; $this->dependency = array(); $this->dependency_line = 'syntax_error'; $this->braces++; } elseif($this->tokens[$i] === '}' && ($this->tokens[$i - 1] === ';' || $this->tokens[$i - 1] === '}' || $this->tokens[$i - 1] === '{') ) { $this->braces--; $this->last_dependency = array_pop($this->dependencies); if($this->dependencyHave($this->last_dependency, 'XSS')) $this->search_xss = true; $this->dependency = array(); } } } return true; } private static function tokenToString($token) { $result = ''; for ($i = 0, $count = count($token); $i < $count; $i++) { if(is_array($token[$i])) $result .= self::tokenToString($token[$i]); else $result .= $token[$i][1]; } return $result; } private function getClassName($token) { $className = ''; while( is_array($this->tokens[$token]) && ( ($this->tokens[$token][0] === T_STRING) || ($this->tokens[$token][0] === T_NS_SEPARATOR) ) ) { $className .= $this->tokens[$token][1]; $token++; } return $className; } private function getVarName($token, $level = -1) { $var_name = $token[1] ?? null; if (isset($token[3]) && is_array($token[3])) { for ($i = 0, $count = count($token[3]); $i < $count && ($i < $level || $level === -1); $i++) { if(is_array($token[3][$i])) { /* // TODO: to fix with foreach $res=$this->getTokensInfo($token[3][$i], true); if($res === false) $var_name .='['.self::tokenToString($token[3][$i]).']'; */ } else { $var_name .= '['.$token[3][$i].']'; } } } return $var_name; } private function clearVariables($var) { $this->variables; if(!empty($this->variables)) { foreach (array_keys($this->variables) as $key) { if(preg_match('/'.preg_quote($var).'\[/is', $key)) unset($this->variables[$key]); } } } private function addVariable($var, $id, $line, $start, $end, $comment = '', $customTokens = array()) { $tokens = !empty($customTokens) ? $customTokens : array_slice($this->tokens, $start, $end); $tokensForScan = !empty($customTokens) ? $customTokens : array_slice($this->tokens, $start + 2, $end - 2); $tokensInfo = $this->getTokensInfo($tokensForScan); $dependencies = array(); /*TODO: Use dependency to detect overwritten variable! * foreach ($this->dependencies as $dep_line => $dependency) { if(!empty($dependency)) $dependencies[$dep_line] = $dependency; }*/ $varName = self::getVarName($var); $this->clearVariables($varName); if($tokensInfo !== false) { $taintedVars = array(); foreach ($tokensInfo[1] as $res) $taintedVars[] = $res['varName'] ?? null; if(!isset($this->variables[$varName])) $var = new CVariable($varName); else $var = $this->variables[$varName]; $var->have_user_input = true; $var->secure = $tokensInfo[0]; $var->requestInitialization = $tokensInfo[2]; if (!$this->search_xss) $var->requestInitialization = false; $var->newDeclare($id, $line, $start, $end, $tokens, $this->comment.$comment, $dependencies, $taintedVars); $this->variables[$varName] = $var; } elseif(isset($this->variables[$varName])) { unset($this->variables[$varName]); //TODO: Fix this, with dependency overwritten! } } public function tokenize($code, $component_template = '') { if(preg_match_all('/\$GLOBALS\[\'[_0-9]*\'\]/', $code, $mat) > 20) return array(); $tokens = token_get_all($code); $tokens = $this->prepareTokens($tokens); $tokens = $this->reconstructArray($tokens); $tokens = $this->fixTokens($tokens, $component_template); $tokens = $this->removeTernary($tokens); return $tokens; } private function prepareTokens($tokens) { for ($i = 0, $c = count($tokens); $i < $c; $i++) { if(is_array($tokens[$i])) { if(in_array($tokens[$i][0], $this->tokens_type['IGNORE'])) unset($tokens[$i]); elseif($tokens[$i][0] === T_CLOSE_TAG) if(!isset($tokens[$i - 1]) || ($tokens[$i - 1] !== '{' && $tokens[$i - 1] !== ';')) $tokens[$i] = ';'; else unset($tokens[$i]); elseif($tokens[$i][0] === T_OPEN_TAG_WITH_ECHO) $tokens[$i][1] = 'echo'; elseif($tokens[$i][0] == T_CLASS) { unset($tokens[$i]); $braces = 1; $f = 1; while ($tokens[$i + $f] !== '{' && ($i + $f) < $c) { unset($tokens[$i + $f]); $f++; } $f++; while ($braces !== 0 && ($i + $f) < $c) { if($tokens[$i + $f] === '{') $braces++; elseif($tokens[$i + $f] === '}') $braces--; unset($tokens[$i + $f]); $f++; } for ($j = $i; $j < $i + $f + 1; $j++) unset($tokens[$j]); $i += $f; } elseif($tokens[$i][0] == T_FUNCTION) { unset($tokens[$i]); if(is_array($tokens[$i + 1]) && $tokens[$i + 1][0] === T_STRING) { $this->sec_func['STRING'][] = strtolower($tokens[$i + 1][1]); } else { $f = 1; while ($tokens[$i + $f] !== '(' && ($i + $f) < $c) { $f++; } if(isset($tokens[$i + $f - 1]) && is_array($tokens[$i + $f - 1]) && $tokens[$i + $f - 1][0] === T_STRING) { $this->sec_func['STRING'][] = strtolower($tokens[$i + $f - 1][1]); } } $f = 1; $braces = 1; while (($i + $f) < $c && $tokens[$i + $f] !== '{') { unset($tokens[$i + $f]); $f++; } $f++; while ($braces !== 0 && ($i + $f) < $c) { if(isset($tokens[$i + $f]) && $tokens[$i + $f] === '{') $braces++; elseif(isset($tokens[$i + $f]) && $tokens[$i + $f] === '}') $braces--; unset($tokens[$i + $f]); $f++; } for ($j = $i; $j < $i + $f + 1; $j++) unset($tokens[$j]); $i += $f; } } elseif(isset($tokens[$i]) && $tokens[$i] === '@') { unset($tokens[$i]); } } return array_values($tokens); } private function wrapBraces($tokens, $start, $between, $end) { $tokens = array_merge( array_slice($tokens, 0, $start), array('{'), array_slice($tokens, $start, $between), array('}'), array_slice($tokens, $end) ); return $tokens; } private function fixTokens($tokens, $component_template = '') { for ($i = 0, $max = count($tokens); $i < $max; $i++) { if($tokens[$i] === '`') { $f = 1; while ($tokens[$i + $f] !== '`') { if(is_array($tokens[$i + $f])) $line = $tokens[$i + $f][2]; $f++; if(!isset($tokens[$i + $f]) || $tokens[$i + $f] === ';') { break; } } if(!empty($line)) { $tokens[$i + $f] = ')'; $tokens[$i] = array(T_STRING, 'backticks', $line); $tokens = array_merge( array_slice($tokens, 0, $i + 1), array('('), array_slice($tokens, $i + 1) ); } } elseif(is_array($tokens[$i])) { if(strtolower($tokens[$i][1]) === 'includecomponenttemplate' || strtolower($tokens[$i][1]) === 'initcomponenttemplate') { $tokens[$i][3] = $component_template; $tokens[$i][1] = strtolower($tokens[$i][1]); $tokens[$i][0] = T_INCLUDE_COMPONENTTEMPLATE; $tmp = array(array(T_INCLUDE_RESULT_MODIFIER, 'include_result_modifier', $tokens[$i][2], $component_template)); array_splice($tokens, $i - 1, 0, $tmp); $i++; } elseif(strtolower($tokens[$i][1]) == 'includecomponent') { $tokens[$i][1] = strtolower($tokens[$i][1]); $tokens[$i][0] = T_INCLUDE_COMPONENT; } elseif(($tokens[$i][0] === T_IF || $tokens[$i][0] === T_ELSEIF || $tokens[$i][0] === T_FOR || $tokens[$i][0] === T_FOREACH || $tokens[$i][0] === T_WHILE) && $tokens[$i + 1] === '(' ) { $f = 2; $braceopen = 1; while ($braceopen !== 0 && ($i + $f) < $max) { if($tokens[$i + $f] === '(') $braceopen++; elseif($tokens[$i + $f] === ')') $braceopen--; $f++; if(!isset($tokens[$i + $f])) { break; } } if($tokens[$i + $f] === ':') { switch ($tokens[$i][0]) { case T_IF: case T_ELSEIF: $endtoken = T_ENDIF; break; case T_FOR: $endtoken = T_ENDFOR; break; case T_FOREACH: $endtoken = T_ENDFOREACH; break; case T_WHILE: $endtoken = T_ENDWHILE; break; default: $endtoken = ';'; } $c = 1; while ($tokens[$i + $f + $c][0] !== $endtoken) { $c++; if(!isset($tokens[$i + $f + $c])) { break; } } $tokens = $this->wrapBraces($tokens, $i + $f + 1, $c + 1, $i + $f + $c + 2); } elseif($tokens[$i + $f] !== '{' && $tokens[$i + $f] !== ';') { $c = 1; while ($tokens[$i + $f + $c] !== ';' && $c < $max) { $c++; } $tokens = $this->wrapBraces($tokens, $i + $f, $c + 1, $i + $f + $c + 1); } } elseif($tokens[$i][0] === T_ELSE && $tokens[$i + 1][0] !== T_IF && $tokens[$i + 1] !== '{' ) { $f = 2; while ($tokens[$i + $f] !== ';' && $f < $max) { $f++; } $tokens = $this->wrapBraces($tokens, $i + 1, $f, $i + $f + 1); } elseif($tokens[$i][0] === T_SWITCH && $tokens[$i + 1] === '(') { $braces = 1; $c = 2; while ($braces !== 0) { if($tokens[$i + $c] === '(') { $braces++; } elseif($tokens[$i + $c] === ')') { $braces--; } elseif(!isset($tokens[$i + $c]) || $tokens[$i + $c] === ';') { break; } $c++; } if($tokens[$i + $c] === ':') { $f = 1; while ($tokens[$i + $c + $f][0] !== T_ENDSWITCH) { $f++; if(!isset($tokens[$i + $c + $f])) break; } $tokens = $this->wrapBraces($tokens, $i + $c + 1, $f + 1, $i + $c + $f + 2); } } elseif($tokens[$i][0] === T_CASE) { $e = 1; while ($tokens[$i + $e] !== ':' && $tokens[$i + $e] !== ';') { $e++; if(!isset($tokens[$i + $e])) break; } $f = $e + 1; if(($tokens[$i + $e] === ':' || $tokens[$i + $e] === ';') && $tokens[$i + $f] !== '{' && $tokens[$i + $f][0] !== T_CASE && $tokens[$i + $f][0] !== T_DEFAULT ) { $braces = 0; while ($braces || (isset($tokens[$i + $f]) && $tokens[$i + $f] !== '}' && !(is_array($tokens[$i + $f]) && ($tokens[$i + $f][0] === T_BREAK || $tokens[$i + $f][0] === T_CASE || $tokens[$i + $f][0] === T_DEFAULT || $tokens[$i + $f][0] === T_ENDSWITCH ) ) )) { if($tokens[$i + $f] === '{') $braces++; elseif($tokens[$i + $f] === '}') $braces--; $f++; if(!isset($tokens[$i + $f])) break; } if($tokens[$i + $f][0] === T_BREAK) { if($tokens[$i + $f + 1] === ';') $tokens = $this->wrapBraces($tokens, $i + $e + 1, $f - $e + 1, $i + $f + 2); else $tokens = $this->wrapBraces($tokens, $i + $e + 1, $f - $e + 2, $i + $f + 3); } else { $tokens = $this->wrapBraces($tokens, $i + $e + 1, $f - $e - 1, $i + $f); } $i++; } } elseif($tokens[$i][0] === T_DEFAULT && $tokens[$i + 2] !== '{' ) { $f = 2; $braces = 0; while ($tokens[$i + $f] !== ';' && $tokens[$i + $f] !== '}' || $braces) { if($tokens[$i + $f] === '{') $braces++; elseif($tokens[$i + $f] === '}') $braces--; $f++; if(!isset($tokens[$i + $f])) break; } $tokens = $this->wrapBraces($tokens, $i + 2, $f - 1, $i + $f + 1); } elseif($tokens[$i][0] === T_FUNCTION) { $tokens[$i + 1][1] = strtolower($tokens[$i + 1][1]); } elseif($tokens[$i][0] === T_STRING && $tokens[$i + 1] === '(') { $tokens[$i][1] = strtolower($tokens[$i][1]); } elseif($tokens[$i][0] === T_DO) { $f = 2; $otherDOs = 0; while ($tokens[$i + $f][0] !== T_WHILE || $otherDOs) { if($tokens[$i + $f][0] === T_DO) $otherDOs++; elseif($tokens[$i + $f][0] === T_WHILE) $otherDOs--; $f++; if(!isset($tokens[$i + $f])) break; } if($tokens[$i + 1] !== '{') { $tokens = $this->wrapBraces($tokens, $i + 1, $f - 1, $i + $f); $f += 2; } $d = 1; while ($tokens[$i + $f + $d] !== ';' && $d < $max) { $d++; } $tokens = array_merge( array_slice($tokens, 0, $i), array_slice($tokens, $i + $f, $d), array_slice($tokens, $i + 1, $f - 1), array_slice($tokens, $i + $f + $d + 1, count($tokens)) ); } } } return array_values($tokens); } private function reconstructArray($tokens) { for ($i = 0, $max = count($tokens); $i < $max; $i++) { if(is_array($tokens[$i]) && $tokens[$i][0] === T_VARIABLE && $tokens[$i + 1] === '[') { $tokens[$i][3] = array(); $has_more_keys = true; $index = -1; $c = 2; while ($has_more_keys && $index < 20) { $index++; if(($tokens[$i + $c][0] === T_CONSTANT_ENCAPSED_STRING || $tokens[$i + $c][0] === T_LNUMBER || $tokens[$i + $c][0] === T_NUM_STRING) && $tokens[$i + $c + 1] === ']') { unset($tokens[$i + $c - 1]); $tokens[$i][3][$index] = str_replace(array('"', "'"), '', $tokens[$i + $c][1]); unset($tokens[$i + $c]); unset($tokens[$i + $c + 1]); $c += 2; } else { $tokens[$i][3][$index] = array(); $braces = 1; unset($tokens[$i + $c - 1]); while ($braces !== 0 && $c < 100) { if($tokens[$i + $c] === '[') { $braces++; } elseif($tokens[$i + $c] === ']') { $braces--; } else { $tokens[$i][3][$index][] = $tokens[$i + $c]; } unset($tokens[$i + $c]); $c++; } unset($tokens[$i + $c - 1]); } if($tokens[$i + $c] !== '[') $has_more_keys = false; $c++; } $i += $c - 1; } } return array_values($tokens); } private function removeTernary($tokens) { $max = count($tokens); $i = 0; while ($i < $max) { if($tokens[$i] === '?') { unset($tokens[$i]); $k = 1; $braces = 0; while (!(isset($tokens[$i - $k]) && ($tokens[$i - $k] === ';' || $tokens[$i - $k] === ', ' || $tokens[$i - $k] === '.' || $tokens[$i - $k] === '{' || $tokens[$i - $k] === '}') && $braces <= 0) && ($i - $k) > 0) { if(isset($tokens[$i - $k]) && $tokens[$i - $k] === ')') $braces++; elseif(isset($tokens[$i - $k]) && $tokens[$i - $k] === '(') $braces--; unset($tokens[$i - $k]); $k++; } $k = 1; $braces = 0; while (!(isset($tokens[$i + $k]) && ($tokens[$i + $k] === ';' || $tokens[$i + $k] === ', ' || $tokens[$i + $k] === '.' || $tokens[$i + $k] === '}') && $braces <= 0) && ($i + $k) < $max) { if(isset($tokens[$i + $k]) && $tokens[$i + $k] === '(') $braces++; elseif(isset($tokens[$i + $k]) && $tokens[$i + $k] === ')') $braces--; unset($tokens[$i + $k]); $k++; } $i += $k; } $i++; } return array_values($tokens); } private function getTokensValue($file_name, $tokens, $start = 0, $stop = 0) { $value = ''; if(!$stop) $stop = count($tokens); for ($i = $start; $i < $stop; $i++) { if(is_array($tokens[$i])) { if($tokens[$i][0] === T_VARIABLE || ($tokens[$i][0] === T_STRING && ($i + 1) < $stop && $tokens[$i + 1] !== '(') ) { if(!in_array($tokens[$i][1], $this->v_userinput)) { if($tokens[$i][1] === 'DIRECTORY_SEPARATOR' || $tokens[$i][1] === 'PATH_SEPARATOR') $value .= '/'; elseif(strtolower($tokens[$i][1]) === '$componentpath') $value = dirname($file_name); elseif($tokens[$i][1] === '$_SERVER' && $tokens[$i][3][0] === 'DOCUMENT_ROOT') $value .= $this->arParams['doc_root_path']; } } elseif($tokens[$i][0] === T_CONSTANT_ENCAPSED_STRING ) { $value .= substr($tokens[$i][1], 1, -1); } elseif($tokens[$i][0] === T_ENCAPSED_AND_WHITESPACE) { $value .= $tokens[$i][1]; } elseif($tokens[$i][0] === T_FILE && ($i > 2 && $tokens[$i - 2][0] === T_STRING && $tokens[$i - 2][1] === 'dirname') ) { $value = dirname($file_name).'/'; } elseif($tokens[$i][0] === T_LNUMBER || $tokens[$i][0] === T_DNUMBER || $tokens[$i][0] === T_NUM_STRING) { $value .= round($tokens[$i][1]); } elseif($tokens[$i][0] === T_AS) { break; } } } return $value; } private function getBraceEnd($tokens, $i) { $c = 1; $braces = 1; $max=count($tokens); while (!($braces === 0 || $tokens[$i + $c] === ';')) { if($tokens[$i + $c] === '(') { $braces++; } elseif($tokens[$i + $c] === ')') { $braces--; } if($c > 50 || $c > $max) break; $c++; } return $c; } private function getColor($token) { if(array_key_exists($token, $this->color)) return $this->color[$token]; else return '#007700'; } private function highlightArray($token) { $result = ''; foreach ($token as $key) { if($key != '*') { $result .= '<span style="color: #007700;">[</span>'; if(!is_array($key)) { if(is_numeric($key)) $result .= '<span style="color: #0000BB;">'.$key.'</span>'; else $result .= '<span style="color: #DD0000;">\''.htmlentities($key, ENT_QUOTES, 'utf-8').'\'</span>'; } else { foreach ($key as $token) { if(is_array($token)) { if(isset($token[3])) $result .= $this->highlightArray($token[3]); else $result .= '<span style="color: '.$this->getColor($token[0]).';">'.htmlentities($token[1], ENT_QUOTES, 'utf-8').'</span>'; } else $result .= "<span style=\"color: #007700;\">{$token}</span>"; } } $result .= '<span style="color: #007700;">]</span>'; } } return $result; } private function highlightLine($line, $tokens = array(), $tainted_vars = array(), $comment = '') { $output = ''; for ($i = 0, $count = count($tainted_vars); $i < $count; $i++) { if($pos = strpos($tainted_vars[$i], '[')) $tainted_vars[$i] = substr($tainted_vars[$i], 0, $pos); } if(isset($line)) $output .= "<span>$line:</span> "; for ($i = 0, $count = count($tokens); $i < $count; $i++) { $token = $tokens[$i]; if(is_string($token)) { if($token === ', ' || $token === ';') $output .= "<span style=\"color: #007700;\">$token </span>"; elseif(in_array($token, $this->tokens_type['SPACE_WRAP_STR']) || in_array($token, $this->tokens_type['ARITHMETIC_STR'])) $output .= '<span style="color: #007700;"> '.$token.' </span>'; else $output .= '<span style="color: #007700;">'.htmlentities($token, ENT_QUOTES, 'utf-8').'</span>'; } elseif(is_array($token) && $token[0] !== T_OPEN_TAG && $token[0] !== T_CLOSE_TAG ) { if(in_array($token[0], $this->tokens_type['SPACE_WRAP']) || in_array($token[0], $this->tokens_type['OPERATOR']) || in_array($token[0], $this->tokens_type['ASSIGNMENT'])) { $output .= ' <span style="color: '.$this->getColor($token[0]).";\">{$token[1]}</span> "; } else { $text = htmlentities($token[1], ENT_QUOTES, 'utf-8'); $text = str_replace(array(' ', "\n"), ' ', $text); if($token[0] === T_FUNCTION) $text .= ' '; $span = "<span "; if($token[0] === T_VARIABLE && is_array($tainted_vars) && in_array($token[1], $tainted_vars)) $span .= "style=\"color: #0000BB;\"><b>".$text."</b></span>"; else $span .= "style=\"color: ".$this->getColor($token[0]).";\">$text</span>"; $text = $span; if(isset($token[3])) { $text .= $this->highlightArray($token[3]); } $output .= $text; if(in_array($token[0], $this->tokens_type['INCLUDES']) || in_array($token[0], $this->tokens_type['XSS']) || $token[0] === 'T_EVAL') { $output .= ' '; } } } } if(!empty($comment)) { $output .= ' <span style="color: #808080;">// '.htmlentities($comment, ENT_QUOTES, 'utf-8').'</span>'; } return '<div style="clear:both;">'.$output.'</div>'; } private function getVulnNodeTitle($func_name) { if(isset($this->vuln_func['XSS'][$func_name])) $vulnname = GetMessage('VULNSCAN_XSS_NAME'); elseif(isset($this->vuln_func['HTTP_HEADER'][$func_name])) $vulnname = GetMessage('VULNSCAN_HEADER_NAME'); elseif(isset($this->vuln_func['DATABASE'][$func_name])) $vulnname = GetMessage('VULNSCAN_DATABASE_NAME'); elseif(isset($this->vuln_func['FILE_INCLUDE'][$func_name])) $vulnname = GetMessage('VULNSCAN_INCLUDE_NAME'); elseif(isset($this->vuln_func['EXEC'][$func_name])) $vulnname = GetMessage('VULNSCAN_EXEC_NAME'); elseif(isset($this->vuln_func['CODE'][$func_name])) $vulnname = GetMessage('VULNSCAN_CODE_NAME'); elseif(isset($this->vuln_func['POP'][$func_name])) $vulnname = GetMessage('VULNSCAN_POP_NAME'); elseif(isset($this->vuln_func['OTHER'][$func_name])) $vulnname = GetMessage('VULNSCAN_OTHER_NAME'); else $vulnname = GetMessage('VULNSCAN_UNKNOWN'); return $vulnname; } private function getVulnNodeDescription($func_name) { if(isset($this->vuln_func['XSS'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_XSS_HELP'); elseif(isset($this->vuln_func['HTTP_HEADER'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_HEADER_HELP'); elseif(isset($this->vuln_func['DATABASE'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_DATABASE_HELP'); elseif(isset($this->vuln_func['FILE_INCLUDE'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_INCLUDE_HELP'); elseif(isset($this->vuln_func['EXEC'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_EXEC_HELP'); elseif(isset($this->vuln_func['CODE'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_CODE_HELP'); elseif(isset($this->vuln_func['POP'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_POP_HELP'); elseif(isset($this->vuln_func['OTHER'][$func_name])) $vulnhelp = GetMessage('VULNSCAN_OTHER_HELP'); else $vulnhelp = GetMessage('VULNSCAN_UNKNOWN_HELP'); return $vulnhelp; } private function getVulnName($func_name) { if (isset($this->vuln_func['XSS'][$func_name])) return 'XSS'; elseif (isset($this->vuln_func['HTTP_HEADER'][$func_name])) return 'HEADER'; elseif (isset($this->vuln_func['DATABASE'][$func_name])) return 'DATABASE'; elseif (isset($this->vuln_func['FILE_INCLUDE'][$func_name])) return 'INCLUDE'; elseif (isset($this->vuln_func['EXEC'][$func_name])) return 'EXEC'; elseif (isset($this->vuln_func['CODE'][$func_name])) return 'CODE'; elseif (isset($this->vuln_func['POP'][$func_name])) return 'POP'; elseif (isset($this->vuln_func['OTHER'][$func_name])) return 'OTHER'; else return 'UNKNOWN'; } private function traverseVar($var, $id = -1) { $result = ''; if(isset($this->variables[$var])) { $cur_var = $this->variables[$var]; foreach ($cur_var->declares as $var_declare) { if($var_declare->id < $id || $id === -1) { foreach ($var_declare->tainted_vars as $taint_var) { $res = $this->traverseVar($taint_var, $var_declare->id); if($res && strpos($result, $res) === false) $result .= $res; } $result .= '<div class="checklist-vulnscan-code-line">'; $result .= $this->highlightLine($var_declare->line, $var_declare->tokens, $var_declare->tainted_vars, $var_declare->comment); $result .= '</div>'; } } } return $result; } private function dependenciesTraverse($dependencies = array()) { $result = ''; if(!empty($dependencies)) { $result .= GetMessage('VULNSCAN_REQUIRE').':'; foreach ($dependencies as $line => $dependency) { if(!empty($dependency)) { $result .= $this->highlightLine($line, $dependency); } } } return $result; } private static function searchSimilarVuln($output, $max) { for ($i = 0; $i < $max; $i++) { if(($output[$i]->name === $output[$max]->name) && ($output[$i]->filename === $output[$max]->filename) && $output[$i]->tainted_vars === $output[$max]->tainted_vars) return $i; } return false; } private function prepareOutput($output) { for ($i = 0, $count = count($output); $i < $count; $i++) { if(($find = self::searchSimilarVuln($output, $i)) !== false) { $output[$find]->additional_text .= '<div class="checklist-vulnscan-dangerous-is-here">'; $output[$find]->additional_text .= $this->highlightLine($output[$i]->line, $output[$i]->tokens, $output[$i]->tainted_vars, $output[$i]->comment); $output[$find]->additional_text .= '</div>'; unset($output[$i]); } } return $output; } private function getHelp($category) { $result = ''; /* $result = '<div class="checklist-vulnscan-helpbox-sheme">'; $result .= '<table>'; $result .= '<tr><td>'.GetMessage('VULNSCAN_HELP_INPUT').'</td><td>'.GetMessage('VULNSCAN_HELP_FUNCTION').'</td><td>'.GetMessage('VULNSCAN_HELP_VULNTYPE').'</td></tr>'; $result .= '<tr><td>'; if (!empty($tree->source)) $result .= $tree->source; else $result .= '$_GET'; $result .= '</td><td>'; $result .= $tree->name.'()'; $result .= '</td><td>'; $result .= $this->getVulnNodeTitle($category); $result .= '</td></tr>'; $result .= '</table></div>';*/ $result .= '<div class="checklist-vulnscan-helpbox-description">'; $result .= GetMessage('VULNSCAN_'.$this->getVulnName($category).'_HELP'); $result .= '</div>'; $result .= '<div class="checklist-vulnscan-helpbox-safe-title">'; $result .= GetMessage('VULNSCAN_HELP_SAFE'); $result .= '</div>'; $result .= '<div class="checklist-vulnscan-helpbox-safe-description">'; $result .= GetMessage('VULNSCAN_'.$this->getVulnName($category).'_HELP_SAFE'); $result .= '</div>'; return $result; } public function getOutput() { $output = $this->prepareOutput($this->arResult); $result = ''; if(!empty($output)) { foreach ($output as $vuln) { $filename = htmlspecialcharsbx(str_replace(realpath(trim($this->arParams['doc_root_path'])), '',str_replace(realpath(trim($this->arParams['path'])), '', realpath(trim($vuln->filename))))); foreach ($vuln->tainted_vars as $tainted_var_name => $tainted_var) { $result .= '<div class="checklist-dot-line"></div><div class="checklist-vulnscan-files">'.'<span class="checklist-vulnscan-filename">'.GetMessage('VULNSCAN_FILE').': '.$filename.'</span>'.'<div id="'.$filename.'">'; $result .= '<div class="checklist-vulnscan-vulnblock">'.'<div class="checklist-vulnscan-vulnscan-blocktitle">'.GetMessage('VULNSCAN_'.$this->getVulnName($vuln->name).'_NAME').'</div>'; $result .= '<div style="visibility: hidden; display:none;" class="checklist-vulnscan-helpbox" data-help="'.$filename.'">'.$this->getHelp($vuln->name).'</div>'; $result .= $tainted_var; $result .= '<div class="checklist-vulnscan-dangerous-is-here">'; $result .= $this->highlightLine($vuln->line, $vuln->tokens, array($tainted_var_name), $vuln->comment); $result .= '</div>'; $result .= '<div class="checklist-vulnscan-dependecies">'; $result .= $this->dependenciesTraverse($vuln->dependencies); $result .= '</div>'; if(!empty($vuln->additional_text)) $result .= "\n".'<div><div class="checklist-vulnscan-vulnblocktitle">'.GetMessage('VULNSCAN_SIMILAR').':</div><div class="checklist-vulnscan-codebox"><div class="checklist-vulnscan-code">'.$vuln->additional_text.'</div></div></div>'; $result .= '</div></div></div>'; } } } return $result; } } class CQAACheckListTests { static private function getFiles($path, $skip_preg, $file_types, $doc_root, &$files, &$dirs) { $handle = opendir($path); if ($handle) { while (($file = readdir($handle)) !== false) { if($file === '.' || $file === '..') continue; $name = $path.'/'.str_replace("\\", "/", $file); if (preg_match($skip_preg, str_replace($doc_root, "", $name))) { continue; } if (is_dir($name)) { $dirs[] = $name; } elseif(in_array(substr($name, -4), $file_types)) { $files[] = $name; } } } closedir($handle); } static private function defineScanParams() { if(!defined('T_INCLUDE_RESULT_MODIFIER')) define('T_INCLUDE_RESULT_MODIFIER', 10001); if(!defined('T_INCLUDE_COMPONENTTEMPLATE')) define('T_INCLUDE_COMPONENTTEMPLATE', 10002); if(!defined('T_INCLUDE_COMPONENT')) define('T_INCLUDE_COMPONENT', 10003); if(!defined('T_INCLUDE_END')) define('T_INCLUDE_END', 10004); $SKIPDIR = array(// skipping directories 'lang', 'help', 'images', 'upload', 'uploads', 'jquery', 'js', 'css', '\/bitrix\/[^t].*', ); $SKIPFILE = array(// skipping files '\.access\.php', '\.description\.php', '\.parameters\.php', 'install\/[\w]*\.php', ); $arResult = Array( 'FILE_TYPES' => Array( '.php', '.inc' ), 'PREG_FOR_SKIP_INCLUDE' => '/\/modules\/(bitrix\.)?[^\W\.]+\//is', 'PREG_FOR_SKIP_SCAN' => '/(\/bitrix\/modules\/)|(\/bitrix\/components\/bitrix\/)|('.implode('$)|(', $SKIPDIR).'$)|('.implode('$)|(', $SKIPFILE).'$)/is', // 'VERBOSITY' => 1, 'MAX_TRACE' => 30, 'MAX_ARRAY_ELEMENTS' => 50, 'MP_mode' => false, 'production_mode' => false, 'path' => $_SERVER['DOCUMENT_ROOT'], 'doc_root_path' => $_SERVER['DOCUMENT_ROOT'] ); $arResult['TOKENS_TYPES'] = Array( 'IGNORE' => Array( T_BAD_CHARACTER, T_DOC_COMMENT, T_COMMENT, T_INLINE_HTML, T_WHITESPACE, T_OPEN_TAG ), 'LOOP_CONTROL' => Array( T_WHILE, T_FOR, T_FOREACH ), 'FLOW_CONTROL' => Array( T_IF, T_SWITCH, T_CASE, T_ELSE, T_ELSEIF ), 'ASSIGNMENT' => Array( T_AND_EQUAL, T_CONCAT_EQUAL, T_DIV_EQUAL, T_MINUS_EQUAL, T_MOD_EQUAL, T_MUL_EQUAL, T_OR_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL, T_SR_EQUAL, T_XOR_EQUAL ), 'ASSIGNMENT_SECURE' => Array( T_DIV_EQUAL, T_MINUS_EQUAL, T_MOD_EQUAL, T_MUL_EQUAL, T_OR_EQUAL, T_PLUS_EQUAL, T_SL_EQUAL, T_SR_EQUAL, T_XOR_EQUAL ), 'OPERATOR' => Array( T_IS_EQUAL, T_IS_GREATER_OR_EQUAL, T_IS_IDENTICAL, T_IS_NOT_EQUAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL ), 'FUNCTIONS' => Array( T_STRING, // all functions T_EVAL ), 'INCLUDES' => Array( T_INCLUDE, T_INCLUDE_ONCE, T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE_COMPONENT, T_INCLUDE_COMPONENTTEMPLATE, T_INCLUDE_RESULT_MODIFIER ), 'XSS' => Array( T_PRINT, T_ECHO, T_OPEN_TAG_WITH_ECHO, T_EXIT ), 'CASTS' => Array( T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_UNSET_CAST, T_UNSET ), 'LOGICAL' => Array( T_BOOLEAN_AND, T_BOOLEAN_OR, T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR ), 'SPACE_WRAP' => Array( T_AS, T_BOOLEAN_AND, T_BOOLEAN_OR, T_LOGICAL_AND, T_LOGICAL_OR, T_LOGICAL_XOR, T_SL, T_SR, T_CASE, T_ELSE, T_GLOBAL, T_NEW ), 'ARITHMETIC' => Array( T_INC, T_DEC ), 'ARITHMETIC_STR' => Array( '+', '-', '*', '/', '%' ), 'SPACE_WRAP_STR' => Array( '.', '=', '>', '<', ':', '?' ) ); $arResult['SECURING_FUNCTIONS'] = Array( 'BOOL' => Array( 'is_double', 'is_float', 'is_real', 'is_long', 'is_int', 'is_integer', 'ctype_alnum', 'ctype_alpha', 'ctype_cntrl', 'ctype_digit', 'ctype_xdigit', 'ctype_upper', 'ctype_lower', 'ctype_space', 'in_array', 'preg_match', 'preg_match_all', 'fnmatch', 'ereg', 'eregi', ), 'STRING' => Array( 'intval', 'floatval', 'doubleval', 'filter_input', 'urlencode', 'rawurlencode', 'round', 'floor', 'strlen', 'hexdec', 'strrpos', 'strpos', 'md5', 'sha1', 'crypt', 'crc32', 'base64_encode', 'ord', 'sizeof', 'count', 'bin2hex', 'levenshtein', 'abs', 'bindec', 'decbin', 'hexdec', 'rand', 'max', 'min', 'preg_replace', //Fix this later 'getimagesize', 'phpformatdatetime', 'mkdatetime', 'formatdateex', ), 'INSTRING' => Array( 'rawurldecode', 'urldecode', 'base64_decode', 'html_entity_decode', 'str_rot13', 'chr', 'htmlspecialcharsback', ), 'XSS' => Array( 'htmlentities', 'htmlspecialchars', 'htmlspecialcharsex', 'htmlspecialcharsbx', 'jsescape', 'jsurlescape', 'phptojsobject', 'showerror', 'showmessage', 'showimage', 'shownote', 'getcurpageparam', 'selectbox', 'selectboxm', 'selectboxfromarray', 'getmessage', 'getvars', 'highlight_string', 'inputtype', 'inputtags', ), 'SQL' => Array( 'addslashes', 'mysql_real_escape_string', 'forsql', ), 'PREG' => Array( 'preg_quote' ), 'FILE' => Array( 'rel2abs' ), 'SYSTEM' => Array( 'escapeshellarg', 'escapeshellcmd', ), 'XPATH' => Array( 'addslashes' ) ); $arResult['SECURING_FUNCTIONS']['QUOTE_ANALYSIS'] = Array($arResult['SECURING_FUNCTIONS']['SQL']); $arResult['SECURING_FUNCTIONS']['SECURES_ALL'] = array_merge( $arResult['SECURING_FUNCTIONS']['XSS'], $arResult['SECURING_FUNCTIONS']['SQL'], $arResult['SECURING_FUNCTIONS']['PREG'], $arResult['SECURING_FUNCTIONS']['FILE'], $arResult['SECURING_FUNCTIONS']['SYSTEM'], $arResult['SECURING_FUNCTIONS']['XPATH'] ); $arResult['VULN_FUNCTIONS'] = Array( 'XSS' => Array( 'echo' => array(0, $arResult['SECURING_FUNCTIONS']['XSS']), 'print' => array(array(1), $arResult['SECURING_FUNCTIONS']['XSS']), 'exit' => array(array(1), $arResult['SECURING_FUNCTIONS']['XSS']), 'die' => array(array(1), $arResult['SECURING_FUNCTIONS']['XSS']), ), 'HTTP_HEADER' => Array( 'header' => array(array(1), array()) ), 'CODE' => Array( 'assert' => Array(Array(1), Array()), 'call_user_func' => Array(Array(1), Array()), 'call_user_func_Array' => Array(Array(1), Array()), 'create_function' => Array(Array(1, 2), Array()), 'eval' => Array(Array(1), Array()), 'mb_ereg_replace' => Array(Array(1, 2), $arResult['SECURING_FUNCTIONS']['PREG']), 'mb_eregi_replace' => Array(Array(1, 2), $arResult['SECURING_FUNCTIONS']['PREG']), 'ob_start' => Array(Array(1), Array()), //'preg_replace' => Array(Array(1, 2), $arResult['SECURING_FUNCTIONS']['PREG']), //'preg_replace_callback' => Array(Array(1, 2), $arResult['SECURING_FUNCTIONS']['PREG']), ), 'FILE_INCLUDE' => Array( 'include' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['FILE']), 'include_once' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['FILE']), 'require' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['FILE']), 'require_once' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['FILE']), 'set_include_path' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['FILE']), ), 'EXEC' => Array( 'backticks' => Array(Array(1), Array()), 'exec' => Array(Array(1), Array()), 'passthru' => Array(Array(1), Array()), 'pcntl_exec' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['SYSTEM']), 'popen' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['SYSTEM']), 'proc_open' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['SYSTEM']), 'shell_exec' => Array(Array(1), Array()), 'system' => Array(Array(1), Array()), 'mail' => Array(Array(5), Array()), 'bxmail' => Array(Array(5), Array()) ), 'DATABASE' => Array( 'query' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['SQL']), 'mysql_query' => Array(Array(1), $arResult['SECURING_FUNCTIONS']['SQL']) ), 'OTHER' => Array( 'dl' => Array(Array(1), Array()), 'ereg' => Array(Array(2), Array()), 'eregi' => Array(Array(2), Array()), 'sleep' => Array(Array(1), Array()), // It's too difficult to validate, maybe in future versions //'unserialize' => Array(Array(1), Array()), //'extract' => Array(Array(1), Array()), //'mb_parse_str' => Array(Array(1), Array()), //'parse_str' => Array(Array(1), Array()), //'define' => Array(Array(1), Array()) ), 'POP' => Array( 'unserialize' => Array(Array(1), Array()), 'is_a' => Array(Array(1), Array()) ) ); $arResult['USER_INPUTS'] = Array( '$_GET', '$_POST', '$_COOKIE', '$_REQUEST', //'$_FILES', // Maybe later //'$_SERVER', // Maybe later '$_ENV', '$HTTP_GET_VARS', '$HTTP_POST_VARS', '$HTTP_COOKIE_VARS', '$HTTP_REQUEST_VARS', '$HTTP_POST_FILES', '$HTTP_SERVER_VARS', '$HTTP_ENV_VARS', '$HTTP_RAW_POST_DATA', '$argc', '$argv' ); $arResult['INIT_FUNCTIONS'] = Array( 'initbvar', 'initbvarfromarr' ); $arResult['COLORS']=array( T_DOLLAR_OPEN_CURLY_BRACES => '#007700', T_CURLY_OPEN => '#007700', T_OPEN_TAG => '#007700', T_CLOSE_TAG => '#007700', T_AND_EQUAL => '#007700', T_CONCAT_EQUAL => '#007700', T_DIV_EQUAL => '#007700', T_MINUS_EQUAL => '#007700', T_MOD_EQUAL => '#007700', T_MUL_EQUAL => '#007700', T_OR_EQUAL => '#007700', T_PLUS_EQUAL => '#007700', T_SL_EQUAL => '#007700', T_SR_EQUAL => '#007700', T_XOR_EQUAL => '#007700', T_IS_EQUAL => '#007700', T_IS_GREATER_OR_EQUAL => '#007700', T_IS_IDENTICAL => '#007700', T_IS_NOT_EQUAL => '#007700', T_IS_NOT_IDENTICAL => '#007700', T_INC => '#007700', T_DEC => '#007700', T_OBJECT_OPERATOR => '#007700', T_IF => '#007700', T_SWITCH => '#007700', T_WHILE => '#007700', T_DO => '#007700', T_EXIT => '#007700', T_TRY => '#007700', T_CATCH => '#007700', T_ISSET => '#007700', T_FOR => '#007700', T_FOREACH => '#007700', T_RETURN => '#007700', T_DOUBLE_ARROW => '#007700', T_AS => '#007700', T_CASE => '#007700', T_DEFAULT => '#007700', T_BREAK => '#007700', T_CONTINUE => '#007700', T_GOTO => '#007700', T_GLOBAL => '#007700', T_LOGICAL_AND => '#007700', T_LOGICAL_OR => '#007700', T_EMPTY => '#007700', T_UNSET => '#007700', T_ELSE => '#007700', T_ELSEIF => '#007700', T_LIST => '#007700', T_ARRAY => '#007700', T_ECHO => '#007700', T_START_HEREDOC => '#007700', T_END_HEREDOC => '#007700', T_FUNCTION => '#007700', T_PUBLIC => '#007700', T_PRIVATE => '#007700', T_PROTECTED => '#007700', T_STATIC => '#007700', T_CLASS => '#007700', T_NEW => '#007700', T_PRINT => '#007700', T_INCLUDE => '#007700', T_INCLUDE_ONCE => '#007700', T_REQUIRE => '#007700', T_REQUIRE_ONCE => '#007700', T_USE => '#007700', T_VAR => '#007700', T_BOOL_CAST => '#007700', T_DOUBLE_CAST => '#007700', T_INT_CAST => '#007700', T_UNSET_CAST => '#007700', T_BOOLEAN_OR => '#007700', T_BOOLEAN_AND => '#007700', T_FILE => '#007700', T_LINE => '#007700', T_DIR => '#007700', T_FUNC_C => '#007700', T_CLASS_C => '#007700', T_METHOD_C => '#007700', T_NS_C => '#007700', T_CONST => '#0000BB', T_VARIABLE => '#0000BB', T_STRING_VARNAME => '#0000BB', T_STRING => '#0000BB', T_EVAL => '#0000BB', T_LNUMBER => '#0000BB', T_ENCAPSED_AND_WHITESPACE => '#DD0000;', T_CONSTANT_ENCAPSED_STRING => '#DD0000;', T_INLINE_HTML => '#000000;', T_COMMENT => '#FF8000;', T_DOC_COMMENT => '#FF8000;' ); ini_set('auto_detect_line_endings', 1); ini_set('short_open_tag', 1); return $arResult; } static private function getCurTemplate($path, $mp_mode=false) { if(!$mp_mode) { $dbSiteRes=CSite::GetTemplateList(CSite::GetSiteByFullPath($path, true)); if(($arSiteRes = $dbSiteRes->Fetch()) !== false) return $arSiteRes['TEMPLATE']; } return '.default'; } static public function checkVulnerabilities($arParams) { if(extension_loaded('tokenizer') === true) { if(empty(\Bitrix\Main\Application::getInstance()->getSession()['BX_CHECKLIST'][$arParams['TEST_ID']])) \Bitrix\Main\Application::getInstance()->getSession()['BX_CHECKLIST'][$arParams['TEST_ID']] = Array(); $NS = &\Bitrix\Main\Application::getInstance()->getSession()['BX_CHECKLIST'][$arParams['TEST_ID']]; $arScanParams = self::defineScanParams(); $phpMaxExecutionTime = ini_get("max_execution_time"); $arScanParams['time_out'] = $phpMaxExecutionTime > 0 ? $phpMaxExecutionTime - 2: 30; $arScanParams['time_start'] = time(); $arScanParams['MP_mode'] = false; if($arParams['STEP'] === 0) { $NS = Array(); $NS['CUR_FILE_ID'] = 0; $NS['FILE_LIST'] = array(); $NS['DIR_LIST'] = array(); self::getFiles( $arScanParams['path'], $arScanParams['PREG_FOR_SKIP_SCAN'], $arScanParams['FILE_TYPES'], $arScanParams['path'], $NS['FILE_LIST'], $NS['DIR_LIST'] ); $NS['VULN_COUNT'] = 0; $NS['STUCK_FILE'] = -1; $NS['MESSAGE'] = Array(); } $time_end = $arScanParams['time_start'] + $arScanParams['time_out']; while ($NS['DIR_LIST'] && $time_end > time()) { $dir = array_shift($NS['DIR_LIST']); self::getFiles( $dir, $arScanParams['PREG_FOR_SKIP_SCAN'], $arScanParams['FILE_TYPES'], $arScanParams['path'], $NS['FILE_LIST'], $NS['DIR_LIST'] ); } if ($NS['DIR_LIST']) { return Array( 'IN_PROGRESS' => 'Y', 'PERCENT' => 0, ); } $result=true; do { if(is_file($file = $NS['FILE_LIST'][$NS['CUR_FILE_ID']])) { if(isset($output)) unset($output); if(isset($scan)) unset($scan); $scan = new CVulnScanner($file, $arScanParams, self::getCurTemplate($file, $arScanParams['MP_mode'])); $result = $scan->process(); if($result !== false) { if($scan->vuln_count > 0) { $NS['MESSAGE'][$NS['CUR_FILE_ID']]['VULN_COUNT'] = $scan->vuln_count; $NS['MESSAGE'][$NS['CUR_FILE_ID']]['OUTPUT'] = $scan->getOutput(); } $NS['CUR_FILE_ID']++; } else { if($NS['STUCK_FILE'] === $NS['CUR_FILE_ID']) { $NS['CUR_FILE_ID']++; $NS['STUCK_FILE'] = -1; } else $NS['STUCK_FILE'] = $NS['CUR_FILE_ID']; } } else { $NS['CUR_FILE_ID']++; } } while ($NS['CUR_FILE_ID'] < count($NS['FILE_LIST']) && $result !== false); if(!($NS['CUR_FILE_ID'] < count($NS['FILE_LIST']))) { $arDetailReport = ''; $vulnCount=0; foreach ($NS['MESSAGE'] as $file_output) if (!empty($file_output)) if (strpos($arDetailReport, $file_output['OUTPUT']) === false) { $arDetailReport .= $file_output['OUTPUT']; $vulnCount += $file_output['VULN_COUNT']; } unset(\Bitrix\Main\Application::getInstance()->getSession()['BX_CHECKLIST'][$arParams['TEST_ID']]); $arResult = Array( 'MESSAGE' => Array( 'PREVIEW' => GetMessage('VULNSCAN_FIULECHECKED').count($NS['FILE_LIST']).GetMessage('VULNSCAN_VULNCOUNTS').$vulnCount, 'PROBLEM_COUNT' => $vulnCount, 'DETAIL' => $arDetailReport ), 'STATUS' => ($vulnCount > 0 ? false : true) ); } else { $percent = round(($NS['CUR_FILE_ID']) / (count($NS['FILE_LIST']) * 0.01), 0); $arResult = Array( 'IN_PROGRESS' => 'Y', 'PERCENT' => number_format($percent, 2), ); } } else { $arResult = Array( 'MESSAGE' => Array( 'PREVIEW' => GetMessage('VULNSCAN_TOKENIZER_NOT_INSTALLED'), ), 'STATUS' => false ); } return $arResult; } } ?>