Вход Регистрация
Файл: forsoc.ru/manul/classes/MalwareDetector.inc.php
Строк: 432
<?php

ob_start
();
require_once(
'XmlValidator.inc.php');
require_once(
'FileInfo.inc.php');
require_once(
'Writer.inc.php');
require_once(
'Quarantiner.inc.php');
ob_end_clean();

class 
MalwareDetector
{
    function 
__construct()
    {
        global 
$projectRootDir$projectTmpDir;

        if (!
function_exists('escapedHexToHex')  || !function_exists('escapedOctDec')) {
            die(
'escapedHexToHex or escapedOctDec is missing');
        }

        
$this->SIGNATURE_FILENAME $projectRootDir '/static/signatures/malware_db.xml';

        
$this->QUEUE_FILENAME $projectTmpDir '/scan_queue.manul.tmp.txt';
        
$this->QUEUE_OFFSET_FILENAME $projectTmpDir '/queue_offset.manul.tmp.txt';
        
$this->MALWARE_LOG_FILENAME $projectTmpDir '/malware_log.manul.tmp.txt';
        
$this->MALWARE_QUARANTINE_FILENAME $projectTmpDir '/malware_quarantine.manul.tmp.txt';
        
$this->MALWARE_QUARANTINE_FILEPATH_FILEPATH $projectTmpDir '/malware_quarantine_filepath.tmp.txt';

        
$this->XML_LOG_FILENAME $projectTmpDir '/scan_log.xml';

        
$this->SCRIPT_START time();

        
$this->MAX_FILESIZE 1024 1024// 1MB
        
$this->MAX_PREVIEW_LENGTH 80// characters
        
$this->MAX_EXECUTION_DURATION 20;

        
$validator = new XmlValidator();
        if (!
$validator->validate(implode(''file($this->SIGNATURE_FILENAME)), $projectRootDir '/static/xsd/malware_db.xsd')) {
            die(
basename(__FILE__) . PS_ERR_MALWARE_DB_BROKEN);
        }

        
$this->signatures = new DOMDocument();
        
$this->signatures->load($this->SIGNATURE_FILENAME);

    }

    function 
setRequestDelay($delay)
    {
        
$this->MAX_EXECUTION_DURATION $delay;
    }

    function 
throwTimeout($filePath)
    {
        echo 
$this->AJAX_HEADER_ERROR "n";
        echo 
basename(__FILE__) . ': timeout while scanning ' $filePath "nTry to increase an interval in settings.n";
        exit;
    }

    function 
normalizeContent($content)
    {
        
$content preg_replace_callback('/\\x([a-fA-F0-9]{1,2})/i''escapedHexToHex'$content); // strip hex ascii notation
        
$content preg_replace_callback('/\\([0-9]{1,3})/i''escapedOctDec'$content); // strip dec ascii notation
        
$content preg_replace('/['"]s*?.s*?['"]/smi', '', $content); // concat fragmented strings
        $content = preg_replace('
|/*.*?*/|smi', '', $content); // remove comments to detect fragmented pieces of malware

        return $content;
    }

    function getFragment($content, $pos)
    {
        $maxChars = $this->MAX_PREVIEW_LENGTH;
        $maxLen = strlen($content);
        $rightPos = min($pos + $maxChars, $maxLen);
        $minPos = max(0, $pos - $maxChars);

        $start = substr($content, 0, $pos);
        $start = str_replace('
r', '', $start);
        $lineNo = strlen($start) - strlen(str_replace("n", '', $start)) + 1;

        $res = '
L' . $lineNo . '' . substr($content, $minPos, $pos - $minPos) .
            '
@_MARKER_@' .
            substr($content, $pos, $rightPos - $pos - 1);

        return htmlspecialchars($res);
    }

    function queueQuarantine($filename)
    {
        if ($fh = fopen($this->MALWARE_QUARANTINE_FILENAME, '
a')) {
            fputs($fh, $filename . "n");
            fclose($fh);
        }
    }

    function checkForValidPhp($content)
    {
        $len = strlen($content);
        $start = 0;
        $valid = false;

        while (($start = strpos($content, '
<?', $start)) !== false) {
            $valid = true;
            $start++;
            $end = strpos($content, '
?>', $start + 1);
            if ($end === false) {
                $end = $len;
            }

            while (++$start < $end) {
                $c = ord($content[$start]);
                if ($c < 9 || ($c >= 14 && $c <= 31) || $c == 11 || $c == 12) {
                    return false;
                }
            }
        }

        return $valid;
    }

    function detectMalware($filePath, &$foundFragment, &$pos, $startTime, $timeout, $ext)
    {

        if (filesize($filePath) > $this->MAX_FILESIZE) {
            return 'skipped';
        }

        if (!is_file($filePath)) {
            return 'no_read';
        }

        $needToScan = false;
        $extensions = array('ph' /* php, php3, phtml */, 'htm' /* htm, html */, 'txt', 'js', 'pl', 'cgi', 'py', 'bash', 'sh', 'xml', 'ssi', 'inc', 'pm', 'tpl');

        // do scan for all kind of scripts 
        foreach ($extensions as $scanExt) {
            if (strpos($ext, $scanExt) !== false) {
                $needToScan = true;
            }
        }

        $content = implode('', file($filePath));

        $fileToBeScannedSignatureList = array(
            '<?php',
            '
<?=',
            '
#!/usr/',
            
'#!/bin/',
            
'#!/local/',
            
'eval(',
            
'assert(',
            
'base64_decode('
        
);

        if (!
$needToScan) {
            foreach (
$fileToBeScannedSignatureList as $scanSig) {
                if (
strpos($content$scanSig) !== false) {
                    
$needToScan true;
                }
            }
        }

        if (!
$needToScan && $this->checkForValidPhp($content)) {
            
$needToScan true;
        }

        if (!
$needToScan) {
            return 
'no_need_to_check';
        }

        
$normalized $this->normalizeContent($content);

        
$db $this->signatures->getElementsByTagName('signature');
        
$detected false;
        foreach (
$db as $sig) {
            if (
$detected) break;

            
$currentTime time();
            if (
$currentTime $startTime $timeout) {
                return 
'timeout';
            }

            
$pos = -1;
            
$sigContent $sig->nodeValue;
            
$attr $sig->attributes;
            
$attrId $attr->getNamedItem('id')->nodeValue;
            
$attrFormat $attr->getNamedItem('format')->nodeValue;
            
$attrChildId $attr->getNamedItem('child_id')->nodeValue;
            
$attrSeverity $attr->getNamedItem('sever')->nodeValue;

            switch (
$attrFormat) {

                case 
're':
                    if ((
preg_match('#(' $sigContent ')#smi'$content$foundPREG_OFFSET_CAPTURE)) ||
                        (
preg_match('#(' $sigContent ')#smi'$normalized$foundPREG_OFFSET_CAPTURE))
                    ) {
                        
$detected true;
                        
$pos $found[0][1];
                        continue;
                    }

                    break;
                case 
'const':
                    if (((
$pos strpos($content$sigContent)) !== FALSE) ||
                        ((
$pos strpos($normalized$sigContent)) !== FALSE)
                    ) {
                        
$detected true;
                        continue;
                    }

                    break;
            }
        }

        if (
$detected) {
            
$foundFragment $this->getFragment($content$pos);
            return 
$attrSeverity;
        }
    }


    function 
parseXml($xmlFilename)
    {
        
$dom null;
        try {
            
$dom = new DOMDocument('1.0''utf-8');
            
$dom->formatOutput true;
            
$dom->load($xmlFilename);
        } catch (
Exception $e) {
            die(
'An exception has occured: ' $e->getMessage() . "n");
        }
        if (!
$dom) {
            die(
'An exception has occured: ');
        }
        return 
$dom;
    }

    function 
repackXMLLog()
    {
        global 
$projectRootDir;

        
$xmlLogFilename $this->XML_LOG_FILENAME;
        
$xml $this->parseXml($xmlLogFilename);
        
$xpath = new DOMXpath($xml);

        if (!
is_file($this->MALWARE_LOG_FILENAME)) {
            die(
basename(__FILE__) . ': cannot open ' $this->MALWARE_LOG_FILENAME ' during repacking');
        }

        
$lines file($this->MALWARE_LOG_FILENAME);

        foreach (
$lines as $lineNum => $line) {
            
#Example /home/www/badcode.tk/web_root/robots.txt.dist;detected=;pos=-1;snippet=
            
$data explode(';'$line);
            
$filePath $data[0];
            
$detected substr($data[1], strlen('detected='));
            if (
$detected) {
                
$pos substr($data[2], strlen('pos='));
                
$snippet trim(substr($data[3], strlen('snippet=')));

                
#Getting current element in DOM
                
$relativePath str_replace($_SERVER['DOCUMENT_ROOT'], '.'$filePath);
                
$filePathNode $xpath->query('/website_info/files/file/path[text()="' $relativePath '"]')->item(0);
                
$fileinfoNode $filePathNode->parentNode;

                if (!
$fileinfoNode) {
                    die(
"XML path error filePath={$filePath} relativePath={$relativePath} projectRootDir={$projectRootDir} docRoot={$_SERVER['DOCUMENT_ROOT']}");
                }

                
#Adding detection info to DOM
                
if ($fileinfoNode) {
                    
$fileinfoNode->setAttribute('detected'$detected);
                    
$fileinfoNode->setAttribute('snippet'$snippet);
                    
$fileinfoNode->setAttribute('pos'$pos);
                }
            }
        }

        
$xml->save($xmlLogFilename);

        return 
$xml->saveXML();
    }

    function 
buildQuarantineArchive()
    {
        if (
file_exists($this->MALWARE_QUARANTINE_FILENAME)) {
            
$list file($this->MALWARE_QUARANTINE_FILENAME);

            if (
count($list) > 0) {
                
$quarantiner = new Quarantiner();
                foreach (
$list as $filename) {
                    
$quarantiner->add(trim($filename));
                }

                return 
$quarantiner->getArchive();
            }
        }

        return 
null;
    }

    function 
finishMalwareScan()
    {
        global 
$php_errormsg;

        
$xml $this->repackXMLLog();

        if (
file_exists($this->MALWARE_LOG_FILENAME)) {
            
unlink($this->MALWARE_LOG_FILENAME);
        }
        if (
file_exists($this->QUEUE_OFFSET_FILENAME)) {
            
unlink($this->QUEUE_OFFSET_FILENAME);
        }
        if (
file_exists($this->QUEUE_FILENAME)) {
            @
unlink($this->QUEUE_FILENAME);
        }

        
$quarantineFilepath $this->buildQuarantineArchive();
        if (
$quarantineFilepath) {
            
file_put_contents2($this->MALWARE_QUARANTINE_FILEPATH_FILEPATH$quarantineFilepath);
        }

        if (
file_exists($this->MALWARE_QUARANTINE_FILENAME)) {
            @
unlink($this->MALWARE_QUARANTINE_FILENAME);
        }

        
$result json_encode(array('type' => 'getSignatureScanResult''status' => 'finished''phpError' => $php_errormsg));
        return 
$result;
    }

    function 
malwareScanRound()
    {

        global 
$php_errormsg;

        
$startTime time();

        if (!
is_file($this->QUEUE_FILENAME)) {
            die(
basename(__FILE__) . ': cannot open ' $this->QUEUE_FILENAME ' on scan round');
        }

        
$fh fopen($this->QUEUE_FILENAME'r');
        
$offset 0;

        if (
file_exists($this->QUEUE_OFFSET_FILENAME)) {
            
$offset = (int)file_get_contents($this->QUEUE_OFFSET_FILENAME);
            
fseek($fh$offset);
        }

        if (
filesize($this->QUEUE_FILENAME) - $offset <= 0) {
            
fclose($fh);
            return 
$this->finishMalwareScan();
        }

        
$queueText fread($fhfilesize($this->QUEUE_FILENAME) - $offset);
        
$queueLines explode("n"$queueText);

        
$numFilesScanned 0;

        if (
count($queueLines) < 1) {
            
fclose($fh);
            return 
$this->finishMalwareScan();
        }

        foreach (
$queueLines as $line) {
            
$executionTime time() - $startTime;
            if (
$executionTime >= round($this->MAX_EXECUTION_DURATION 0.8)) {
                break;
            } else if (empty(
$line)) {
                continue;
            }

            
$offset += strlen($line) + 1;

            
$fileinfo explode(' '$line);

            
$filePath $fileinfo[0];
            
$fileHash $fileinfo[1];

            
$snippet '';
            
$fileExtension pathinfo(basename($filePath), PATHINFO_EXTENSION);

            
$res $this->detectMalware($filePath$snippet$pos$this->SCRIPT_START$this->MAX_EXECUTION_DURATION$fileExtension);
            switch (
$res) {
                case 
'no_need_to_check':
                    break;
                case 
'no_read':
                    break;
                case 
'skipped':
                    break;
                case 
'timeout':
                    
file_put_contents2($this->QUEUE_OFFSET_FILENAME$offset);
                    
$this->throwTimeout($filePath);
                    break;
                default:
                    
$numFilesScanned++;
                    
$content $filePath ';detected=' $res ';pos=' $pos ';snippet=' base64_encode($snippet) . PHP_EOL;
                    
file_put_contents2($this->MALWARE_LOG_FILENAME$content'a');

                    if (
$res) {
                        
$this->queueQuarantine($filePath);
                    }
            }
        }

        
file_put_contents2($this->QUEUE_OFFSET_FILENAME$offset);
        
fclose($fh);

        if (
count($queueLines) <= 1) {
            return 
$this->finishMalwareScan();
        }

        
$data = array('filesScannedThisTime' => $numFilesScanned'filesLeft' => count($queueLines), 'lastFile' => $filePath);
        
$result json_encode(array('type' => 'getSignatureScanResult''status' => 'inProcess''data' => $data'phpError' => $php_errormsg));
        return 
$result;

    }


// End of class
Онлайн: 3
Реклама