[code] // Copyright Oliver Lillie 2011 // $Id: Search.php 1938 2011-06-09 13:25:22Z buggedcom $ $Rev: 1938 $ class Security_Directory_Integrity { public static function listFiles($options=array()) { $options = array_merge(array( 'ignored_directories' => Configuration::read('security/directory_integrity:ignored_directories'), 'extensions' => Configuration::read('security/directory_integrity:extensions'), 'sniff' => Configuration::read('security/directory_integrity:sniff'), ), $options); $directories = array(rtrim(PATH, DS)); $files = array(); $extensions = count($options['extensions']) > 0 ? array_flip($options['extensions']) : array(); $extensions['php'] = 1; $ignored_directories = $options['ignored_directories']; foreach ($ignored_directories as $key => $path) { $path = rtrim($path, DS); if(strpos($path, DS) !== 0) { $ignored_directories[$key] = PATH.$path; } } $ignored_directories = array_flip($ignored_directories); $depth = 999; while($directory = array_shift($directories)) { // should this directy be ignored? $directory = rtrim($directory, DS); if(isset($ignored_directories[$directory]) === true) { continue; } $file_list = glob($directory.DS.'*'); if($file_list !== false) { foreach ($file_list as $path) { $is_file = is_file($path); $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); if(($extensions !== true && isset($extensions[$ext]) === false) || $is_file === false) { continue; } $size = 0; $hash = ''; $fmtime = filemtime($path); if($is_file === true) { $size = filesize($path); $hash = md5_file($path); } $file_data = array( 'extension' => $ext, 'is_file' => $is_file, 'size' => $size, 'hash' => $hash, 'last_modified' => $fmtime, 'contains_php' => null, ); if($options['sniff'] === true) { $contains_php = false; if($ext !== 'php') { $contents = Files::get($path); $contains_php = strpos($contents, ' SITE_DB_ID )); $file_list = self::listFiles(); $inserts = array(); $date = date('Y-m-d H:i:s'); foreach ($file_list as $path=>$data) { array_push($inserts, array( 'file_site_id' => $path === PATH.'index.php' || $path === PATH.'.htaccess' || strpos($path, PATH.'application'.DS) === 0 || strpos($path, PATH.'assets'.DS.'system'.DS) === 0 ? 0 : SITE_DB_ID, 'file_date_added' => $date, 'file_date_last_modified' => date('Y-m-d H:i:s', $data['last_modified']), 'file_size' => $data['size'], 'file_hash' => $data['hash'], 'file_path' => $path, 'file_extension' => $data['extension'], 'file_contains_php' => $data['contains_php'] === true ? 'yes' : 'no', 'file_is_file' => $data['is_file'] === true ? 'yes' : 'no', )); if(count($inserts) >= 100) { Database::insert('T_security_integrity_files', $inserts); $inserts = array(); } } if(count($inserts) > 0) { Database::insert('security_integrity_files', $inserts); } } public static function compareManifest($options=array()) { $options = array_merge(array( 'ignored_directories' => Configuration::read('security/directory_integrity:ignored_directories'), 'extensions' => Configuration::read('security/directory_integrity:extensions'), 'sniff' => Configuration::read('security/directory_integrity:sniff'), ), $options); $return = array( 'new' => array(), 'changed' => array(), 'missing' => array(), 'warning' => array(), ); $rows = Database::read( 'SELECT * FROM T_security_integrity_files WHERE 1;'); if($rows !== false) { $current_files = self::listFiles($options); foreach ($rows as $key => $row) { $path = $row->file_path; if(isset($current_files[$path]) === false) { array_push($return['missing'], $path); unset($current_files[$path]); } else { $current_data = $current_files[$path]; if(strtotime($row->file_date_last_modified) !== $current_data['last_modified'] || ((int) $row->file_size) !== $current_data['size'] || $row->file_hash !== $current_data['hash'] || $row->file_contains_php !== ($current_data['contains_php'] === true ? 'yes' : 'no') ) { array_push($return['changed'], $path); } else if($row->file_extension !== 'php' && $row->file_contains_php === 'yes') { array_push($return['warning'], $path); } unset($current_files[$path]); } } if(count($current_files) > 0) { $return['new'] = array_keys($current_files); foreach ($current_files as $path=>$data) { if($data['extension'] !== 'php' && $data['contains_php'] === true) { array_push($return['warning'], $path); } } } } return $return; } public static function updateManifestFile($path, $options=array()) { if(file_exists($path) === false || is_dir($path) === true) { return false; } $options = array_merge(array( 'ignored_directories' => Configuration::read('security/directory_integrity:ignored_directories'), 'extensions' => Configuration::read('security/directory_integrity:extensions'), 'sniff' => Configuration::read('security/directory_integrity:sniff'), ), $options); $extensions = count($options['extensions']) > 0 ? array_flip($options['extensions']) : array(); $extensions['php'] = 1; $ext = pathinfo($path, PATHINFO_EXTENSION); if(isset($extensions[$ext]) === false) { return false; } $ignored_directories = $options['ignored_directories']; foreach ($ignored_directories as $key => $directory_path) { $directory_path = rtrim($directory_path, DS); if(strpos($directory_path, DS) !== 0) { $directory_path = PATH.$directory_path; } if(strpos($path, $directory_path) === 0) { return false; } } $fmtime = filemtime($path); $size = filesize($path); $hash = md5_file($path); $contains_php = null; if($options['sniff'] === true) { $contains_php = false; if($ext !== 'php') { $contents = Files::get($path); $contains_php = strpos($contents, ' $path === PATH.'index.php' || $path === PATH.'.htaccess' || strpos($path, PATH.'application'.DS) === 0 || strpos($path, PATH.'assets'.DS.'system'.DS) === 0 ? 0 : SITE_DB_ID, 'file_date_added' => date('Y-m-d H:i:s'), 'file_date_last_modified' => date('Y-m-d H:i:s', $fmtime), 'file_size' => $size, 'file_hash' => $hash, 'file_path' => $path, 'file_extension' => $ext, 'file_contains_php' => $contains_php === true ? 'yes' : 'no', 'file_is_file' => 'yes', )); } } [/code] Here is the table to store the manifests files [code]CREATE TABLE `security_integrity_files` ( `file_site_id` mediumint(3) DEFAULT NULL, `file_date_added` datetime DEFAULT NULL, `file_date_last_modified` datetime DEFAULT NULL, `file_size` int(11) DEFAULT NULL, `file_hash` varchar(100) DEFAULT NULL, `file_path` varchar(255) NOT NULL DEFAULT '', `file_extension` varchar(8) DEFAULT NULL, `file_contains_php` enum('no','yes') DEFAULT 'no', `file_is_file` enum('no','yes') DEFAULT 'no', PRIMARY KEY (`file_path`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;[/code] Here is the default config I use. [code] $configuration = array ( 'ignored_directories' => array ( 0 => 'application/data/', ), 'extensions' => array ( 1 => 'html', 2 => 'js', 3 => 'css', 4 => 'swf', 5 => 'htaccess', ), 'sniff' => true, ); [/code]