From 34a89a95185640376abed20a5d4a75190712c0c9 Mon Sep 17 00:00:00 2001 From: Gabor Gyorvari Date: Sat, 25 Aug 2018 18:13:45 +0200 Subject: [PATCH 1/4] Added new argument to display matching pattern's line number in the file. --- README.md | 1 + scan.php | 48 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6ea71f0..637670f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Usage: php scan.php -d -s --no-stop Continue scanning file after first hit -p --pattern Show Patterns next to the file name -t --time Show time of last file change + --line-number Display matching pattner line number in file ``` Ignore argument could be used multiple times and accept glob style matching ex.: "`cache*`", "`??-cache.php`" or "`/cache`" etc. diff --git a/scan.php b/scan.php index a7f9be3..44486fa 100644 --- a/scan.php +++ b/scan.php @@ -37,6 +37,7 @@ class MalwareScanner private $flagTime = false; private $flagExtraCheck = false; private $flagFollowSymlink = false; + private $flagLineNumber = false; private $whitelist = array(); private $ignore = array(); private $stat = array( @@ -197,7 +198,8 @@ class MalwareScanner 'no-color', 'no-stop', 'pattern', - 'time' + 'time', + 'line-number' ) ); @@ -263,6 +265,9 @@ class MalwareScanner if (isset($options['time']) || isset($options['t'])) { $this->setFlagTime(true); } + if (isset($options['line-number'])) { + $this->setFlagLineNumber(true); + } } public function setExtensions(array $a) @@ -301,6 +306,11 @@ class MalwareScanner $this->flagTime = $b; } + public function setFlagLineNumber($b) + { + $this->flagLineNumber = $b; + } + public function setFlagBase64($b) { $this->flagBase64 = $b; @@ -367,7 +377,7 @@ class MalwareScanner -Pattern Matched -The last comment to appear in the pattern file before this pattern */ - private function printPath(&$found, &$path, &$pattern, &$comment, &$hash) + private function printPath($found, $path, $pattern, $comment, $hash, $lineNumber) { $output_string = '# '; @@ -414,15 +424,18 @@ class MalwareScanner //'#' added again as code snippets have the potential to be valid shell commands if ($found) { if ($this->flagPattern) { - $opatt = "# $pattern "; - $output_string = $output_string . $state_color . $opatt . $this->ANSI_OFF; + $opatt = "# $pattern"; + $output_string = $output_string . $state_color . $opatt . $this->ANSI_OFF . ' '; } if ($this->flagComments) { - $output_string = $output_string . $this->ANSI_BLUE . $comment . $this->ANSI_OFF; + $output_string = $output_string . $this->ANSI_BLUE . $comment . $this->ANSI_OFF . ' '; + } + if ($this->flagLineNumber) { + $output_string = $output_string . '# ' . $lineNumber; } } - $output_string = $output_string . PHP_EOL; + $output_string = trim($output_string) . PHP_EOL; echo $output_string; } @@ -516,7 +529,7 @@ class MalwareScanner } if (!$found) { - $this->printPath($found, $path, $toSearch, $comment, $hash); + $this->printPath($found, $path, $toSearch, $comment, $hash, -1); return false; } @@ -532,14 +545,14 @@ class MalwareScanner //Returns true if the raw string exists in the file contents. private function scanFunc_STR(&$pattern, &$content) { - return (strpos($content, $pattern) !== false); + return strpos($content, $pattern); } //Performs raw string, case insensitive matching. //Returns true if the raw string exists in the file contents, ignoring case. private function scanFunc_STRI(&$pattern, &$content) { - return (stripos($content, $pattern) !== false); + return stripos($content, $pattern); } //Performs regular expression matching. @@ -547,7 +560,11 @@ class MalwareScanner //Patterns will match multiple lines, though you can use ^$ to match the beginning and end of a line. private function scanFunc_RE(&$pattern, &$content) { - return preg_match('/' . $pattern . '/im', $content); + $ret = preg_match('/' . $pattern . '/im', $content, $match, PREG_OFFSET_CAPTURE); + if ($ret) { + return $match[0][1]; + } + return false; } //First parameter '$scanFunction' is a defined function name passed as a string. @@ -563,12 +580,19 @@ class MalwareScanner foreach ($patterns as $pattern => $comment) { //Call the function that is named in $scanFunction //This allows multiple search/match functions to be used without duplicating the loop code. - if ($this->$scanFunction($pattern, $fileContent)) { + $position = $this->$scanFunction($pattern, $fileContent); + if ($position !== false) { $found = true; if ($hash === '') { $hash = md5($fileContent); } - $this->printPath($found, $path, $pattern, $comment, $hash); + $lineNumber = 1; + if ($this->flagLineNumber) { + if ($pos = strrpos(substr($fileContent, 0, $position), "\n")) { + $lineNumber = substr_count(substr($fileContent, 0, $pos + 1), "\n") + 1; + } + } + $this->printPath($found, $path, $pattern, $comment, $hash, $lineNumber); if (!$this->flagNoStop) { return; } From d17d6c01ba868a0fc40d21a2e792dd81b50ec578 Mon Sep 17 00:00:00 2001 From: Gabor Gyorvari Date: Sat, 25 Aug 2018 18:15:13 +0200 Subject: [PATCH 2/4] Typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 637670f..db5be08 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Usage: php scan.php -d -s --no-stop Continue scanning file after first hit -p --pattern Show Patterns next to the file name -t --time Show time of last file change - --line-number Display matching pattner line number in file + --line-number Display matching pattern line number in file ``` Ignore argument could be used multiple times and accept glob style matching ex.: "`cache*`", "`??-cache.php`" or "`/cache`" etc. From f9647806c836be676c2826beadc3337ebf404318 Mon Sep 17 00:00:00 2001 From: Gabor Gyorvari Date: Wed, 5 Sep 2018 13:01:21 +0200 Subject: [PATCH 3/4] Output formatting argument --- README.md | 21 +++++++++-- scan.php | 104 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index db5be08..35be287 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Usage: php scan.php -d -p --pattern Show Patterns next to the file name -t --time Show time of last file change --line-number Display matching pattern line number in file + --output-format Custom defined output format ``` Ignore argument could be used multiple times and accept glob style matching ex.: "`cache*`", "`??-cache.php`" or "`/cache`" etc. @@ -38,8 +39,22 @@ Ignore argument could be used multiple times and accept glob style matching ex.: Extension argument defaults to "`.php`" and also can be used multiple times too. * `--base64` is an alternative scan mode which ignores the main pattern files and uses a large list of php keywords and functions that have been converted to base64. Slower and prone to false positives, but gives additional base64 scanning coverage. These pattern files are located in base64_patterns and were derived from php 7 keywords and functions. Not many PHP extensions are included. -* `--comment` flag will display the last comment to appear in the pattern file before the matched pattern, so documenting the pattern files is important. -* `--pattern` flag will display the pattern string that was matched. +* `--comment` flag will display the last comment to appear in the pattern file before the matched pattern, so documenting the pattern files is important. + +Output formatting +----------------- + +Default output depending on the specified parameters, but the full format is "%S %T %M # {%F} %C %P # %L" and using ANSI coloring too. + +Possible variables are: + +* `%S` - matching indicator, possible values are OK, ER, WL +* `%T` - file change time +* `%M` - file md5 hash value +* `%F` - file with path +* `%P` - pattern +* `%C` - pattern comment +* `%L` - matching pattern line number Patterns -------- @@ -56,7 +71,7 @@ Whitelisting See [whitelist.txt](https://github.com/scr34m/php-malware-scanner/blob/master/whitelist.txt) file for a predefined MD5 hash list. Only the first 32 characters are used, rest of the line ignored so feel free to leave a comment. Tools ---------- +----- **text2base64.py** diff --git a/scan.php b/scan.php index 44486fa..9a912e8 100644 --- a/scan.php +++ b/scan.php @@ -38,6 +38,7 @@ class MalwareScanner private $flagExtraCheck = false; private $flagFollowSymlink = false; private $flagLineNumber = false; + private $outputFormat = ''; private $whitelist = array(); private $ignore = array(); private $stat = array( @@ -199,7 +200,8 @@ class MalwareScanner 'no-stop', 'pattern', 'time', - 'line-number' + 'line-number', + 'output-format:' ) ); @@ -268,6 +270,11 @@ class MalwareScanner if (isset($options['line-number'])) { $this->setFlagLineNumber(true); } + if (isset($options['output-format'])) { + $this->setOutputFormat( + is_array($options['output-format']) ? $options['output-format'] : array ($options['output-format']) + ); + } } public function setExtensions(array $a) @@ -341,6 +348,11 @@ class MalwareScanner $this->flagNoStop = $b; } + public function setOutputFormat(array $format) + { + $this->outputFormat = array_shift($format); + } + // @see http://stackoverflow.com/a/13914119 private function pathMatches($path, $pattern, $ignoreCase = false) { @@ -367,21 +379,29 @@ class MalwareScanner return (bool)preg_match($expr, $path); } - /* - Formats and prints the scan result output line by line. - Depending on specified options, it will print: - -Status code - -Last Modified Time - -MD5 Hash - -File Path - -Pattern Matched - -The last comment to appear in the pattern file before this pattern - */ + /** + * Formats and prints the scan result output line by line. + * + * Depending on specified options, it will print: + * - Status code + * - Last Modified Time + * - MD5 Hash + * - File Path + * - Pattern Matched + * - The last comment to appear in the pattern file before this pattern + * - Matching line number + * + * @param $found + * @param $path + * @param $pattern + * @param $comment + * @param $hash + * @param $lineNumber + */ private function printPath($found, $path, $pattern, $comment, $hash, $lineNumber) { - $output_string = '# '; + $default_format = '%S '; - //OK if (!$found) { if ($this->flagHideOk) { return; @@ -389,55 +409,77 @@ class MalwareScanner $state = 'OK'; $hash = ' '; $state_color = $this->ANSI_GREEN; - } //WL - elseif ($this->inWhitelist($hash)) { + } elseif ($this->inWhitelist($hash)) { if ($this->flagHideWhitelist) { return; } $state = 'WL'; $state_color = $this->ANSI_YELLOW; - } //ER - else { + } else { $state = 'ER'; $state_color = $this->ANSI_RED; } - $output_string = $state_color . $output_string . $state . $this->ANSI_OFF . ' '; //Include cTime if ($this->flagTime) { $changed_time = filectime($path); - $htime = date('H:i d-m-Y', $changed_time); - $output_string = $output_string . $this->ANSI_BLUE . $htime . $this->ANSI_OFF . ' '; + $ctime = date('H:i d-m-Y', $changed_time); + $default_format .= '%T'; + } else { + $ctime = ''; } //Include Checksum/Hash if ($this->flagChecksum) { - $output_string = $output_string . $this->ANSI_BLUE . $hash . $this->ANSI_OFF . ' '; + $default_format .= '%M '; } - //Append Path - //'#' and {} included to prevent accidental script execution attempts + // '#' and {} included to prevent accidental script execution attempts // in the event that script output is pasted into a root terminal - $opath = '# ' . '{' . $path . '}'; - $output_string = $output_string . $opath . ' '; + $default_format .= '# {%F} '; //'#' added again as code snippets have the potential to be valid shell commands if ($found) { if ($this->flagPattern) { - $opatt = "# $pattern"; - $output_string = $output_string . $state_color . $opatt . $this->ANSI_OFF . ' '; + $default_format .= '%P '; } if ($this->flagComments) { - $output_string = $output_string . $this->ANSI_BLUE . $comment . $this->ANSI_OFF . ' '; + $default_format .= '%C '; } if ($this->flagLineNumber) { - $output_string = $output_string . '# ' . $lineNumber; + $default_format .= '# %L'; } } - $output_string = trim($output_string) . PHP_EOL; + if ($this->outputFormat) { + $map = [ + '%S' => $state, + '%T' => $ctime, + '%M' => $hash, + '%F' => $path, + '%P' => $pattern, + '%C' => $comment, + '%L' => $lineNumber, + ]; + } else { + $map = [ + '%S' => $state_color . '# ' . $state . $this->ANSI_OFF, + '%T' => $this->ANSI_BLUE . $ctime . $this->ANSI_OFF, + '%M' => $this->ANSI_BLUE . $hash . $this->ANSI_OFF, + '%F' => $path, + '%P' => $state_color . '#' . $pattern . $this->ANSI_OFF, + '%C' => $this->ANSI_BLUE . $comment . $this->ANSI_OFF, + '%L' => $lineNumber, + ]; + } - echo $output_string; + if ($this->outputFormat) { + $format = $this->outputFormat; + } else { + $format = trim($default_format); + } + + echo str_replace(array_keys($map), array_values($map), $format) . PHP_EOL; } //Recursively scales the file system. From a6360c56a0b92a244d3198a959528d51d61b9e81 Mon Sep 17 00:00:00 2001 From: Gabor Gyorvari Date: Wed, 5 Sep 2018 19:56:43 +0200 Subject: [PATCH 4/4] Short argument names --- README.md | 4 ++-- scan.php | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 35be287..1f4207b 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Usage: php scan.php -d -s --no-stop Continue scanning file after first hit -p --pattern Show Patterns next to the file name -t --time Show time of last file change - --line-number Display matching pattern line number in file - --output-format Custom defined output format + -L --line-number Display matching pattern line number in file + -o --output-format Custom defined output format ``` Ignore argument could be used multiple times and accept glob style matching ex.: "`cache*`", "`??-cache.php`" or "`/cache`" etc. diff --git a/scan.php b/scan.php index 9a912e8..6656ceb 100644 --- a/scan.php +++ b/scan.php @@ -182,7 +182,7 @@ class MalwareScanner private function parseArgs() { $options = getopt( - 'd:e:i:abmcxlhkwnspt', + 'd:e:i:o:abmcxlhkwnsptL', array( 'directory:', 'extension:', @@ -267,13 +267,12 @@ class MalwareScanner if (isset($options['time']) || isset($options['t'])) { $this->setFlagTime(true); } - if (isset($options['line-number'])) { + if (isset($options['line-number']) || isset($options['L'])) { $this->setFlagLineNumber(true); } - if (isset($options['output-format'])) { - $this->setOutputFormat( - is_array($options['output-format']) ? $options['output-format'] : array ($options['output-format']) - ); + if (isset($options['output-format']) || isset($options['o'])) { + $tmp = isset($options['output-format']) ? $options['output-format'] : $options['o']; + $this->setOutputFormat(is_array($tmp) ? $tmp : array($tmp)); } } @@ -571,7 +570,7 @@ class MalwareScanner } if (!$found) { - $this->printPath($found, $path, $toSearch, $comment, $hash, -1); + $this->printPath($found, $path, $toSearch, $comment, $hash, 0); return false; } @@ -628,7 +627,7 @@ class MalwareScanner if ($hash === '') { $hash = md5($fileContent); } - $lineNumber = 1; + $lineNumber = 0; if ($this->flagLineNumber) { if ($pos = strrpos(substr($fileContent, 0, $position), "\n")) { $lineNumber = substr_count(substr($fileContent, 0, $pos + 1), "\n") + 1; @@ -663,6 +662,8 @@ class MalwareScanner echo ' -s --no-stop Continue scanning file after first hit' . PHP_EOL; echo ' -p --pattern Show Patterns next to the file name' . PHP_EOL; echo ' -t --time Show time of last file change' . PHP_EOL; + echo ' -L --line-number Display matching pattern line number in file' . PHP_EOL; + echo ' -o --output-format Custom defined output format' . PHP_EOL; } }