4use PhpParser\NodeFinder;
12 static $var =
'\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
13 static $spaces =
"[ \r\t\n]*";
14 static $request =
'(?:_REQUEST|_GET|_POST|_COOKIE|_SERVER(?!\[[\'"]DOCUMENT_ROOT[\'"]\])|_FILES)';
15 static $functions =
'(?:parse_str|hex2bin|str_rot13|base64_decode|url_decode|str_replace|str_ireplace|preg_replace|move_uploaded_file)';
16 static $evals = [
'eval',
'assert',
'create_function',
'exec',
'passthru',
'pcntl_exec',
'popen',
'proc_open',
'set_include_path',
'shell_exec',
'system'];
17 static $evals_reg =
'(?:assert|call_user_func|call_user_func_array|create_function|eval|exec|ob_start|passthru|pcntl_exec|popen|proc_open|set_include_path|shell_exec|system)';
18 static $black_reg =
'(https?://[0-9a-z\-]+\.pw/|password_verify|https?://(?:sw\.)?bitrix\.dev|wp-config|wp-admin|wp-login|deprecated-media-js|customize-menus-rtl|adminer_errors|/etc/passwd|/etc/hosts|mysql_pdo|__halt_compiler|/bin/sh|registerPHPFunctions)';
20 'Bitrix\Im\Call\Auth::authorizeById',
21 'Bitrix\ImOpenLines\Controller\Widget\Filter\Authorization::authorizeById',
22 'Bitrix\Imopenlines\Widget\Auth::authorizeById',
23 'Bitrix\Sale\Delivery\Services\Automatic::createConfig',
24 'Bitrix\Sender\Internals\DataExport::toCsv',
25 'Bitrix\Sender\Internals\QueryController\Base::call',
26 'CAllSaleBasket::ExecuteCallbackFunction',
27 'CAllSaleOrder::PrepareSql',
28 'CBPHelper::UsersStringToArray',
29 'CControllerClient::RunCommand',
30 'CMailFilter::CheckPHP',
31 'CMailFilter::DoPHPAction',
32 'CRestUtil::makeAuth',
33 'CSaleHelper::getOptionOrImportValues',
34 'CWebDavTools::sendJsonResponse',
36 private const false_positives = [
'9223e925409363b7db262cfea1b6a7e2',
'4d2cb64743ff3647bad4dea540d5b08e',
'd40c4da27ce1860c111fc0e68a4b39b5',
37 'ef9287187dc22a6ce47476fd80720878',
'13484affcdf9f45d29b61d732f8a5855',
'4a171d5dc7381cce26227c5d83b5ba0c',
'b41d3b390f0f5ac060f9819e40bda7eb',
38 '40142320d26a29586dc8528cfb183aac',
'f454f39a15ec9240d93df67536372c1b',
'29bba835e33ab80598f88e438857f342',
'77cdd8164d4940cb6bfaac906383a766',
39 '5b3425a6ff518fa2337b373e1c799959',
'7c60ccaee2b919c9e6b16b307eb80dab',
'bde611db5c3545005a7270edcffd8dc2',
'4d6b616171dbf06ff57d1dab8ea6bbce',
40 'a85abce54b4deb8cb157438dddca5a7c',
'de4f7ee97d421cf14d3951c0b4e5c2dd',
'379918e8f6486ce9a7bb2ed5a69dbee6',
'7ac4a2afcee04e683b092eb9402ee7ed',
41 '1d5eb769111fc9c7be2021300ee5740e',
'f2357a1fe8e984052b6ee69933d467dc',
'a9158139e1a619ca8cc320cf4469c250'];
44 static $default_config = [
'request' =>
true,
'from_request' =>
true,
'crypted' =>
true,
'files' =>
true,
45 'assigned' =>
false,
'params' =>
false,
'concat' =>
true,
'hardcoded' =>
false,
'value' =>
true,
'recursive' =>
false];
46 public static $database =
false;
47 public $db_log =
null;
48 public $db_file =
null;
49 public $doc_root =
null;
51 public $time_limit =
null;
52 public $base_dir =
null;
53 public $break_point =
null;
54 public $skip_path =
null;
55 public $found =
false;
56 public $db_error =
false;
59 public $collect_exceptions =
true;
62 static $cryptors = [
'rot13',
'str_rot13',
'base32_decode',
'base64_decode',
'gzinflate',
'unserialize',
63 'url_decode',
'pack',
'unpack',
'hex2bin',
'bzdecompress',
'gzuncompress',
'lzf_decompress',
'strrev'];
64 static $string_change = [
'preg_replace',
'str_ireplace',
'str_replace',
'substr',
'strrev'];
66 '[337] strings from black list' => [0.9],
67 '[630] long line' => [0.4],
68 '[321] base64_encoded code' => [0.8],
69 '[610] strange vars' => [0.5],
70 '[302] preg_replace_eval' => [0.9],
71 '[663] binary data' => [0.75],
72 '[640] strange exif' => [0.6],
73 '[500] php wrapper' => [0.7],
74 '[665] chars by code' => [0.8],
75 '[665] encoded code' => [0.8],
76 '[303] create_function' => [0.8, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
77 '[300] eval' => [1, 0.4, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.9],
78 '[302] unsafe callable argument' => [0.8, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
79 '[307] danger method' => [1, 0.4, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.9],
80 '[662] function return as a function' => [0.9, 0.8, 0.1, 1, 1, 0.3, 0.7, 0.7, 0.8],
81 '[663] strange function' => [1, 1, 0.1, 1, 1, 0.8, 0.7, 0.8, 0.9],
82 '[302] eregi' => [0.8, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
83 '[887] backticks' => [1, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
84 '[600] strange include' => [0.8, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
85 '[660] array member as a function' => [0.9, 0.8, 0.1, 1, 0.8, 0.3, 0.5, 0.8, 0.8],
86 '[298] mysql function' => [0.6, 0.8, 0.1, 1, 0.8, 0.9, 0.7, 0.8, 0.8],
87 '[300] command injection' => [1, 0.7, 0.1, 1, 0.8, 0.6, 0.7, 0.8, 0.9],
88 '[299] mail function' => [0.6, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
89 '[650] variable as a function' => [0.9, 0.8, 0.1, 1, 0.8, 0.3, 0.5, 0.8, 0.8],
90 '[304] filter_callback' => [0.6, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
91 '[305] strange function and eval' => [0.8, 0.8, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
92 '[301] file operations' => [0.5, 0.4, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
93 '[302] file operations' => [0.8, 0.4, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
94 '[400] bitrix auth' => [0.9, 0.8, 1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
95 '[308] no prolog file operations' => [0.5, 0.4, 0.1, 1, 0.8, 0.3, 0.7, 0.8, 0.8],
97 private $results = [];
99 private $result_collection =
null;
102 function __construct($progress = 0, $total = 0, $start_path=
'')
104 $this->doc_root = rtrim(
$_SERVER[
'DOCUMENT_ROOT'],
'/');
105 $this->base_dir = $start_path;
107 $this->result_collection = new \Bitrix\Security\XScanResults();
108 $this->db_file = $this->doc_root .
'/bitrix/modules/security/data/database.json';
109 $this->start_time = time();
111 $mem = (int)ini_get(
'memory_limit');
112 $this->time_limit = ini_get(
'max_execution_time') ?: 30;
113 $this->time_limit = min($this->time_limit, 30);
114 $this->time_limit = $this->time_limit * 0.7;
116 $this->db_error = $mem >= 0 && $mem <= 128;
118 $this->progress = $progress;
119 $this->total = $total;
121 $this->parser = (
new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
122 $this->nodeFinder =
new NodeFinder;
123 $this->errorHandler =
new PhpParser\ErrorHandler\Collecting;
124 $this->pprinter =
new PhpParser\PrettyPrinter\Standard;
126 $errs = XScanResultTable::getList([
'select' => [
'SRC'],
'filter' => [
'TYPE' =>
'file',
'MESSAGE' =>
'error']]);
128 while ($row = $errs->fetch())
130 $this->errors[] = $row[
'SRC'];
134 function start($start_path)
137 $this->CheckTriggers();
138 $this->CheckEvents();
139 $this->CheckAgents();
140 $this->CheckTemplates();
141 $this->Search($start_path,
'count');
145 private function clean()
148 $DB->Query(
"TRUNCATE TABLE b_sec_xscan_results",
true);
152 function CheckTriggers()
156 if ($r =
$DB->Query(
'SHOW triggers;',
true))
158 if ($row = $r->Fetch())
160 $result = (
new XScanResult)->
setType(
'trigg')->setSrc($row[
'Trigger'])->setScore(1)->setMessage(
'[040] triggers');
161 $this->result_collection[] =
$result;
166 function CheckEvents()
170 $r =
$DB->Query(
'SELECT * from b_module_to_module');
172 while ($row = $r->Fetch())
174 if ($row[
'TO_CLASS'] && $row[
'TO_METHOD'])
176 $class_method = trim($row[
'TO_CLASS'] .
'::' . $row[
'TO_METHOD'],
'\\');
178 foreach (self::$mehtods as $mtd)
180 if (stripos($class_method, $mtd) !==
false)
189 $result = (
new XScanResult)->
setType(
'event')->setSrc($row[
'ID'])->setScore(1)->setMessage(
'[050] dangerous method at event, check arguments');
190 $this->result_collection[] =
$result;
196 function CheckAgents()
200 $r =
$DB->Query(
'SELECT * from b_agent');
202 while ($row = $r->Fetch())
209 $src =
"<?php\n" . $row[
'NAME'] .
"\n?>";
210 $this->CheckCode($src);
215 foreach ($this->results as
$res)
226 $this->result_collection[] =
$result;
231 function CheckTemplates()
235 $r =
$DB->Query(
'SELECT * from b_site_template');
237 while ($row = $r->Fetch())
239 if (!$row[
'CONDITION'])
244 $src =
"<?php\n" . $row[
'CONDITION'] .
"\n?>";
245 $this->CheckCode($src);
250 foreach ($this->results as
$res)
261 $this->result_collection[] =
$result;
266 static function crc(
$a)
268 return crc32(implode(
'|',
$a));
271 static function CountBlocks($src, &
$result)
273 $code = strtolower($src);
275 $code = preg_replace(
'~<\?(php|=)?~',
'',
$code);
276 $code = preg_replace(
'~<[^>$()]*?>~',
'',
$code);
278 $code = preg_split(
'~[\n;{}(),\s.]+~',
$code);
282 foreach (
$code as $chunk)
284 $chunk = trim($chunk);
302 $crcs[] = self::crc($block);
305 for (
$i = 3;
$i <= $end;
$i++)
307 $block = [$block[1], $block[2],
$arr[
$i]];
308 $crcs[] = self::crc($block);
319 function SearchInDataBase($src)
323 self::CountBlocks($src,
$result);
327 if (isset(self::$database[
'tokens'][$token]))
329 foreach (self::$database[
'tokens'][$token] as $shell)
331 if (!isset($found[$shell]))
342 foreach ($found as
$key => $value)
344 if ($value / self::$database[
'shells'][
$key] > 0.8)
357 function addResult($subj,
$code, $score, $checksum =
'')
359 $this->results[] = [
'subj' => $subj,
'code' =>
$code,
'score' => $score,
'checksum' => $checksum];
363 static function detectDocRoot($file_path)
367 if (!$doc_root || strpos($file_path, $doc_root .
'/') !== 0)
369 $path = explode(
'/', ltrim($file_path,
'/'));
372 foreach (
$path as $comp)
374 if (is_file($doc_root .
'/bitrix/.settings.php'))
379 $doc_root .=
'/' . $comp;
391 static function getVersion($moduleName, $doc_root)
393 $moduleName = preg_replace(
"/[^a-zA-Z0-9_.]+/i",
"", trim($moduleName));
394 if ($moduleName ==
'')
398 if ($moduleName ==
'main')
400 $content = file_get_contents(
"$doc_root/bitrix/modules/main/classes/general/version.php");
405 $content = file_get_contents(
"$doc_root/bitrix/modules/$moduleName/install/version.php");
408 preg_match(
'/\d+\.\d+\.\d+/',
$content, $m);
409 $version =
count($m)? $m[0]:
false;
414 static function getHashes($module, $version)
418 static $static_cache;
420 if (!is_array($static_cache)){
424 $key = $module .
'_' . $version;
426 if (isset($static_cache[
$key]))
428 return $static_cache[
$key];
431 $cache = \Bitrix\Main\Data\Cache::createInstance();
433 if ($cache && $cache->initCache(12 * 3600,
'xscan_' .
$key,
'xscan'))
437 return $static_cache[
$key];
441 $sHost = \Bitrix\Main\Application::getInstance()->getLicense()->getDomainStoreLicense();
442 $dbtype = mb_strtolower(
$DB->type);
443 $http = new \Bitrix\Main\Web\HttpClient();
445 $data = $http->get(
$sHost .
"/bitrix/updates/checksum.php?check_sum=Y&module_id={$module}&ver={$version}&dbtype={$dbtype}&mode=2");
446 $result = @unserialize(gzinflate(
$data), [
'allowed_classes' =>
false]);
448 $static_cache[
$key] = [];
452 return preg_match(
'/\.(php|js)$/', $value);
453 }, ARRAY_FILTER_USE_KEY);
455 $cache->startDataCache();
460 return $static_cache[
$key];
466 static function checkByHash($file_path)
470 if (!preg_match(
"~/bitrix/(?:modules|components|wizards|templates/bitrix24|blocks/bitrix|js)/~", $file_path))
475 $doc_root = static::detectDocRoot($file_path);
485 if (preg_match(
"~bitrix/modules/(.+?)/(.+)~", $file_path,
$matches))
490 elseif (preg_match(
"~bitrix/js/(.+?)/(.+)~", $file_path,
$matches))
492 $file =
'install/js/' .
$matches[1] .
'/' .$matches[2];
495 elseif(preg_match(
"~/bitrix(/components/bitrix/mobile\.tasks\..+)~", $file_path,
$matches))
498 $module =
'tasksmobile';
502 $len = strlen($doc_root .
'/bitrix');
503 $file =
'install' . substr($file_path, $len);
504 preg_match(
'~^install/.+?/.+?/.+?/~', $file,
$key);
507 if (!isset(
$map[$doc_root]))
509 $map[$doc_root] = [];
511 foreach(glob(
"{$doc_root}/bitrix/modules/*") as
$dir){
516 $len = strlen(
$dir) + 1;
518 foreach ([
'components',
'wizards',
'blocks',
'templates'] as $sub)
520 if (!is_dir(
"{$dir}/install/{$sub}"))
525 foreach(glob(
"{$dir}/install/{$sub}/*/*") as $c)
527 $c = substr($c, $len);
535 if (isset(
$map[$doc_root]) && isset(
$map[$doc_root][
$key]))
541 if (!$module || !$file)
546 $version = static::getVersion($module, $doc_root);
552 $hashes = static::getHashes($module, $version);
554 if ($hashes && isset($hashes[$file]))
556 return $hashes[$file] === md5_file($file_path);
562 function CheckFile($file_path)
570 $me = realpath(__FILE__);
572 if (realpath($file_path) == $me)
577 if (in_array($file_path, $this->errors))
582 if ($this->SystemFile($file_path))
588 if (basename($file_path) ==
'.htaccess')
590 $src = file_get_contents($file_path);
591 $res = preg_match(
'#<(\?|script)#i', $src, $regs);
594 $this->addResult(
'[100] htaccess', $regs[0], 1);
598 $res = preg_match(
'#\bwp-[a-z]+\.php#i', $src, $regs);
601 $this->addResult(
'[100] htaccess', $regs[0], 1);
605 if (stripos($src,
'(index.php|cache.php)') !==
false)
607 $this->addResult(
'[100] htaccess',
'(index.php|cache.php)', 1);
611 if (preg_match_all(
'#x-httpd-php[578]?\s+(.+)#i', $src, $regs))
613 foreach ($regs[1] as
$i =>
$val)
616 foreach (
$val as $ext)
618 $ext = trim(strtolower($ext),
'"\'');
619 if (!in_array($ext, [
'.php',
'.php5',
'.php7',
'.html',
'']))
621 $this->addResult(
'[100] htaccess', $regs[0][
$i], 1);
632 if (preg_match(
'#^/upload/.*\.php$#i', str_replace($this->doc_root,
'', $file_path)))
634 $this->addResult(
'[110] php file in upload dir',
'', 1);
638 $hash_check = static::checkByHash($file_path);
640 if ($hash_check ===
true)
644 elseif ($hash_check ===
false)
646 $this->addResult(
'[201] file was modified',
'', 1);
649 if (!preg_match(
'#\.php[578]?$#i', $file_path, $regs))
651 return !empty($this->results);
655 if (($src = @file_get_contents($file_path)) ===
false)
657 $this->addResult(
'[200] read error',
'', 1);
661 $this->CheckCodeInternal($src, $file_path);
664 foreach ($this->results as $value)
666 $tot = $tot * (1 -
$value[
'score']);
669 $tot = round(1 - $tot, 2);
673 return !empty($this->results);
676 function CalcChecksum($file_path,
$code, $subj)
679 $doc_root = static::detectDocRoot($file_path);
683 $file_path = substr($file_path, strlen($doc_root));
686 if (strpos($file_path,
'/') !== 0)
688 $file_path =
'/' . $file_path;
691 $file_path = preg_replace(
'#^/bitrix/modules/[a-z0-9._]+/install/components/bitrix#',
'/bitrix/components/bitrix', $file_path);
692 $checksum = md5($file_path .
'|' . trim(
$code) .
'|' . $subj);
697 function IsFalsePositive($checksum)
699 return in_array($checksum, self::false_positives,
true);
704 return $this->results;
712 function setErrors(
$val)
714 $this->errors =
$val;
727 function CheckCode(&$src, $file_path =
false)
731 return $this->CheckCodeInternal($src, $file_path);
734 private function load_db(){
736 if (self::$database || $this->db_error)
741 $cache = \Bitrix\Main\Data\Cache::createInstance();
743 if ($cache && $cache->initCache(12 * 3600,
'xscan_db',
'xscan'))
749 $http = new \Bitrix\Main\Web\HttpClient();
750 $data = $http->get(
"https://wwall.bitrix.info/database.gz");
751 $result = $http->getStatus() == 200 &&
$data ? json_decode(gzuncompress(
$data),
true) :
'error';
753 $cache->startDataCache();
763 elseif (is_file($this->db_file))
765 $tmp = file_get_contents($this->db_file);
766 self::$database = json_decode(
$tmp,
true);
771 $this->db_error =
true;
776 private function CheckCodeInternal(&$src, $file_path =
false)
778 $file_path = $file_path ? $file_path :
'';
782 $code = preg_replace(
"/<\?=/",
"<?php echo ", $src);
783 $code = preg_replace(
"/<\?(?!php)/",
"<?php ",
$code);
784 $code = preg_replace(
"/else if\s*\(/",
"elseif (",
$code);
790 ($cmt =
'$$') && substr_count(
$code,
'${${') > 0 ||
791 ($cmt =
'vars') && preg_match_all(
'/(?:\$|function\s+)(?:[o0]{4,}|[il]{4,})/i',
$code) > 3 ||
792 ($cmt =
'goto') && preg_match_all(
'/goto\s+[0-9A-Z]+\s*;/i',
$code) > 2 ||
793 ($cmt =
'globals') && preg_match_all(
'/\$GLOBALS\s*\[["\'][0-9_]+["\']\]/',
$code) > 3 ||
794 ($cmt =
'base64') && preg_match_all(
"/base64_decode\s*\(\s*[^$].{3,14}\)/i",
$code) > 3 ||
795 ($cmt =
'base64') && preg_match_all(
"~(?:base64_decode\s*\(\s*)(?:(['\"])[0-9a-z=+\/_]{1,20}\\1(?:\s*\.\s*)?){2,}~i",
$code) > 3 ||
796 ($cmt =
'functions') && preg_match_all(
"/function\s+_\w{1,3}\b/i",
$code) > 3 ||
797 ($cmt =
'concat') && preg_match_all(
"~(?:(?:(['\"])[0-9a-z=+\/_]{1,20}\\1|\\$?[0-9a-z_]+\(\))(?:\s*\.\s*)?){3,}~i",
$code) > 20 ||
800 ($cmt =
'urlenc') && preg_match_all(
'/(%[0-9A-Z]{2}){80,100}/i',
$code) > 2 ||
801 ($cmt =
'base64_keys') && substr_count(
$code,
"[base64") > 1 ||
802 ($cmt =
'unicode') && preg_match_all(
'~[\p{So}\p{Sk}\p{Nl}]{4,}~u',
$code) > 2
807 $this->tags[] = defined(
'XSCAN_DEBUG') ?
"obfuscator [$cmt]" :
"obfuscator";
808 if (in_array($cmt, [
'$$',
'vars',
'goto',
'globals',
'base64',
'functions',
'concat',
'unicode']))
810 $this->addResult(
'[001] obfuscator',
'', 0.6);
816 preg_match_all(
'~/\*(.+?)\*/~',
$code, $comments);
818 $comments = $comments ? $comments[1] : [];
820 $comments = array_unique($comments);
825 if (strlen(
$comment) <= 15 && preg_match(
'~([A-Za-z\s_]++|[0-9]++|[!@#$%^&():;`<>?,.{}|\~[\]+-=?]){4,}~',
$comment))
830 $this->tags[] = defined(
'XSCAN_DEBUG') ?
"obfuscator [comments]" :
"obfuscator";
831 $this->addResult(
'[001] obfuscator',
'', 0.6);
842 preg_match_all(
'/(?:\$|function\s+)([0-9a-z_]++)/i',
$code, $funcsVars);
844 $funcsVars = $funcsVars ? $funcsVars[1] : [];
845 $funcsVars = array_unique($funcsVars);
847 foreach ($funcsVars as $value)
849 $value = str_replace(
'24',
'', $value);
850 if (preg_match(
'/\d/', $value) && preg_match(
'/_\d|(?:[a-z_]++|[A-Z_]++|[0-9]++){4,}/', $value))
856 if (
count($funcsVars) && $cnt /
count($funcsVars) > 0.5)
858 $this->tags[] = defined(
'XSCAN_DEBUG') ?
"obfuscator [rand_names]" :
"obfuscator";
864 if (strpos($file_path,
'/bitrix/modules/main/') !==
false)
866 $this->tags[] =
'core';
872 $included = get_included_files();
875 if (in_array($file_path, $included) || in_array(realpath($file_path), $included)){
876 $this->tags[] =
"included";
879 if (preg_match(
'~/bitrix/(?:modules|components)/[0-9a-z_]+\.[0-9a-z_]+/~i', $file_path) ||
880 preg_match(
'~/bitrix/components/(?!bitrix/)~i', $file_path))
882 $this->tags[] =
'marketplace';
885 if (preg_match(
'/(?:[a-z_]++|[0-9]++){4,}/i', $file_path))
887 $this->tags[] =
'random_name';
890 if (preg_match(
'~/lang/~i', $file_path))
892 $this->tags[] =
'lang';
895 if (preg_match(
'~/\.~i', $file_path))
897 $this->tags[] =
'hidden';
900 if (strpos($file_path,
'/bitrix/modules/') ===
false &&
901 strpos($file_path,
'/upload/') ===
false &&
902 strpos($file_path,
'/bitrix/php_interface/') ===
false &&
903 strpos($src,
'B_PROLOG_INCLUDED') ===
false &&
904 strpos($src,
'/bitrix/header.php') ===
false &&
905 strpos($src,
'/bitrix/modules/main/start.php') ===
false &&
906 strpos($src,
'/bitrix/modules/main/include/prolog') ===
false &&
907 strpos($src,
'/bitrix/modules/main/include/mainpage.php') ===
false &&
908 strpos($src,
'/bitrix/main/include/routing_index.php') ===
false
911 $this->tags[] =
'no_prolog';
913 if (preg_match(
'/copy\s*\(|file_put_contents|move_uploaded_file|fwrite|fputs/i', $src, $m))
915 $subj =
'[308] no prolog file operations';
916 $checksum = $this->CalcChecksum($file_path, $m[0], $subj);
917 if (!$this->IsFalsePositive($checksum))
919 $this->addResult($subj, $m[0], self::CalcCrit($subj), $checksum);
924 $parser = $this->parser;
925 $errorHandler = $this->errorHandler;
926 $pprinter = $this->pprinter;
928 $errorHandler->clearErrors();
932 $stmts = $parser->parse(
$code, $errorHandler);
935 if (!$stmts && $errorHandler->getErrors())
937 throw new Exception(
'syntax error in file');
940 $this->CheckStmts($stmts,
$params, $file_path);
945 if ($this->collect_exceptions)
947 $this->addResult(
'[000] syntax error in file',
'', 1);
953 $src = preg_replace(
'#/\*.*?\*/#s',
'', $src);
954 $src = preg_replace(
'#[\r\n][ \t]*//.*#m',
'', $src);
955 $src = preg_replace(
'/[\r\n][ \t]*#.*/m',
'', $src);
958 if (self::$database && $this->SearchInDataBase($src))
960 $this->addResult(
'[007] looks like a well-known shell',
'', 1);
965 if (preg_match_all(
'#preg_replace' . self::$spaces .
'(\(((?>[^()]+)|(?-2))*\))#i', $src, $regs))
967 foreach ($regs[1] as
$i =>
$val)
971 $spl = $spiltter ===
'#' ?
'~' :
'#';
972 if (preg_match($spl . preg_quote($spiltter) .
'[imsxADSUXju]*e[imsxADSUXju]*[\'"]' . $spl,
$val))
974 $subj =
'[302] preg_replace_eval';
975 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
976 if (!$this->IsFalsePositive($checksum))
978 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
984 $content = preg_replace(
'/[\'"]\s*?\.\s*?[\'"]/smi',
'', $src);
987 if (preg_match_all(
'#[A-Za-z0-9+/]{20,}=*#i',
$content, $regs))
989 foreach ($regs[0] as
$val)
993 if (preg_match(
'#(' . self::$request .
'|' . self::$functions .
'|' . self::$evals_reg .
'|' . self::$black_reg .
')#i',
$val))
995 $subj =
'[321] base64_encoded code';
996 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
997 if (!$this->IsFalsePositive($checksum))
999 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1007 if (preg_match_all(
'#' . self::$black_reg .
'#i',
$content, $regs))
1009 $code = implode(
' | ', $regs[0]);
1011 $subj =
'[337] strings from black list';
1012 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1013 if (!$this->IsFalsePositive($checksum))
1015 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1038 if (preg_match_all(
'#[\'"](php://filter|phar://)#i',
$content, $regs))
1040 foreach ($regs[0] as
$i => $value)
1043 $subj =
'[500] php wrapper';
1044 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1045 if (!$this->IsFalsePositive($checksum))
1047 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1053 if (preg_match(
'#[a-z0-9+=/\n\r]{255,}#im', $src, $regs))
1056 if (!preg_match(
'#data:image/[^;]+;base64,[a-z0-9+=/]{255,}#i', $src, $regs))
1058 $subj =
'[630] long line';
1059 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1060 if (!$this->IsFalsePositive($checksum))
1062 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1068 if (preg_match_all(
'#exif_read_data\(#i', $src, $regs))
1070 foreach ($regs[0] as
$i => $value)
1073 $subj =
'[640] strange exif';
1074 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1075 if (!$this->IsFalsePositive($checksum))
1077 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1083 if (preg_match(
"#^.*([\x01-\x08\x0b\x0c\x0f-\x1f])#m", $src, $regs))
1086 if (!preg_match(
'#^\$ser_content = #', $regs[0]))
1088 $subj =
'[663] binary data';
1089 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1090 if (!$this->IsFalsePositive($checksum))
1092 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1098 if ($file_path && preg_match_all(
'#(?:\\\\x[a-f0-9]{2}|\\\\[0-9]{2,3})+#i',
$content, $regs))
1101 $all = implode(
"", $regs);
1102 if (
count($regs) > 1)
1108 foreach ($regs as
$code)
1111 if (preg_match(
'#(' . self::$request .
'|' . self::$functions .
'|' . self::$evals_reg .
'|' . self::$black_reg .
')#i',
$val))
1113 $subj =
'[665] encoded code';
1114 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1115 if (!$this->IsFalsePositive($checksum))
1117 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1121 elseif (preg_match_all(
'#[A-Za-z0-9+/]{20,}=*#i',
$val, $regs))
1123 foreach ($regs[0] as
$val)
1126 if (preg_match(
'#(' . self::$request .
'|' . self::$functions .
'|' . self::$evals_reg .
'|' . self::$black_reg .
')#i',
$val))
1128 $subj =
'[665] encoded code';
1129 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1130 if (!$this->IsFalsePositive($checksum))
1132 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1140 if (!$found && strlen($all) / filesize($file_path) > 0.1)
1142 $subj =
'[665] chars by code';
1143 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
1144 if (!$this->IsFalsePositive($checksum))
1146 $this->addResult($subj,
$code, self::CalcCrit($subj), $checksum);
1154 return !empty($this->results);
1157 function CheckStmts($stmts, &
$params, $file_path, $in_closure =
false)
1159 $nodeFinder = $this->nodeFinder;
1160 $pprinter = $this->pprinter;
1162 $nodeFinder->find($stmts,
function (Node $node) use (&$file_path) {
1163 if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod)
1165 $this->CheckStmts($node->stmts, $node->params, $file_path);
1168 elseif ($node instanceof Node\Expr\Closure)
1170 $this->CheckStmts($node->stmts, $node->params, $file_path,
true);
1173 elseif ($node instanceof Node\Expr\ArrowFunction)
1175 $this->CheckStmts($node->expr, $node->params, $file_path,
true);
1180 $nodes = [
'assigns' => [],
'variables' => [],
'params' => [],
'foreaches' => [],
'calls' => [],
'evals' => [],
1181 'backticks' => [],
'includes' => [],
'auth' => [],
'mtds' => [],
'strings' => []];
1184 $nodeFinder->find($stmts,
function (Node $node) use (&$nodes, &$pprinter, &$extract)
1186 if ($node->getComments())
1188 $node->setAttribute(
'comments', []);
1197 if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignOp)
1199 $nodes[
'assigns'][] = $node;
1201 if ($node instanceof Node\Expr\Variable)
1203 $nodes[
'variables'][] = $node;
1205 if ($node instanceof Node\Stmt\Foreach_)
1207 $nodes[
'foreaches'][] = $node;
1209 if ($node instanceof Node\Expr\FuncCall)
1211 $nodes[
'calls'][] = $node;
1212 if ($node->name instanceof Node\Name && $node->name->toLowerString() ===
'extract')
1214 if (
count($node->args) < 2 || $pprinter->prettyPrintExpr($node->args[1]->value) !==
'EXTR_SKIP')
1220 if ($node instanceof Node\Expr\Eval_)
1222 $nodes[
'evals'][] = $node;
1224 if ($node instanceof Node\Expr\ShellExec)
1226 $nodes[
'backticks'][] = $node;
1228 if ($node instanceof Node\Expr\Include_)
1230 $nodes[
'includes'][] = $node;
1232 if ($node instanceof Node\Expr\MethodCall && $node->name instanceof Node\Identifier && $node->name->toLowerString() ==
'authorize'
1233 && preg_match(
'/user|globals/i', $pprinter->prettyPrintExpr($node->var)))
1235 $nodes[
'auth'][] = $node;
1238 if ($node instanceof Node\Expr\MethodCall && !($node->name instanceof Node\Identifier)
1239 && preg_match(
'/^\$(user|globals)/i', $pprinter->prettyPrintExpr($node->var)))
1241 $nodes[
'auth'][] = $node;
1244 if ($node instanceof Node\Expr\MethodCall && $node->name instanceof Node\Expr\Variable &&
1245 $node->var instanceof Node\Expr\ArrayDimFetch &&
1246 $node->var->var instanceof Node\Expr\Variable && $node->var->var->name ==
'GLOBALS' &&
1247 (!($node->var->dim instanceof Node\Scalar\String_) || $node->var->dim->value ==
'USER')
1250 $nodes[
'auth'][] = $node;
1253 if ($node instanceof Node\Expr\StaticCall)
1255 $nodes[
'mtds'][] = $node;
1258 if ($node instanceof Node\Scalar\String_ && $node->value)
1260 $nodes[
'strings'][] = $node;
1263 # this is dirty hack
1264 if ($node instanceof Node\Expr\ArrayDimFetch && $node->var instanceof Node\Expr\Variable && $node->var->name ==
'_SERVER')
1266 $dim = $node->dim instanceof Node\Scalar\String_ ? $node->dim->value :
"qwerty";
1267 if (!preg_match(
'/^(?:DOCUMENT_ROOT|SERVER_ADDR|REMOTE_ADDR|SERVER_NAME|HTTPS|SERVER_PORT|REMOTE_PORT)$/', $dim))
1269 $node->var->name =
'_REQUEST';
1284 'request' => [
'_GET' =>
true,
'_POST' =>
true,
'_REQUEST' =>
true,
'_COOKIE' =>
true,
'_FILES' =>
true],
1286 'from_request' => [],
1288 'assigned' => [
'_GET' =>
true,
'_POST' =>
true,
'_REQUEST' =>
true,
'_COOKIE' =>
true,
'_SESSION' =>
true,
1289 '_SERVER' =>
true,
'_FILES' =>
true,
'this' =>
true,
'USER' =>
true,
'DB' =>
true,
'APPLICATION' =>
true],
1294 foreach ($nodes[
'variables'] as
$var)
1296 if (is_string(
$var->name))
1302 $var = $this->pprinter->prettyPrintExpr(
$var->name);
1305 $vars_names[] =
$var;
1307 $vars_names = array_unique($vars_names);
1311 $n = substr($this->pprinter->prettyPrintExpr($param->var), 1);
1312 $vars[
'params'][
$n] =
true;
1313 $vars[
'assigned'][
$n] =
true;
1315 if ($param->type instanceof Node\Name\FullyQualified && implode(
'', $param->type->parts) ==
'Closure')
1317 $vars[
'closures'][] =
$n;
1321 foreach ($nodes[
'assigns'] as $fnd)
1323 $n = substr($this->pprinter->prettyPrintExpr($fnd->var), 1);
1324 $vars[
'assigned'][
$n] =
true;
1325 if ($fnd->expr instanceof Node\Expr\Closure)
1327 $vars[
'closures'][] =
$n;
1331 foreach ($nodes[
'foreaches'] as $fnd)
1335 $n = substr($this->pprinter->prettyPrintExpr($fnd->keyVar), 1);
1336 $vars[
'assigned'][
$n] =
true;
1340 $n = substr($this->pprinter->prettyPrintExpr($fnd->valueVar), 1);
1341 $vars[
'assigned'][
$n] =
true;
1345 for ($_ = 0; $_ < 2; $_++)
1348 foreach ($nodes[
'assigns'] as $node)
1350 $flag = $nodeFinder->findFirst($node->expr,
1351 function (Node $node) use (&$vars) {
1352 return $node instanceof Node\Expr\Variable && is_string($node->name) && $node->name && (isset($vars[
'request'][$node->name]) || isset($vars[
'from_request'][$node->name]));
1362 foreach (
$res as $fnd)
1364 $n = substr($this->pprinter->prettyPrintExpr($fnd->var), 1);
1365 $vars[
'from_request'][
$n] =
true;
1369 for ($_ = 0; $_ < 1; $_++)
1372 foreach ($nodes[
'assigns'] as $node)
1374 $flag = $nodeFinder->findFirst($node->expr,
1375 function (Node $node) {
1376 return $node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name &&
1377 in_array($node->name->toLowerString(), self::$cryptors,
true);
1386 foreach (
$res as $fnd)
1388 $n = substr($this->pprinter->prettyPrintExpr($fnd->var), 1);
1389 $vars[
'crypted'][
$n] =
true;
1393 foreach ($nodes[
'assigns'] as $fnd)
1395 $n = substr($this->pprinter->prettyPrintExpr($fnd->var), 1);
1396 $tmp = $this->parseValue($fnd->expr, $vars);
1397 if (!isset($vars[
'values'][
$n]))
1399 $vars[
'values'][
$n] =
$tmp;
1403 $vars[
'values'][
$n] .=
'|' .
$tmp;
1409 $crypto_vars = array_keys($vars[
'crypted']);
1414 $config = self::genConfig([
'params' =>
true,
'assigned' => $extract]);
1415 foreach ($nodes[
'evals'] as $node)
1420 $node->setAttribute(
'comment',
$comment);
1425 $this->CheckResults(
$res,
'[300] eval', $file_path);
1430 'mysqli_connect' => [0, 1, 2, 3],
1431 'mysqli_query' => [1],
1432 'mysqli_real_query' => [1],
1433 # i know its removed at php7
1434 'mysql_connect' => [0, 1, 2],
1435 'mysql_query' => [1],
1436 'mysql_db_query' => [0, 1],
1439 $config = self::genConfig([
'params' =>
true]);
1440 $res = $this->checkFuncCalls($nodes[
'calls'], $sql_map, $nodeFinder, $vars,
$config);
1442 $this->CheckResults(
$res,
'[298] mysql function', $file_path);
1452 $res = $this->checkFuncCalls($nodes[
'calls'], $mail_map, $nodeFinder, $vars,
$config);
1454 $this->CheckResults(
$res,
'[299] mail function', $file_path);
1460 'create_function' => [0],
1463 'pcntl_exec' => [0],
1466 'set_include_path' => [0],
1467 'shell_exec' => [0],
1471 $config = self::genConfig([
'params' =>
true,
'assigned' => $extract]);
1472 $res = $this->checkFuncCalls($nodes[
'calls'], $evals_map, $nodeFinder, $vars,
$config);
1474 $this->CheckResults(
$res,
'[300] command injection', $file_path);
1480 'file_get_contents' => [0],
1481 'file_put_contents' => [0],
1482 'move_uploaded_file' => [1],
1487 $config = self::genConfig([
'concat' =>
false,
'files' =>
false,
'value' =>
false]);
1488 $res = $this->checkFuncCalls($nodes[
'calls'], $files_map, $nodeFinder, $vars,
$config);
1490 $this->CheckResults(
$res,
'[301] file operations', $file_path);
1508 $f_w_clb_map = [
'call_user_func' => [0],
1509 'call_user_func_array' => [0],
1510 'forward_static_call' => [0],
1511 'forward_static_call_array' => [0],
1512 'register_shutdown_function' => [0],
1513 'register_tick_function' => [0],
1518 'array_walk' => [1],
1519 'array_walk_recursive' => [1],
1520 'array_reduce' => [1],
1521 'array_intersect_ukey' => [2],
1522 'array_uintersect' => [2],
1523 'array_uintersect_assoc' => [2],
1524 'array_intersect_uassoc' => [2],
1525 'array_uintersect_uassoc' => [2, 3],
1526 'array_diff_ukey' => [2],
1527 'array_udiff' => [2],
1528 'array_udiff_assoc' => [2],
1529 'array_diff_uassoc' => [2],
1530 'array_udiff_uassoc' => [2, 3],
1531 'array_filter' => [1],
1533 'mb_ereg_replace_callback' => [1]
1536 $config = self::genConfig([
'assigned' => $extract]);
1537 $res = $this->checkFuncCalls($nodes[
'calls'], $f_w_clb_map, $nodeFinder, $vars,
$config);
1539 $this->CheckResults(
$res,
'[302] unsafe callable argument', $file_path);
1543 $some_calls = array_filter($nodes[
'calls'],
function (Node $node) use (&$danger) {
1544 return $node->name instanceof Node\Name && $node->name->toLowerString() ==
'create_function';
1549 foreach ($some_calls as $node)
1551 $flag = $nodeFinder->findFirst($node->args,
1552 function (Node $node) {
1553 return ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && (!function_exists($node->name->toLowerString())
1554 || in_array($node->name->toLowerString(), self::$cryptors,
true)
1555 || in_array($node->name->toLowerString(), self::$string_change,
true))
1557 ($node instanceof Node\Scalar\String_ && preg_match(
'/(?:assert|' . implode(
'|', self::$cryptors) .
')/i', $node->value));
1567 $this->CheckResults(
$res,
'[303] create_function', $file_path);
1571 $clb = [
'filter_input',
'filter_input_array',
'filter_var',
'filter_var_array'];
1573 $some_calls = array_filter($nodes[
'calls'],
function (Node $node) use (&$clb) {
1574 return $node->name instanceof Node\Name && in_array($node->name->toLowerString(), $clb,
true);
1579 foreach ($some_calls as $node)
1581 if (preg_match_all(
'#(?:_POST|_GET|_COOKIE|_REQUEST|FILTER_CALLBACK|1024|filter_input|filter_var)|' . self::$evals_reg .
'|' . self::$functions .
'#i', $pprinter->prettyPrint($node->args)) > 1)
1587 $this->CheckResults(
$res,
'[304] filter_callback', $file_path);
1592 foreach ($nodes[
'evals'] as $node)
1594 $flag = $nodeFinder->findFirst($node->expr,
1595 function (Node $node) {
1596 return ($node instanceof Node\Expr\FuncCall && (
1597 ($node->name instanceof Node\Name && !function_exists($node->name->toLowerString())) ||
1598 ($node->name instanceof Node\Expr\Variable) ||
1599 ($node->name instanceof Node\Expr\ArrayDimFetch)
1607 $node->setAttribute(
'comment',
'strange code');
1612 $this->CheckResults(
$res,
'[305] strange function and eval', $file_path);
1617 'mb_eregi_replace' => [1],
1618 'mb_ereg_replace' => [1],
1622 $res = $this->checkFuncCalls($nodes[
'calls'], $eregi_map, $nodeFinder, $vars,
$config);
1624 $this->CheckResults(
$res,
'[302] eregi', $file_path);
1629 foreach ($nodes[
'mtds'] as $node)
1631 $class = $node->class instanceof Node\Name ? $node->class->toString() :
'';
1632 $mtd = $node->name instanceof Node\Identifier ? $node->name->toString() :
'';
1634 if (!$class || !$mtd)
1639 $class_method =
"$class::$mtd";
1640 foreach (self::$mehtods as $mtd)
1642 if (stripos($class_method, $mtd) !==
false)
1644 $arg = isset($node->args[0]) ? $node->args[0] :
false;
1650 $node->setAttribute(
'comment',
$comment);
1657 $this->CheckResults(
$res,
'[307] danger method', $file_path);
1661 $config = self::genConfig([
'hardcoded' =>
true]);
1664 foreach ($nodes[
'auth'] as $node)
1666 $arg = isset($node->args[0]) ? $node->args[0] :
false;
1677 $node->setAttribute(
'comment',
$comment);
1681 elseif ($node->var instanceof Node\Expr\ArrayDimFetch){
1686 $this->CheckResults(
$res,
'[400] bitrix auth', $file_path);
1691 $config = self::genConfig([
'concat' =>
false,
'value' =>
false]);
1693 foreach ($nodes[
'includes'] as $node)
1698 $inc = $pprinter->prettyPrintExpr($node->expr);
1700 if (preg_match(
'/\.(gif|png|jpg|jpeg|var|pdf|exe)/i', $inc))
1703 $comment =
'gif|png|jpg|jpeg|var|pdf|exe';
1705 elseif (preg_match(
'#(https?|ftps?|compress\.zlib|php|glob|data|phar|zip)://#i', $inc))
1722 $node->setAttribute(
'comment',
$comment);
1727 $this->CheckResults(
$res,
'[600] strange include', $file_path);
1732 foreach ($nodes[
'variables'] as
$var)
1734 $v = $this->pprinter->prettyPrintExpr(
$var);
1735 if (in_array($v, $checked,
true))
1740 if (
$var->name instanceof Node\Expr\BinaryOp || preg_match(
'#\$_{3,}#i', $v) || preg_match(
'#\$\{.*?(?:->|::|\()#i', $v))
1742 $subj =
'[610] strange vars';
1743 $checksum = $this->CalcChecksum($file_path, $v, $subj);
1744 if (!$this->IsFalsePositive($checksum))
1746 $this->addResult($subj, $v, self::CalcCrit($subj), $checksum);
1750 if (preg_match(
'#\${["\']\\\\x[0-9]{2}[a-z0-9\\\\]+["\']}#i', $v))
1752 $subj =
'[615] hidden vars';
1753 $checksum = $this->CalcChecksum($file_path, $v, $subj);
1754 if (!$this->IsFalsePositive($checksum))
1756 $this->addResult($subj, $v, self::CalcCrit($subj), $checksum);
1760 if (preg_match(
"#\$(?:[\x80-\xff][_\x80-\xff]*|_(?:[\x80-\xff][_\x80-\xff]*|_[_\x80-\xff]+))" . self::$spaces .
'=#i', $v))
1762 $subj =
'[620] binary vars';
1763 $checksum = $this->CalcChecksum($file_path, $v, $subj);
1764 if (!$this->IsFalsePositive($checksum))
1766 $this->addResult($subj, $v, self::CalcCrit($subj), $checksum);
1776 foreach ($nodes[
'calls'] as $node)
1780 if ($node->name instanceof Node\Expr\Variable)
1782 $var = is_string($node->name) ?
'$' . $node->name : $this->pprinter->prettyPrintExpr($node->name);
1788 $flag = $nodeFinder->findFirst($node->args,
1789 function (Node $node) {
1790 return $node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Expr\Variable;
1801 foreach ($node->args as $arg)
1803 if ($arg instanceof Node\Arg)
1814 if (!$flag && !in_array(
$name, $vars[
'closures'],
true) && !$in_closure)
1817 if (self::isVarStrange(
$var))
1826 $node->setAttribute(
'comment',
$comment);
1832 $this->CheckResults(
$res,
'[650] variable as a function', $file_path);
1839 foreach ($nodes[
'calls'] as $node)
1841 if ($node->name instanceof Node\Expr\ArrayDimFetch)
1849 foreach ($node->args as $arg)
1861 $node->setAttribute(
'comment',
$comment);
1866 $this->CheckResults(
$res,
'[660] array member as a function', $file_path);
1873 foreach ($nodes[
'calls'] as $node)
1875 if ($node->name instanceof Node\Expr\FuncCall)
1882 $node->setAttribute(
'comment',
$comment);
1887 $this->CheckResults(
$res,
'[662] function return as a function', $file_path);
1890 foreach ($nodes[
'calls'] as $node)
1892 if ($node->name instanceof Node\Scalar\String_ || $node->name instanceof Node\Expr\BinaryOp)
1899 $node->setAttribute(
'comment',
$comment);
1904 $this->CheckResults(
$res,
'[663] strange function', $file_path);
1908 foreach ($nodes[
'strings'] as $node)
1910 $str = $node->value;
1912 $str2 = base64_decode(
$str);
1913 if ($str2 && preg_match(
'#(' . self::$request .
'|' . self::$functions .
'|' . self::$evals_reg .
'|' . self::$black_reg .
')#i', $str2))
1919 if (preg_match(
'/^[a-z\s0-9:._-]+$/i',
$str))
1924 if (strpos(
$str,
'<?') !==
false && preg_match(
'#(' . self::$request .
'|' . self::$functions .
'|' . self::$evals_reg .
'|' . self::$black_reg .
')#i',
$str))
1926 $subscan =
new CBitrixXscan();
1927 $subscan->collect_exceptions =
false;
1929 if ($subscan->CheckCode(
$str))
1933 unset(
$str, $str2, $subscan);
1937 $this->CheckResults(
$res,
'[665] encoded code', $file_path);
1941 $this->CheckResults($nodes[
'backticks'],
'[887] backticks', $file_path);
1943 unset($stmts, $nodes, $some_calls,
$res, $req,
$code);
1946 public static function genConfig(
$options =
false)
1948 $config = self::$default_config;
1960 public function checkFuncCalls(&$all_calls, &$funcs_map, &$nodeFinder, &$vars, &
$config)
1962 $funcs = array_keys($funcs_map);
1964 $some_calls = array_filter($all_calls,
function (Node $node) use (&$funcs) {
1965 return $node->name instanceof Node\Name && in_array($node->name->toLowerString(), $funcs,
true);
1969 foreach ($some_calls as $node)
1972 $func = $node->name->toLowerString();
1976 foreach ($funcs_map[$func] as
$i)
1978 if (!isset($node->args[
$i]) || $ret)
1983 if ($node->args[
$i] instanceof Node\Arg && $node->args[
$i]->value instanceof Node\Expr\Closure)
1988 $arg = $node->args[
$i]->value;
1995 $node->setAttribute(
'comment',
$comment);
2002 public function parseValue($node, &$vars)
2007 while ($node instanceof Node\Expr\ArrayDimFetch || $node instanceof Node\Expr\PropertyFetch)
2009 if ($node instanceof Node\Expr\ArrayDimFetch && $node->dim instanceof Node\Scalar\String_ and $node->dim->value ==
'tmp_name')
2013 if ($node instanceof Node\Expr\ArrayDimFetch && $node->var instanceof Node\Expr\Variable && $node->var->name ==
'GLOBALS'
2014 && $node->dim instanceof Node\Scalar\String_)
2016 $node =
new Node\Expr\Variable($node->dim->value);
2024 if ($node instanceof Node\Expr\Variable)
2026 $name = $node->name;
2029 if (isset($vars[
'values'][
$name]))
2031 $ret = $vars[
'values'][
$name];
2033 elseif (isset($vars[
'request'][
$name]) && !$temp_name)
2039 $ret =
'$_FROM_REQUEST';
2052 $ret =
$name = $this->parseValue(
$name, $vars);
2055 elseif ($node instanceof Node\Expr\BinaryOp)
2057 $left = $this->parseValue($node->left, $vars);
2058 $right = $this->parseValue($node->right, $vars);
2060 if ($node instanceof Node\Expr\BinaryOp\Div && (
int)
$right != 0)
2062 $ret = (string)((
int)$left / (
int)
$right);
2064 elseif ($node instanceof Node\Expr\BinaryOp\Mul)
2066 $ret = (string)((
int)$left * (
int)
$right);
2068 elseif ($node instanceof Node\Expr\BinaryOp\Minus)
2070 $ret = (string)((
int)$left - (
int)
$right);
2072 elseif ($node instanceof Node\Expr\BinaryOp\Plus)
2074 $ret = (string)((
int)$left + (
int)
$right);
2076 elseif ($node instanceof Node\Expr\BinaryOp\BitwiseXor)
2078 $ret = (string)($left ^
$right);
2085 elseif ($node instanceof Node\Scalar\Encapsed)
2087 foreach ($node->parts as $part)
2089 $part = $this->parseValue($part, $vars);
2093 elseif ($node instanceof Node\Scalar\LNumber ||
2094 $node instanceof Node\Scalar\DNumber ||
2095 $node instanceof Node\Scalar\String_ ||
2096 $node instanceof Node\Scalar\EncapsedStringPart
2099 $ret = (string)$node->value;
2101 elseif ($node instanceof Node\Expr\FuncCall)
2103 $name = $node->name instanceof Node\Name ? $node->name->toLowerString() :
"\$v";
2105 if (
$name ===
'chr')
2107 $v = $this->parseValue($node->args[0]->value, $vars);
2108 $ret = chr((
int)$v);
2113 foreach ($node->args as $arg)
2115 if ($arg instanceof Node\Arg)
2117 $args[] = $this->parseValue($arg->value, $vars);
2121 $ret =
"$name(" . implode(
",", $args) .
")";
2123 [
$a, $b] = self::checkString($ret);
2124 $ret =
$a ? $b :
'';
2128 elseif ($node instanceof Node\Expr\Ternary)
2130 $if = $this->parseValue($node->if, $vars);
2131 $ret = $if ?: $this->parseValue($node->else, $vars);
2133 elseif ($node && property_exists($node,
'expr'))
2135 return $this->parseValue($node->expr, $vars);
2141 public function CheckArg($arg, &$vars, &
$config)
2147 while ($arg instanceof Node\Expr\ArrayDimFetch || $arg instanceof Node\Expr\PropertyFetch)
2149 if ($arg instanceof Node\Expr\ArrayDimFetch && $arg->dim instanceof Node\Scalar\String_ and $arg->dim->value ==
'tmp_name')
2154 if ($arg instanceof Node\Expr\ArrayDimFetch && $arg->var instanceof Node\Expr\Variable && $arg->var->name ==
'GLOBALS'
2155 && $arg->dim instanceof Node\Scalar\String_)
2157 $arg =
new Node\Expr\Variable($arg->dim->value);
2165 $temp_name = $temp_name && ($arg instanceof Node\Expr\Variable and is_string($arg->name) && $arg->name ==
'_FILES');
2167 while ($arg instanceof Node\Expr\ConstFetch)
2173 $arg instanceof Node\Scalar\LNumber ||
2174 $arg instanceof Node\Scalar\DNumber ||
2175 $arg instanceof Node\Scalar\String_ ||
2176 $arg instanceof Node\Scalar\Encapsed)
2182 elseif ($arg instanceof Node\Expr\Variable)
2185 if (!is_string(
$name))
2194 (
$config[
'from_request'] && isset($vars[
'from_request'][
$name]) &&
$comment =
'var from request') ||
2196 (
$config[
'assigned'] && !isset($vars[
'assigned'][
$name]) &&
$comment =
'var was not assigned') ||
2202 elseif ($arg instanceof Node\Expr\FuncCall && $arg->name instanceof Node\Name)
2204 $name = $arg->name->toLowerString();
2205 $ret = in_array(
$name, self::$evals,
true) || in_array(
$name, [
'getenv',
'debug_backtrace'],
true) || (
$config[
'crypted'] && in_array(
$name, self::$cryptors,
true));
2208 foreach ($arg->args as $argv)
2222 elseif ($arg instanceof Node\Scalar\String_)
2225 $ret = preg_match(
'/^(' . implode(
'|', self::$evals) .
'|call_user_func|getenv)$/i', $arg->value);
2227 elseif ($arg instanceof Node\Scalar\EncapsedStringPart)
2230 $ret = preg_match(
'/(' . implode(
'|', self::$evals) .
'|call_user_func|getenv)/i', $arg->value);
2232 elseif ($arg instanceof Node\Name)
2235 $func = $arg->toLowerString();
2236 $ret = preg_match(
'/(' . implode(
'|', self::$evals) .
'|call_user_func|getenv)/i', $func);
2238 elseif ($arg instanceof Node\Expr\BinaryOp\Concat || $arg instanceof Node\Expr\BinaryOp\Coalesce)
2240 [
$a, $b] = $this->CheckArg($arg->left, $vars,
$config);
2247 [
$a, $b] = $this->CheckArg($arg->right, $vars,
$config);
2254 $comment =
'strange concatination';
2259 elseif ($arg instanceof Node\Scalar\Encapsed)
2261 foreach ($arg->parts as $part)
2263 [
$a, $b] = $this->CheckArg($part, $vars,
$config);
2270 elseif ($arg instanceof Node\Expr\Ternary)
2272 [
$a, $b] = $this->CheckArg($arg->if, $vars,
$config);
2279 [
$a, $b] = $this->CheckArg($arg->else, $vars,
$config);
2289 if (!$ret &&
$config[
'value'])
2291 $val = $this->parseValue($arg, $vars);
2295 $subscan =
new CBitrixXscan();
2296 $subscan->collect_exceptions =
false;
2300 [$ret,
$comment] = [
true,
'recursive'];
2313 public static function checkString(
$val)
2318 if (preg_match(
'/BXS_(?:EVAL|CRYPTED|BLACKLIST|REQUEST)/',
$val, $m))
2323 elseif (preg_match(
'/\b(' . implode(
'|', self::$evals) .
'|getenv)\b/i',
$val))
2328 elseif (preg_match(
'/\b(' . implode(
'|', self::$cryptors) .
'|CRYPTED)\b/i',
$val))
2333 elseif (preg_match(
'#\b' . self::$black_reg .
'\b#i',
$val))
2338 elseif (preg_match(
'/(\$_REQUEST|\$_FROM_REQUEST)/i',
$val))
2346 public static function isVarStrange(
$var)
2349 $ret = preg_match(
'/^\$_?([0o]+|[1li]+)$/i',
$var);
2350 $ret = $ret || preg_match(
'/^\$__/i',
$var) ||
$var ==
'$_';
2351 $ret = $ret || preg_match(
'/__/',
$var);
2352 $ret = $ret || preg_match(
'/^\$_*[a-z0-9]{1,2}$/i',
$var);
2354 $ret = $ret || preg_match(
'/\d{2,}$/i',
$var);
2355 $ret = $ret || preg_match_all(
'/[A-Z][a-z][A-Z]/',
$var) > 1;
2357 $ret = $ret || preg_match(
'/[^$a-z0-9_]/i',
$var);
2358 $ret = $ret || preg_match(
'/[a-z]+[0-9]+[a-z]+/i',
$var);
2360 $ret = $ret || (preg_match_all(
'#[qwrtpsdfghjklzxcvbnm]{4,}#i',
$var, $regs)
2361 && (strlen(implode(
'', $regs[0])) / strlen(
$var) > 0.4));
2366 public static function CalcCrit($subj, $com =
'')
2368 if (!isset(self::$scoring[$subj]))
2370 die(
"error: " . $subj);
2375 'strange concatination' => 1,
2376 'hardcoded value' => 2,
2378 'danger function' => 4,
2379 'var from params' => 5,
2380 'var was not assigned' => 6,
2382 'var from request' => 8,
2385 $self = self::$scoring[$subj][0];
2387 if ($com ==
'other')
2393 $num = isset($nums[$com]) ? $nums[$com] : 0;
2394 $arg = isset(self::$scoring[$subj][$num]) ? self::$scoring[$subj][$num] : 1;
2397 return round($self * $arg, 2);
2400 public function CheckResults(&
$res, $subj, $file_path)
2402 foreach (
$res as $r)
2404 $code = $this->pprinter->prettyPrintExpr($r);
2406 $com = $r->getAttribute(
'comment',
'');
2407 $crit = self::CalcCrit($subj, $com);
2408 $checksum = $this->CalcChecksum($file_path,
$code, $subj);
2410 $str = defined(
'XSCAN_DEBUG') ?
"$subj [$com] | $crit | $checksum" : $subj;
2412 if (!$this->IsFalsePositive($checksum))
2414 $this->addResult(
$str,
$code, $crit, $checksum);
2419 public static function ParseNode(&$node)
2423 foreach ($node as $v)
2425 self::ParseNode($v);
2431 static function CountVars(
$str)
2433 $regular =
'#' . self::$var .
'#';
2434 if (!preg_match_all($regular,
$str, $regs))
2439 $ar0 = array_unique($ar0);
2440 $ar0 = array_filter($ar0,
function ($v) {
2441 return !in_array($v, [
'$_GET',
'$_POST',
'$_REQUEST',
'$_GET',
'$_SERVER',
'$_FILES',
'$APPLICATION',
'$DB',
'$USER']);
2447 static function StatVulnCheck(
$str, $bAll =
false)
2449 $regular = $bAll ?
'#\$?[a-z_]+#i' :
'#' . self::$var .
'#';
2450 if (!preg_match_all($regular,
$str, $regs))
2455 $ar1 = array_unique($ar0);
2459 foreach ($ar1 as
$var)
2461 if ($bAll && function_exists(
$var))
2465 elseif ($bAll && preg_match(
'#^[a-z]{1,2}$#i',
$var))
2469 elseif (preg_match(
'#^\$?(function|php|csv|sql|__DIR__|__FILE__|__LINE__|DBDebug|DBType|DBName|DBPassword|DBHost|APPLICATION)$#i',
$var))
2477 elseif (preg_match(
'#^\$(ar|str)[A-Z]#',
$var, $regs))
2481 elseif (preg_match_all(
'#([qwrtpsdfghjklzxcvbnm]{3,}|[a-z]+[0-9]+[a-z]+)#i',
$var, $regs))
2483 $p = strlen(implode(
'', $regs[0])) / strlen(
$var) > 0.3;
2492 $prob = array_sum($ar2) /
count($ar2);
2500 return self::StatVulnCheck(
$str,
true);
2506 function Search(
$path, $mode =
'search')
2511 $path = str_replace(
'//',
'/',
$path, $flag);
2515 if (php_sapi_name() !=
"cli")
2517 header(
'xscan-bp: ' .
$path,
true);
2520 if ($this->start_time && time() - $this->start_time > $this->time_limit)
2522 if ($mode ==
'search' && !$this->break_point)
2524 $this->break_point =
$path;
2526 if ($mode ==
'count')
2533 if ($mode ==
'search' && $this->skip_path && !$this->found)
2535 if (strpos($this->skip_path, dirname(
$path)) !== 0)
2540 if ($this->skip_path ==
$path)
2542 $this->found =
true;
2552 if ($this->base_dir && strpos(
$p .
'/', $this->base_dir .
'/') !==
false)
2557 $d = dirname(
$path);
2558 if (strpos(
$p, $d) !==
false || strpos($d,
$p) !==
false)
2565 $isbitrix = basename(
$path) ==
'bitrix' && is_file(
$path .
'/.settings.php');
2567 while ($item = readdir(
$dir))
2569 if ($item ==
'.' || $item ==
'..')
2574 if ($isbitrix && in_array($item, [
'cache',
'managed_cache',
'stack_cache',
'updates']))
2579 $this->Search(
$path .
'/' . $item, $mode);
2583 elseif (preg_match(
'/\.(?:htaccess|php[578]?|js)$/i',
$path))
2585 if ($mode ==
'count')
2591 if (!$this->skip_path || $this->found)
2593 $this->progress += 1;
2597 $this->pushResult(
$path);
2603 function SystemFile(
$f)
2606 '/bitrix/modules/controller/install/activities/bitrix/controllerremoteiblockactivity/controllerremoteiblockactivity.php',
2607 '/bitrix/activities/bitrix/controllerremoteiblockactivity/controllerremoteiblockactivity.php',
2608 '/bitrix/modules/main/classes/general/update_class.php',
2609 '/bitrix/modules/main/classes/general/file.php',
2610 '/bitrix/modules/imconnectorserver/lib/connectors/telegrambot/emojiruleset.php',
2611 '/bitrix/modules/imconnectorserver/lib/connectors/facebook/emojiruleset.php',
2612 '/bitrix/modules/main/include.php',
2613 '/bitrix/modules/main/classes/general/update_client.php',
2614 '/bitrix/modules/main/install/wizard/wizard.php',
2615 '/bitrix/modules/main/start.php',
2616 '/bitrix/modules/landing/lib/mutator.php',
2617 '/bitrix/modules/main/tools.php',
2618 '/bitrix/modules/main/lib/engine/response/redirect.php',
2619 '/bitrix/modules/main/lib/config/option.php',
2620 '/bitrix/modules/main/classes/general/main.php',
2621 '/bitrix/modules/main/lib/UpdateSystem/PortalInfo.php',
2622 '/bitrix/modules/main/lib/UpdateSystem/HashCodeParser.php',
2623 '/bitrix/modules/main/lib/UpdateSystem/ActivationSystem.php',
2624 '/bitrix/modules/main/lib/license.php',
2625 '/bitrix/modules/crm/classes/general/sql_helper.php',
2626 '/bitrix/modules/main/lib/security/w/wwall.php',
2627 '/bitrix/modules/main/lib/security/w/rules/intvalrule.php',
2628 '/bitrix/modules/main/lib/security/w/rules/pregmatchrule.php',
2629 '/bitrix/modules/main/lib/security/w/rules/pregreplacerule.php',
2630 '/bitrix/modules/main/lib/security/w/rules/rule.php'
2632 foreach ($system as
$path)
2634 if (preg_match(
'#' .
$path .
'$#',
$f))
2642 function pushResult(
$f)
2645 foreach ($this->results as
$res)
2658 if (is_array($stat))
2663 $result->setTags(implode(
' ', $this->tags));
2665 $this->result_collection[] =
$result;
2670 if (isset($this->result_collection) && $this->result_collection)
2672 $this->result_collection->save(
true);
2674 unset($this->result_collection);
2677 static function ShowMsg(
$str, $color =
'green')
2679 $class = $color ==
'green' ?
'ui-alert-primary ui-alert-icon-info' :
'ui-alert-danger ui-alert-icon-danger';
2680 return '<br><div class="ui-alert ' . $class .
'"><span class="ui-alert-message">' .
$str .
'</span></div><br>';
2683 static function HumanSize($s)
2686 $ar = [
'b',
'kb',
'M',
'G'];
2692 return round($s, 1) .
' ' .
$ar[
$i];
2695 static function getIsolateButton($file_path)
2698 return '<a class="ui-btn ui-btn-danger ui-btn-sm" style="text-decoration: none; color: #ffffff;" onclick="xscan_prison(\'' . $file_path .
'\')
">' . GetMessage("BITRIX_XSCAN_ISOLATE
") . '</a>';
2701 static function getUnIsolateButton($file_path)
2703 $file_path = htmlspecialcharsbx(CUtil::JSEscape($file_path));
2704 return '<a class="ui-btn ui-btn-primary ui-btn-sm
" style="text-decoration: none;
color: #ffffff;
" onclick="xscan_release(\
'' . $file_path .
'\')
">' . GetMessage("BITRIX_XSCAN_UNISOLATE
") . '</a>';
2707 static function getHideButton($file_path)
2709 $file_path = htmlspecialcharsbx(CUtil::JSEscape($file_path));
2710 return '<a class="ui-btn ui-btn-success ui-btn-sm
" style="text-decoration: none;
color: #ffffff;
" onclick="xscan_hide(\
'' . $file_path .
'\')
">' . GetMessage("BITRIX_XSCAN_HIDE_BTN
") . '</a>';
2713 static function getFileWatchLink($file_path)
2716 '<a href="?action=showfile&file=%s
">%s</a>',
2717 urlencode($file_path),
2718 htmlspecialcharsbx($file_path)
2722 static function getFileWatchButton($file_path)
2725 '<a class="ui-btn ui-btn-sm
" style="text-decoration: none;
color: #ffffff;
" target="_blank
" href="?action=showfile&file=%s
">' . GetMessage("BITRIX_XSCAN_WATCH_EVENT
") . '</a>',
2726 urlencode($file_path)
2730 static function getEventWatchLink($event, $table, $id)
2733 '<a target="_blank
" href="/bitrix/admin/perfmon_row_edit.php?table_name=%s&pk[ID]=%d
">%s</a>',
2736 htmlspecialcharsbx($event)
2740 static function getEventWatchButton($table, $id)
2743 '<a class="ui-btn ui-btn-sm
" target="_blank
" style="text-decoration: none;
color: #ffffff;
" href="/bitrix/admin/perfmon_row_edit.php?table_name=%s&pk[ID]=%d
">' . GetMessage("BITRIX_XSCAN_WATCH_EVENT
") . '</a>',
2749 static function getTriggerWatchButton()
2751 return '<a class="ui-btn ui-btn-sm
" target="_blank
" style="text-decoration: none;
color: #ffffff;
" href="/bitrix/admin/sql.php?query=show%20triggers;
">' . GetMessage("BITRIX_XSCAN_WATCH_EVENT
") . '</a>';
2754 static function getTotal($filter)
2756 return XScanResultTable::getCount($filter);
2759 static function getList($filter, $nav, $sort)
2763 $results = XScanResultTable::getList([
2764 'filter' => $filter,
2765 'offset' => $nav->getOffset(),
2766 'limit' => $nav->getlimit(),
2767 'order' => $sort['sort']
2770 foreach ($results as $result)
2772 if ($result['TYPE'] === 'file')
2774 $type = $result['MESSAGE'];
2775 $f = $result['SRC'];
2777 $code = preg_match('#\[([0-9]+)\]#', $type, $regs) ? $regs[1] : 0;
2778 $fu = urlencode(trim($f));
2779 $bInPrison = strpos('[100]', $type) === false;
2781 if (!file_exists($f) && file_exists($new_f = preg_replace('#\.php[578]?$#i', '.ph_', $f)))
2785 $fu = urlencode(trim($new_f));
2790 if (preg_match('/\.ph[_p][578]?$/i', $f))
2792 $action = strtolower(substr($f, -4)) !== '.ph_' ? self::getIsolateButton($f) : self::getUnIsolateButton($f);
2797 'ID' => $result['ID'],
2798 'FILE_NAME' => self::getFileWatchLink($f),
2799 'FILE_TYPE' => $type,
2800 'FILE_SCORE' => $result['SCORE'],
2801 'FILE_SIZE' => self::HumanSize(@filesize($f)),
2802 'FILE_MODIFY' => $result['MTIME'],
2803 'FILE_CREATE' => $result['CTIME'],
2804 'TAGS' => $result['TAGS'],
2805 'ACTIONS' => $action,
2806 'HIDE' => self::getHideButton($f),
2810 elseif ($result['TYPE'] === 'trigg')
2814 'ID' => $result['ID'],
2815 'FILE_NAME' => $result['SRC'],
2816 'FILE_TYPE' => $result['MESSAGE'],
2817 'FILE_SCORE' => $result['SCORE'],
2818 'ACTIONS' => self::getTriggerWatchButton()
2825 $table = match ($result['TYPE'])
2827 'agent' => 'b_agent',
2828 'event' => 'b_module_to_module',
2829 'tmpl' => 'b_site_template',
2834 'ID' => $result['ID'],
2835 'FILE_NAME' => self::getEventWatchLink($result['TYPE'] . " " . $result['SRC'], $table, $result['SRC']),
2836 'FILE_TYPE' => $result['MESSAGE'],
2837 'FILE_SCORE' => $result['SCORE'],
2838 'ACTIONS' => self::getEventWatchButton($table, $result['SRC'])
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
if(!is_array($prop["VALUES"])) $tmp
$_SERVER["DOCUMENT_ROOT"]
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
htmlspecialcharsbx($string, $flags=ENT_COMPAT, $doubleEncode=true)
ConvertTimeStamp($timestamp=false, $type="SHORT", $site=false, $bSearchInSitesOnly=false)
IncludeModuleLangFile($filepath, $lang=false, $bReturnArray=false)
__construct(?int $storeId, int $productId, string $barcode, int $userId)
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
if(empty($signedUserToken)) $key
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']