mirror of
https://github.com/scr34m/php-malware-scanner.git
synced 2026-06-16 12:30:35 +00:00
Added Comments and Documentation
Added Comments and Documentation
This commit is contained in:
108
scan.php
108
scan.php
@@ -44,24 +44,24 @@ class MalwareScanner
|
|||||||
);
|
);
|
||||||
private $followSymlink = false;
|
private $followSymlink = false;
|
||||||
|
|
||||||
|
//Pattern File Attributes
|
||||||
private $patterns_raw = array();
|
private $patterns_raw = array();
|
||||||
private $patterns_iraw = array();
|
private $patterns_iraw = array();
|
||||||
private $patterns_re = array();
|
private $patterns_re = array();
|
||||||
private $patterns_b64functions = array();
|
private $patterns_b64functions = array();
|
||||||
private $patterns_b64keywords = array();
|
private $patterns_b64keywords = array();
|
||||||
|
|
||||||
|
//Constructor - Likes to do as little as possible.
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
//Read Run Options
|
//Read Run Options
|
||||||
$this->parseArgs();
|
$this->parseArgs();
|
||||||
|
|
||||||
//Load Patterns
|
|
||||||
$this->initializePatterns();
|
|
||||||
|
|
||||||
//Initiate Scan
|
//Initiate Scan
|
||||||
$this->run($this->dir);
|
$this->run($this->dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the -n/--no-color flag to easily remove color characters.
|
||||||
private function disableColor()
|
private function disableColor()
|
||||||
{
|
{
|
||||||
$this->ANSI_GREEN = '';
|
$this->ANSI_GREEN = '';
|
||||||
@@ -71,6 +71,8 @@ class MalwareScanner
|
|||||||
$this->ANSI_OFF = '';
|
$this->ANSI_OFF = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Prints the passed 'string' in red text, calls showHelp().
|
||||||
|
//Exits
|
||||||
private function error($msg)
|
private function error($msg)
|
||||||
{
|
{
|
||||||
echo $this->ANSI_RED . 'Error: ' . $msg . $this->ANSI_OFF . PHP_EOL;
|
echo $this->ANSI_RED . 'Error: ' . $msg . $this->ANSI_OFF . PHP_EOL;
|
||||||
@@ -79,29 +81,35 @@ class MalwareScanner
|
|||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Handles pattern loading and saving to the class object
|
||||||
private function initializePatterns()
|
private function initializePatterns()
|
||||||
{
|
{
|
||||||
|
//Loads either the primary scanning patterns or the base64 patterns depending on -b/--base64 flag
|
||||||
if (!$this->flagBase64) {
|
if (!$this->flagBase64) {
|
||||||
$this->patterns_raw = $this->loadPatterns(dirname(__FILE__) . '/patterns_raw.txt');
|
$this->patterns_raw = $this->loadPatterns(dirname(__FILE__) . '/patterns_raw.txt');
|
||||||
$this->patterns_iraw = $this->loadPatterns(dirname(__FILE__) . '/patterns_iraw.txt');
|
$this->patterns_iraw = $this->loadPatterns(dirname(__FILE__) . '/patterns_iraw.txt');
|
||||||
$this->patterns_re = $this->loadPatterns(dirname(__FILE__) . '/patterns_re.txt');
|
$this->patterns_re = $this->loadPatterns(dirname(__FILE__) . '/patterns_re.txt');
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if ($this->flagBase64) {
|
|
||||||
$this->patterns_b64functions = $this->loadPatterns(dirname(__FILE__). '/php_functions.txt');
|
$this->patterns_b64functions = $this->loadPatterns(dirname(__FILE__). '/php_functions.txt');
|
||||||
$this->patterns_b64keywrds = $this->loadPatterns(dirname(__FILE__). '/php_keywords.txt');
|
$this->patterns_b64keywrds = $this->loadPatterns(dirname(__FILE__). '/php_keywords.txt');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Adds additional checks to patterns_raw
|
||||||
|
//This may be something to move into a pattern file rather than leave hardcoded.
|
||||||
if ($this->extraCheck) {
|
if ($this->extraCheck) {
|
||||||
array_push($this->patterns_raw, "googleBot", "htaccess");
|
$this->patterns_raw['googleBot'] = '# ';
|
||||||
|
$this->patterns_raw['htaccess'] = '# ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if the md5 checksum exists in the whitelist and returns true if it does.
|
||||||
private function inWhitelist($hash)
|
private function inWhitelist($hash)
|
||||||
{
|
{
|
||||||
return in_array($hash, $this->whitelist);
|
return in_array($hash, $this->whitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check if -i/--ignore flag listed this path to be omitted.
|
||||||
private function isIgnored($pathname)
|
private function isIgnored($pathname)
|
||||||
{
|
{
|
||||||
foreach ($this->ignore as $pattern) {
|
foreach ($this->ignore as $pattern) {
|
||||||
@@ -113,6 +121,10 @@ class MalwareScanner
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Loads individual pattern files
|
||||||
|
//Skips blank linese
|
||||||
|
//Stores most recent comment with the pattern in the list[] array
|
||||||
|
//Returns an array of patterns:comments in key:value pairs
|
||||||
private function loadPatterns($file)
|
private function loadPatterns($file)
|
||||||
{
|
{
|
||||||
$last_comment = '';
|
$last_comment = '';
|
||||||
@@ -124,6 +136,8 @@ class MalwareScanner
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
//Check if first char in pattern is a '#' which indicates a comment and skips.
|
//Check if first char in pattern is a '#' which indicates a comment and skips.
|
||||||
|
//Stores the comment to be stored with the pattern in the list as key:value pairs.
|
||||||
|
//The pattern is the key and the comment is the value.
|
||||||
if ($pattern[0] === '#') {
|
if ($pattern[0] === '#') {
|
||||||
$last_comment = $pattern;
|
$last_comment = $pattern;
|
||||||
continue;
|
continue;
|
||||||
@@ -134,6 +148,7 @@ class MalwareScanner
|
|||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Loads the whitelist file
|
||||||
private function loadWhitelist()
|
private function loadWhitelist()
|
||||||
{
|
{
|
||||||
if (!is_file(__DIR__ . '/whitelist.txt')) {
|
if (!is_file(__DIR__ . '/whitelist.txt')) {
|
||||||
@@ -146,6 +161,8 @@ class MalwareScanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Handles the getopt() function call, sets attributes according to flags.
|
||||||
|
//All flag handling stuff should be setup here.
|
||||||
private function parseArgs()
|
private function parseArgs()
|
||||||
{
|
{
|
||||||
$options = getopt('d:e:i:bmcxlhkwntv', array('directory:', 'extension:', 'ignore:', 'base', 'checksum', 'comment', 'extra-check', 'follow-link', 'help', 'hide-ok', 'hide-whitelist', 'no-color', 'time', 'verbose'));
|
$options = getopt('d:e:i:bmcxlhkwntv', array('directory:', 'extension:', 'ignore:', 'base', 'checksum', 'comment', 'extra-check', 'follow-link', 'help', 'hide-ok', 'hide-whitelist', 'no-color', 'time', 'verbose'));
|
||||||
@@ -173,13 +190,13 @@ class MalwareScanner
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Simple Flag Options
|
//Simple Flag Options
|
||||||
if (isset($options['base64']) || isset($options['b'])){
|
if (isset($options['base64']) || isset($options['b'])) {
|
||||||
$this->flagBase64 = true;
|
$this->flagBase64 = true;
|
||||||
}
|
}
|
||||||
if (isset($options['checksum']) || isset($options['m'])){
|
if (isset($options['checksum']) || isset($options['m'])) {
|
||||||
$this->flagChecksum = true;
|
$this->flagChecksum = true;
|
||||||
}
|
}
|
||||||
if (isset($options['comment']) || isset($options['c'])){
|
if (isset($options['comment']) || isset($options['c'])) {
|
||||||
$this->flagComments = true;
|
$this->flagComments = true;
|
||||||
}
|
}
|
||||||
if (isset($options['extra-check']) || isset($options['x'])) {
|
if (isset($options['extra-check']) || isset($options['x'])) {
|
||||||
@@ -194,13 +211,13 @@ class MalwareScanner
|
|||||||
if (isset($options['hide-whitelist']) || isset($options['w'])) {
|
if (isset($options['hide-whitelist']) || isset($options['w'])) {
|
||||||
$this->flagHideWhitelist = true;
|
$this->flagHideWhitelist = true;
|
||||||
}
|
}
|
||||||
if (isset($options['no-color']) || isset($options['n'])){
|
if (isset($options['no-color']) || isset($options['n'])) {
|
||||||
$this->disableColor();
|
$this->disableColor();
|
||||||
}
|
}
|
||||||
if (isset($options['time']) || isset($options['t'])){
|
if (isset($options['time']) || isset($options['t'])) {
|
||||||
$this->flagTime = true;
|
$this->flagTime = true;
|
||||||
}
|
}
|
||||||
if (isset($options['verbose']) || isset($options['v'])){
|
if (isset($options['verbose']) || isset($options['v'])) {
|
||||||
$this->verbose = true;
|
$this->verbose = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +248,16 @@ class MalwareScanner
|
|||||||
return (bool)preg_match($expr, $path);
|
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)
|
private function printPath(&$found, &$path, &$pattern, &$comment, &$hash)
|
||||||
{
|
{
|
||||||
$output_string = '# ';
|
$output_string = '# ';
|
||||||
@@ -261,7 +288,7 @@ class MalwareScanner
|
|||||||
$output_string = $output_string . $this->ANSI_BLUE . $htime . $this->ANSI_OFF . ' ';
|
$output_string = $output_string . $this->ANSI_BLUE . $htime . $this->ANSI_OFF . ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
//Include Checksum
|
//Include Checksum/Hash
|
||||||
if ($this->flagChecksum) {
|
if ($this->flagChecksum) {
|
||||||
$output_string = $output_string . $this->ANSI_BLUE . $hash . $this->ANSI_OFF . ' ';
|
$output_string = $output_string . $this->ANSI_BLUE . $hash . $this->ANSI_OFF . ' ';
|
||||||
}
|
}
|
||||||
@@ -286,6 +313,8 @@ class MalwareScanner
|
|||||||
echo $output_string;
|
echo $output_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Recursively scales the file system.
|
||||||
|
//Calls the scan() function for each file found.
|
||||||
private function process($dir)
|
private function process($dir)
|
||||||
{
|
{
|
||||||
$dh = opendir($dir);
|
$dh = opendir($dir);
|
||||||
@@ -315,6 +344,7 @@ class MalwareScanner
|
|||||||
closedir($dh);
|
closedir($dh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Prints stats on the run.
|
||||||
private function report($start, $dir)
|
private function report($start, $dir)
|
||||||
{
|
{
|
||||||
$end = time();
|
$end = time();
|
||||||
@@ -327,6 +357,9 @@ class MalwareScanner
|
|||||||
echo 'Total malware identified: ' . $this->stat['files_infected'] . PHP_EOL;
|
echo 'Total malware identified: ' . $this->stat['files_infected'] . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Validates the input directory
|
||||||
|
//Calls the load pattern and load whitelist functions
|
||||||
|
//Calls the process and report functions.
|
||||||
private function run($dir)
|
private function run($dir)
|
||||||
{
|
{
|
||||||
//Make sure a directory was specified.
|
//Make sure a directory was specified.
|
||||||
@@ -340,16 +373,22 @@ class MalwareScanner
|
|||||||
$this->error('Specified path is not a directory: ' . $dir);
|
$this->error('Specified path is not a directory: ' . $dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Load Patterns
|
||||||
|
$this->initializePatterns();
|
||||||
|
|
||||||
|
//Load Whitelist
|
||||||
|
$this->loadWhitelist();
|
||||||
|
|
||||||
$start = time();
|
$start = time();
|
||||||
$this->loadWhitelist();
|
|
||||||
$this->process($dir . '/');
|
$this->process($dir . '/');
|
||||||
$this->report($start, $dir . '/');
|
$this->report($start, $dir . '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Loads target file contents for scanning
|
||||||
|
//Initiates the multiple scan types by calling the scanLoop function
|
||||||
private function scan($path)
|
private function scan($path)
|
||||||
{
|
{
|
||||||
$this->stat['files_scanned']++;
|
$this->stat['files_scanned']++;
|
||||||
|
|
||||||
$fileContent = file_get_contents($path);
|
$fileContent = file_get_contents($path);
|
||||||
$found = false;
|
$found = false;
|
||||||
$hash = '';
|
$hash = '';
|
||||||
@@ -361,7 +400,7 @@ class MalwareScanner
|
|||||||
$this->scanLoop('scanFunc_STRI', $fileContent, $this->patterns_iraw, $path, $found, $hash);
|
$this->scanLoop('scanFunc_STRI', $fileContent, $this->patterns_iraw, $path, $found, $hash);
|
||||||
$this->scanLoop('scanFunc_RE', $fileContent, $this->patterns_re, $path, $found, $hash);
|
$this->scanLoop('scanFunc_RE', $fileContent, $this->patterns_re, $path, $found, $hash);
|
||||||
}
|
}
|
||||||
if ($this->flagBase64) {
|
else {
|
||||||
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64functions, $path, $found, $hash);
|
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64functions, $path, $found, $hash);
|
||||||
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64keywords, $path, $found, $hash);
|
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64keywords, $path, $found, $hash);
|
||||||
}
|
}
|
||||||
@@ -379,40 +418,52 @@ class MalwareScanner
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Performs raw string, case sensitive matching.
|
||||||
|
//Returns true if the raw string exists in the file contents.
|
||||||
private function scanFunc_STR(&$pattern, &$content)
|
private function scanFunc_STR(&$pattern, &$content)
|
||||||
{
|
{
|
||||||
return (strpos($content, $pattern) !== false);
|
return (strpos($content, $pattern) !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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)
|
private function scanFunc_STRI(&$pattern, &$content)
|
||||||
{
|
{
|
||||||
return (stripos($content, $pattern) !== false);
|
return (stripos($content, $pattern) !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Performs regular expression matching.
|
||||||
|
//Returns true if the Regular Expression matches something in the file.
|
||||||
|
//Patterns will match multiple lines, though you can use ^$ to match the beginning and end of a line.
|
||||||
private function scanFunc_RE(&$pattern, &$content)
|
private function scanFunc_RE(&$pattern, &$content)
|
||||||
{
|
{
|
||||||
return preg_match('/' . $pattern . '/im', $content);
|
return preg_match('/' . $pattern . '/im', $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//First parameter '$scanFunction' is a defined function name passed as a string.
|
||||||
|
//This function should accept a pattern string and a content string.
|
||||||
|
//This function will return true if the pattern exists in the content.
|
||||||
|
//See 'scanFunc_STR', 'scanFunc_STRI', 'scanFUNC_RE' above as examples.
|
||||||
|
|
||||||
|
//Loops through all patterns in a file using the passed function name to determine a match.
|
||||||
|
//Variables passed by reference for performance and modification access.
|
||||||
private function scanLoop($scanFunction, &$fileContent, &$patterns, &$path, &$found, &$hash)
|
private function scanLoop($scanFunction, &$fileContent, &$patterns, &$path, &$found, &$hash)
|
||||||
{
|
{
|
||||||
if (!$found || $this->verbose) {
|
if (!$found || $this->verbose) {
|
||||||
foreach ($patterns as $pattern => $comment) {
|
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)) {
|
if ($this->$scanFunction($pattern, $fileContent)) {
|
||||||
$found = true;
|
$found = true;
|
||||||
if ($hash === ''){
|
if ($hash === ''){$hash = md5($fileContent);}
|
||||||
$hash = md5($fileContent);
|
|
||||||
}
|
|
||||||
$this->printPath($found, $path, $pattern, $comment, $hash);
|
$this->printPath($found, $path, $pattern, $comment, $hash);
|
||||||
if (!$this->verbose){
|
if (!$this->verbose){return;}
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Prints out the usage menu options.
|
||||||
private function showHelp()
|
private function showHelp()
|
||||||
{
|
{
|
||||||
echo 'Usage: scan.php -d <directory>' . PHP_EOL;
|
echo 'Usage: scan.php -d <directory>' . PHP_EOL;
|
||||||
@@ -421,17 +472,18 @@ class MalwareScanner
|
|||||||
echo ' -e <file extension> --extension File Extension to Scan' . PHP_EOL;
|
echo ' -e <file extension> --extension File Extension to Scan' . PHP_EOL;
|
||||||
echo ' -i <directory|file> --ignore Directory of file to igonre' . PHP_EOL;
|
echo ' -i <directory|file> --ignore Directory of file to igonre' . PHP_EOL;
|
||||||
echo ' -b --base64 Scan for base64 encoded PHP keywords' . PHP_EOL;
|
echo ' -b --base64 Scan for base64 encoded PHP keywords' . PHP_EOL;
|
||||||
echo ' -m --checksum Display MD5 Checksum of file' . PHP_EOL;
|
echo ' -m --checksum Display MD5 Hash/Checksum of file' . PHP_EOL;
|
||||||
echo ' -c --comment Display comments for matched patterns' . PHP_EOL;
|
echo ' -c --comment Display comments for matched patterns' . PHP_EOL;
|
||||||
echo ' -x --extra-check Adds GoogleBot and htaccess to Scan List' . PHP_EOL;
|
echo ' -x --extra-check Adds GoogleBot and htaccess to Scan List' . PHP_EOL;
|
||||||
echo ' -l --follow-symlink Follow symlinked directories' . PHP_EOL;
|
echo ' -l --follow-symlink Follow symlinked directories' . PHP_EOL;
|
||||||
echo ' -k --hide-ok Hide OK aka not infected messages' . PHP_EOL;
|
echo ' -k --hide-ok Hide results with \'OK\' status' . PHP_EOL;
|
||||||
echo ' -w --hide-whitelist Hide whitelisted messages' . PHP_EOL;
|
echo ' -w --hide-whitelist Hide results with \'WL\' status' . PHP_EOL;
|
||||||
echo ' -t --time Show time of last file change' . PHP_EOL;
|
echo ' -t --time Show time of last file change' . PHP_EOL;
|
||||||
echo ' -v --verbose Continue scanning file afater first hit' . PHP_EOL;
|
echo ' -v --verbose Continue scanning file after first hit' . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Creates a new MalwareScanner object which does all the work.
|
||||||
new MalwareScanner();
|
new MalwareScanner();
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user