530 lines
13 KiB
PHP
530 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* <pre>
|
|
* Invision Power Services
|
|
* IP.Board v3.2.3
|
|
* Virus scanner
|
|
* Last Updated: $Date: 2011-07-06 16:54:25 -0400 (Wed, 06 Jul 2011) $
|
|
* </pre>
|
|
*
|
|
* @author $Author: bfarber $
|
|
* @copyright (c) 2001 - 2009 Invision Power Services, Inc.
|
|
* @license This is NULLED!
|
|
* @package IP.Board
|
|
* @link http://hatynka.in
|
|
* @since Tue. 17th August 2004
|
|
* @version $Rev: 9172 $
|
|
*
|
|
*/
|
|
|
|
if ( ! defined( 'IN_IPB' ) )
|
|
{
|
|
print "<h1>Incorrect access</h1>You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files.";
|
|
exit();
|
|
}
|
|
|
|
class virusChecker
|
|
{
|
|
/**
|
|
* Directory separator
|
|
*
|
|
* @access public
|
|
* @var string
|
|
*/
|
|
public $dir_split = "/";
|
|
|
|
/**
|
|
* Dodgy files
|
|
*
|
|
* @access public
|
|
* @var array
|
|
*/
|
|
public $bad_files = array();
|
|
|
|
/**
|
|
* Checked folders
|
|
*
|
|
* @access public
|
|
* @var string
|
|
*/
|
|
public $checked_folders = array();
|
|
|
|
/**
|
|
* Known names
|
|
*
|
|
* @access public
|
|
* @var string
|
|
*/
|
|
public $known_names = array();
|
|
|
|
/**#@+
|
|
* Registry Object Shortcuts
|
|
*
|
|
* @access protected
|
|
* @var object
|
|
*/
|
|
protected $registry;
|
|
protected $DB;
|
|
protected $settings;
|
|
protected $request;
|
|
protected $lang;
|
|
protected $member;
|
|
protected $memberData;
|
|
protected $cache;
|
|
protected $caches;
|
|
/**#@-*/
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @access public
|
|
* @param object ipsRegistry reference
|
|
* @return @e void
|
|
*/
|
|
public function __construct( ipsRegistry $registry )
|
|
{
|
|
//-----------------------------------------
|
|
// Make object references
|
|
//-----------------------------------------
|
|
|
|
$this->registry = $registry;
|
|
$this->DB = $registry->DB();
|
|
$this->settings = $registry->settings();
|
|
$this->member = $registry->member();
|
|
$this->memberData =& $registry->member()->fetchMemberData();
|
|
$this->cache = $registry->cache();
|
|
$this->caches =& $registry->cache()->fetchCaches();
|
|
$this->request = $registry->request();
|
|
|
|
set_time_limit(0);
|
|
|
|
if( strpos( strtolower( PHP_OS ), 'win' ) === 0 )
|
|
{
|
|
$this->dir_split = "\\";
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Known names
|
|
//-----------------------------------------
|
|
$KNOWN_NAMES = array();
|
|
require( IPS_ROOT_PATH . 'sources/classes/virusChecker/lib_known_names.php' );/*noLibHook*/
|
|
|
|
$this->known_names = $KNOWN_NAMES;
|
|
}
|
|
|
|
/**
|
|
* Runs the scan
|
|
* All suspicious files are entered into
|
|
* $this->bad_files array
|
|
*
|
|
* @access public
|
|
* @return @e void
|
|
*/
|
|
public function runScan()
|
|
{
|
|
//-----------------------------------------
|
|
// INIT
|
|
//-----------------------------------------
|
|
|
|
$expected = array();
|
|
$root_dir = preg_replace( "#^(.+?)\/$#", "\\1", DOC_IPS_ROOT_PATH );
|
|
$skin_dirs = array();
|
|
|
|
//-----------------------------------------
|
|
// Get libs
|
|
//-----------------------------------------
|
|
$WRITEABLE_DIRS = array();
|
|
require( IPS_ROOT_PATH . 'sources/classes/virusChecker/lib_writeable_dirs.php' );/*noLibHook*/
|
|
|
|
//-----------------------------------------
|
|
// Sort out directory separator
|
|
//-----------------------------------------
|
|
|
|
if ( $this->dir_split != '/' )
|
|
{
|
|
$_WRITEABLE_DIRS = $WRITEABLE_DIRS;
|
|
$WRITEABLE_DIRS = array();
|
|
|
|
foreach( $_WRITEABLE_DIRS as $dir )
|
|
{
|
|
$WRITEABLE_DIRS[] = str_replace( '/' , $this->dir_split, $dir );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Get language directories
|
|
//-----------------------------------------
|
|
|
|
if ( ipsRegistry::cache()->getCache('lang_data') AND is_array( ipsRegistry::cache()->getCache('lang_data') ) && count( ipsRegistry::cache()->getCache('lang_data') ) )
|
|
{
|
|
foreach( ipsRegistry::cache()->getCache('lang_data') as $v )
|
|
{
|
|
$WRITEABLE_DIRS[] = 'cache' . $this->dir_split . 'lang_cache' . $this->dir_split . $v['lang_id'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->DB->build( array( 'select' => 'lang_id', 'from' => 'core_sys_lang' ) );
|
|
$this->DB->execute();
|
|
|
|
while( $v = $this->DB->fetch() )
|
|
{
|
|
$WRITEABLE_DIRS[] = 'cache' . $this->dir_split . 'lang_cache' . $this->dir_split . $v['lang_id'];
|
|
}
|
|
}
|
|
|
|
ipsRegistry::DB()->build( array( 'select' => 'lang_id, word_app, word_pack', 'from' => 'core_sys_lang_words' ) );
|
|
ipsRegistry::DB()->execute();
|
|
|
|
while( $r = ipsRegistry::DB()->fetch() )
|
|
{
|
|
$expected[ $r['word_app'] . '_' . $r['word_pack'] ] = 'cache' . $this->dir_split . 'lang_cache' . $this->dir_split . $r['lang_id'] . $this->dir_split . $r['word_app'] . '_' . $r['word_pack'] . '.php';
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Get skin directories
|
|
//-----------------------------------------
|
|
|
|
if ( is_array( ipsRegistry::cache()->getCache('skinsets') ) && count( ipsRegistry::cache()->getCache('skinsets') ) )
|
|
{
|
|
foreach( ipsRegistry::cache()->getCache('skinsets') as $k => $v )
|
|
{
|
|
if ( $k == 1 && !IN_DEV )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$WRITEABLE_DIRS[] = 'cache' . $this->dir_split . 'skin_cache' . $this->dir_split . 'cacheid_' . $v['set_id'];
|
|
$skin_dirs[] = $v['set_id'];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->DB->build( array( 'select' => 'set_id', 'from' => 'skin_collections' ) );
|
|
$this->DB->execute();
|
|
|
|
while( $v = $this->DB->fetch() )
|
|
{
|
|
$WRITEABLE_DIRS[] = 'cache' . $this->dir_split . 'skin_cache' . $this->dir_split . 'cacheid_' . $v['set_id '];
|
|
$skin_dirs[] = $v['set_id'];
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Get skin files
|
|
//-----------------------------------------
|
|
|
|
$this->DB->build( array( 'select' => $this->DB->buildDistinct( 'template_group' ),
|
|
'from' => 'skin_templates',
|
|
'group' => 'template_group'
|
|
) );
|
|
$this->DB->execute();
|
|
|
|
while( $v = $this->DB->fetch() )
|
|
{
|
|
foreach( $skin_dirs as $dir )
|
|
{
|
|
$expected[] = 'cache' . $this->dir_split . 'skin_cache' . $this->dir_split . 'cacheid_' . $dir . $this->dir_split . $v['template_group'] . '.php';
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Alright, do it!
|
|
//-----------------------------------------
|
|
|
|
$WRITEABLE_DIRS = array_unique($WRITEABLE_DIRS);
|
|
|
|
foreach( $WRITEABLE_DIRS as $dir_to_check )
|
|
{
|
|
if ( $dir_to_check == 'uploads' OR $dir_to_check == 'style_emoticons' )
|
|
{
|
|
# Leave this 'til later
|
|
continue;
|
|
}
|
|
|
|
if ( file_exists( $root_dir . $this->dir_split . $dir_to_check ) )
|
|
{
|
|
$this->checked_folders[] = $root_dir . $this->dir_split . $dir_to_check;
|
|
|
|
$dh = opendir( $root_dir . $this->dir_split . $dir_to_check );
|
|
|
|
while ( false !== ( $file = readdir($dh) ) )
|
|
{
|
|
if ( preg_match( "#.*\.(php|js|html|htm|cgi|pl|perl|php3|php4|php5|php6)$#i", $file ) )
|
|
{
|
|
if ( ! in_array( $dir_to_check . $this->dir_split . $file, $expected ) AND $file != "index.html" AND $file != 'lang_javascript.js' )
|
|
{
|
|
$score = intval( $this->_scoreFile( $root_dir . $this->dir_split . $dir_to_check . $this->dir_split . $file ) );
|
|
|
|
$this->bad_files[] = array( 'file_path' => $root_dir . $this->dir_split . $dir_to_check . $this->dir_split . $file,
|
|
'file_name' => $file,
|
|
'score' => $score );
|
|
}
|
|
}
|
|
}
|
|
|
|
@closedir($dh);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Check 'blog' dir
|
|
//-----------------------------------------
|
|
|
|
if ( file_exists( $root_dir . $this->dir_split . 'blog' ) )
|
|
{
|
|
$this->deepScan( $root_dir . $this->dir_split . 'blog', 'all', array( 'index.php' ) );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Check 'html' dir
|
|
//-----------------------------------------
|
|
|
|
if ( file_exists( $root_dir . $this->dir_split . 'html' ) )
|
|
{
|
|
$this->deepScan( $root_dir . $this->dir_split . 'html', 'all', array( 'index.php' ) );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Check 'Skin' dir
|
|
//-----------------------------------------
|
|
|
|
if ( file_exists( $root_dir . $this->dir_split . 'Skin' ) )
|
|
{
|
|
$this->deepScan( $root_dir . $this->dir_split . 'Skin', 'all' );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Check emoticons
|
|
//-----------------------------------------
|
|
|
|
$this->deepScan( $root_dir . $this->dir_split . PUBLIC_DIRECTORY . '/style_emoticons', 'all' );
|
|
|
|
//-----------------------------------------
|
|
// Check image directories
|
|
//-----------------------------------------
|
|
|
|
$this->deepScan( $root_dir . $this->dir_split . PUBLIC_DIRECTORY . '/style_images', '(php|cgi|pl|perl|php3|php4|php5|php6|phtml|shtml)' );
|
|
|
|
//-----------------------------------------
|
|
// Check upload directories
|
|
//-----------------------------------------
|
|
|
|
$this->deepScan( $root_dir . $this->dir_split . 'uploads' );
|
|
}
|
|
|
|
/**
|
|
* Score a file
|
|
*
|
|
* Return a score from 0 being harmless to 10 being, well the complete opposite to harmless.
|
|
*
|
|
* Score information:
|
|
* Name 3 chars or less + 2
|
|
* '- Name 2 chars or less + 4
|
|
* Size over 65k + 3
|
|
* '- Size over 100k + 4
|
|
* User nobody + 3
|
|
* Modified in the 30 days + 1
|
|
* In non PHP folder + 3
|
|
*
|
|
* @access protected
|
|
* @param string Full file path and name
|
|
* @param array stat info
|
|
* @return int Score (0 - 10 )
|
|
*/
|
|
protected function _scoreFile( $file_name, $stat=array() )
|
|
{
|
|
//-----------------------------------------
|
|
// INIT
|
|
//-----------------------------------------
|
|
|
|
$SCORE = 0;
|
|
$name = preg_replace( "#^(.*)/(.+?)$#is" , "\\2", $file_name );
|
|
$name_sans_ext = preg_replace( "#^(.*)\.(.+?)$#si", "\\1", $name );
|
|
|
|
//-----------------------------------------
|
|
// Check
|
|
//-----------------------------------------
|
|
|
|
if ( ! $file_name )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if ( ! is_array( $stat ) OR ! count( $stat ) )
|
|
{
|
|
$stat = stat( $file_name );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Alright...
|
|
//-----------------------------------------
|
|
|
|
if ( in_array( $file_name, $this->known_names ) )
|
|
{
|
|
$SCORE += 7;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// User nobody?
|
|
//-----------------------------------------
|
|
|
|
if ( $stat['uid'] == 99 )
|
|
{
|
|
$SCORE += 3;
|
|
}
|
|
|
|
if ( strlen( $name_sans_ext ) < 3 )
|
|
{
|
|
$SCORE += 4;
|
|
}
|
|
else if ( $name_sans_ext == 'temp' OR $name_sans_ext == 'test' )
|
|
{
|
|
$SCORE +=2;
|
|
}
|
|
else if ( strlen( $name_sans_ext ) < 4 )
|
|
{
|
|
$SCORE += 2;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Size
|
|
//-----------------------------------------
|
|
|
|
if ( $stat['size'] > 100 * 1024 )
|
|
{
|
|
$SCORE += 4;
|
|
}
|
|
else if ( $stat['size'] > 65 * 1024 )
|
|
{
|
|
$SCORE += 3;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Last modified...
|
|
//-----------------------------------------
|
|
|
|
if ( $stat['mtime'] > time() - 86400 * 30 )
|
|
{
|
|
$SCORE += 1;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Non PHP folder...
|
|
//-----------------------------------------
|
|
|
|
if ( preg_match( "#(?:style_images|style_emoticons|uploads|default|1|skin_acp|html|skin)/#i", $file_name ) )
|
|
{
|
|
$SCORE += 3;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Run any plugins
|
|
//-----------------------------------------
|
|
|
|
if( is_dir( IPS_ROOT_PATH . '/sources/classes/virusChecker/plugins' ) )
|
|
{
|
|
try
|
|
{
|
|
foreach( new DirectoryIterator( IPS_ROOT_PATH . '/sources/classes/virusChecker/plugins' ) as $file )
|
|
{
|
|
if ( $file->isFile() )
|
|
{
|
|
$_name = $file->getFileName();
|
|
|
|
if( strpos( $_name, '.php' ) )
|
|
{
|
|
$classToLoad = IPSLib::loadLibrary( $file->getPathname(), 'virusScannerPlugin_' . str_replace( '.php', '', $_name ) );
|
|
|
|
if( class_exists( $classToLoad ) )
|
|
{
|
|
$plugin = new $classToLoad( $this->registry );
|
|
|
|
if( method_exists( $plugin, 'run' ) )
|
|
{
|
|
$SCORE += $plugin->run( $file_name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch ( Exception $e ) {}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Return
|
|
//-----------------------------------------
|
|
|
|
return $SCORE > 10 ? 10 : $SCORE;
|
|
}
|
|
|
|
/**
|
|
* Deep scan
|
|
*
|
|
* All suspicious files are entered into
|
|
* $this->bad_files array
|
|
*
|
|
* @access public
|
|
* @param string Directory
|
|
* @param string Regex to look for
|
|
* @param array Files to ignore
|
|
*/
|
|
public function deepScan( $dir, $look_for='(php|js|html|htm|cgi|pl|perl|php3|php4|php5|php6|htaccess|so|phtml|shtml)', $ignore_files=array() )
|
|
{
|
|
//-----------------------------------------
|
|
// Short-hand
|
|
//-----------------------------------------
|
|
|
|
if ( $look_for == 'all' )
|
|
{
|
|
$look_for = '(php|js|html|htm|cgi|pl|perl|php3|php4|php5|php6|htaccess|so|phtml|shtml)';
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Add into checked folders
|
|
//-----------------------------------------
|
|
|
|
$this->checked_folders[] = $dir . $this->dir_split;
|
|
|
|
$dh = opendir( $dir );
|
|
|
|
while ( false !== ( $file = readdir($dh) ) )
|
|
{
|
|
if ( IN_DEV )
|
|
{
|
|
if ( $file == '.' or $file == '..' or $file == '.svn' or $file == '.DS_store' or $file == 'index.html' )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( $file == '.' or $file == '..' )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( is_dir( $dir . $this->dir_split . $file ) )
|
|
{
|
|
$this->deepScan( $dir . $this->dir_split . $file, $look_for );
|
|
}
|
|
else
|
|
{
|
|
if ( in_array( $dir . $this->dir_split . $file, $ignore_files ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( preg_match( "#^(.*)?\." . $look_for . "(?:\..+?)?$#i", $file ) )
|
|
{
|
|
$score = intval( $this->_scoreFile( $dir . $this->dir_split . $file ) );
|
|
|
|
$this->bad_files[] = array( 'file_path' => $dir . $this->dir_split . $file,
|
|
'file_name' => $file,
|
|
'score' => $score );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |