Merge branch 'line-number-argument'

This commit is contained in:
Gabor Gyorvari
2018-09-15 07:48:51 +02:00
2 changed files with 124 additions and 41 deletions

View File

@@ -30,6 +30,8 @@ Usage: php scan.php -d <directory>
-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
-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.
@@ -37,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
--------
@@ -55,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**

143
scan.php
View File

@@ -37,6 +37,8 @@ class MalwareScanner
private $flagTime = false;
private $flagExtraCheck = false;
private $flagFollowSymlink = false;
private $flagLineNumber = false;
private $outputFormat = '';
private $whitelist = array();
private $ignore = array();
private $stat = array(
@@ -180,7 +182,7 @@ class MalwareScanner
private function parseArgs()
{
$options = getopt(
'd:e:i:abmcxlhkwnspt',
'd:e:i:o:abmcxlhkwnsptL',
array(
'directory:',
'extension:',
@@ -197,7 +199,9 @@ class MalwareScanner
'no-color',
'no-stop',
'pattern',
'time'
'time',
'line-number',
'output-format:'
)
);
@@ -263,6 +267,13 @@ class MalwareScanner
if (isset($options['time']) || isset($options['t'])) {
$this->setFlagTime(true);
}
if (isset($options['line-number']) || isset($options['L'])) {
$this->setFlagLineNumber(true);
}
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));
}
}
public function setExtensions(array $a)
@@ -301,6 +312,11 @@ class MalwareScanner
$this->flagTime = $b;
}
public function setFlagLineNumber($b)
{
$this->flagLineNumber = $b;
}
public function setFlagBase64($b)
{
$this->flagBase64 = $b;
@@ -331,6 +347,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)
{
@@ -357,21 +378,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
*/
private function printPath(&$found, &$path, &$pattern, &$comment, &$hash)
/**
* 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;
@@ -379,52 +408,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) {
$default_format .= '# %L';
}
}
$output_string = $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.
@@ -516,7 +570,7 @@ class MalwareScanner
}
if (!$found) {
$this->printPath($found, $path, $toSearch, $comment, $hash);
$this->printPath($found, $path, $toSearch, $comment, $hash, 0);
return false;
}
@@ -532,14 +586,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 +601,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 +621,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 = 0;
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;
}
@@ -597,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;
}
}