http://cyberspark.net/webmasters This is a standalone, not to be confused with the Cloudkick 'agent' plugin version, which has some similar functionality. /help Describes what options are available. /path Reports the filesystem path to this script. Does nothing else. /report /report=n Spiders the site and builds a baseline set of hashes or lengths. Does nothing active. If "n" (a number) is present the site is spidered only to this maximum depth. It's best to start with 1 or 2 so as not to overburden your server. /report=n&base=xxxxxxx Prepares a report using "xxxxxxx" as the base subdirectory and a depth of "n" REPAIR CAPABILITIES ARE NOT PRESENT IN THIS SCRIPT. Create a directory /cyberspark within the docroot of the web server Make this directory world-writeable (chmod 777 or chmod a+rwx ) Within your Apache (or other) web server configuration, add: deny from all So the directory cannot be shown to the outside world. **/ $depth = 0; // current spidering depth (recursive calls) $maxDepth = 50; // number of levels 'deep' to spider $results = array(); $status = array(); $newFiles = 0; // number files found on this scan that weren't present previous time $newSizes = 0; // number of files that have changed size $newSuspect = 0; // number of files containing suspect functions like 'eval()' $totalFiles = 0; // number of files seen $phpFiles = 0; // number of PHP files examined $path = ''; $base = ''; $report = false; $views = 0; $wrap = 0; $wrapAt = 250; $maxFileSize= 1000000; // maximum file size that we will open and inspect $maxDataSize= 4000000; // maximum serialized data file size function readData($path, $base) { global $fileHandle; global $status; global $maxDataSize; if (isset($base) && (strlen($base)>0)) { $cleanBase = str_replace('/','-',$base); } else { $cleanBase = ''; } if($fileHandle = @fopen("$path"."cyberspark/".$cleanBase."spiderlength.txt","r+")) { while (!feof($fileHandle)) { $status = unserialize(fread($fileHandle, $maxDataSize)); } fclose($fileHandle); } } function writeData($path, $base) { global $results; global $fileHandle; if (isset($base) && (strlen($base)>0)) { $cleanBase = str_replace('/','-',$base); } else { $cleanBase = ''; } if($fileHandle = fopen("$path"."cyberspark/".$cleanBase."spiderlength.txt","w+")) { rewind($fileHandle); fwrite($fileHandle, serialize($results)); fclose($fileHandle); } } function paramValue($paramName) { if (isset($_GET[$paramName])) { // try { if (($md = @intval($_GET[$paramName])) > 0) return $md; // } // catch (Exception $pvx) { // } } if (isset($_POST[$paramName])) { // try { if (($md = @intval($_POST[$paramName])) > 0) return $md; // } // catch (Exception $pvx) { // } } // No parameter, return "0" which is "failure" return 0; } function nValue() { // Several options allow "=n" to specify the spidering depth. Such as: // cyberspark-utility.php?report=3 // This function looks for the "n" (which means really the value of the // parameter "repair" "remove" or "report") and sets $maxDepth accordingly. // $maxDepth remains untouched if there is no "=n" global $maxDepth; if (($md = paramValue('report')) > 0) { return $md; } } function spiderThis($baseDirectory, $maxDepth) { global $depth; // current depth of spidering global $results; // current 'signatures' of files global $status; // previous 'signatures' of files global $newFiles; // number of new files found during this scan global $newSizes; // number of files that changed size global $wrap; global $wrapAt; global $newSuspect; // number of files containing 'suspect' PHP functions eval() base64_decode() etc. global $maxFileSize; global $phpFiles; global $totalFiles; // Be sure we're working with a directory if (is_dir($baseDirectory) && ($maxDepth>$depth)) { $wrap = $wrap + 1; if ($wrap >= $wrapAt) { echo "
\r\n"; $wrap = 1; } echo "."; // try { $depth++; $dirContents = dir($baseDirectory); // Run through this directory while (($entry = $dirContents->read()) !== false) { // Next entry in the directory $thisEntry = $baseDirectory.$entry; if ((strcmp('.',$entry)<>0) && (strcmp('..',$entry)<>0) && is_dir($thisEntry)) { // Next entry is a directory, dive into it spiderThis($thisEntry."/", $maxDepth); } else if (is_link($thisEntry)) { // Skip 'link' (not directory, not file) avoids recursion } else if (is_file($thisEntry)) { $stat = stat($thisEntry); $totalFiles++; // MD5: use this to record md5 hashes of files rather than lengths // but this will be much more time-consuming than just looking at lengths. // $filemd5s[] = md5_file($thisentry); // Record file lengths $fileSize = $stat['size']; $results[$thisEntry] = $fileSize; if ($status[$thisEntry] <> $fileSize) { if ($status[$thisEntry] == 0) { echo "
\nNew file: ".$status[$thisEntry]." -> [".$fileSize."] $thisEntry "; $newFiles++; } else { $len = strlen($thisEntry); if(($len > 3) and !(strpos(strtolower($thisEntry), "log", $len-3) == ($len-3))) { echo "
\nNew size: ".$status[$thisEntry]." -> [".$fileSize."] $thisEntry "; $newSizes++; } } } // And scan PHP files for eval and gzinflate and base64 // try { $len = strlen($thisEntry); if(($len > 4) and (strpos(strtolower($thisEntry), ".php", $len-4) == ($len-4))) { $thisFile = fopen($thisEntry,"r"); $thisContents = fread($thisFile, $maxFileSize); fclose($thisFile); $phpFiles++; if (strlen($thisContents) > 0) { if (strpos(strtolower($thisContents), "eval(") !== false) { echo "
\nFound eval(): -> $thisEntry"; $newSuspect++; } else if (strpos(strtolower($thisContents), "gzinflate(") !== false) { echo "
\nFound gzinflate(): -> $thisEntry "; $newSuspect++; } else if (strpos(strtolower($thisContents), "base64_decode(") !== false) { echo "
\nFound base64_decode(): -> $thisEntry "; $newSuspect++; } } } // } // catch (Exception $egbx) { // } // Remove from 'previous status' array. When we finish, anything left in // this array will be a file that has disappeared. unset($status[$thisEntry]); } // Otherwise ignore ("." and ".." for instance) } $depth--; // } // catch (Exception $x) { // echo "
\r\nException: $x->getMessage()
\r\n"; // } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // MAIN script - this is executed when the file is invoked by HTTP GET or HTTP PUT // Get the filesystem path to this file (only the PATH) including an ending "/" $path = substr(__FILE__,0,strrpos(__FILE__,'/')+1); // including the last "/" // '/help' - - - - - - - - - - - - if (isset($_GET['help'])) { // /help echo "\n /help
/path
/report
/report=n
/report=n&base=xxxxxxx

Produces a report on status of all PHP files. If 'n' (a number) is present the site is spidered only to this maximum depth. (Start by using 1 or 2 for the depth until you know how quickly your server can perform this task.)

Run several times to establish a baseline, then in the future you can watch for any significant changes.

If base=xxxxxxx is specified then the report starts at directory /xxxxxxx with respect to where the CyberSpark PHP is located

\n\n"; return; } // '/path' - - - - - - - - - - - - - if (isset($_GET['path'])) { // /path echo "\r\n\r\n
$path
\r\n\r\n\r\n"; return; } // '/report' detection - - - - - - - if (isset($_GET['report']) or isset($_POST['report'])) { // Set flag for actions to perform $report = true; } // '=n' detection+processing - - - - $maxDepth = nValue(); // 'base=xxxxxxx' detection+processing if ($report) { if (isset($_GET['base'])) { $base = $_GET['base'] . '/'; } if (isset($_POST['base'])) { $base = $_POST['base'] . '/'; } if (strpos($base, '..') !== false) { // Don't permit /../ anywhere in the base string // Just fall back to the directory containing this script $base = ''; } } // header for reports - - - - - - - - if ($report) { // /report $fullPath = $path . $base; echo "

CyberSpark local agent report
Base directory is $fullPath
Spidering depth will be $maxDepth
Any changes reported below are 'since the last time this script was run.'
Do not stop this script or leave this page until it finishes.

"; // The html is closed off later on after the scan has been completed } // read previous file status info - - - - readData($path, $base); // the bulk of processing HERE - - - - - if ($report) { spiderThis($path . $base, $maxDepth); // spider with a maximum depth Example: "1" limits to top directory writeData($path, $base); echo "
\r\n"; } // reporting out - - - - - - - - - - - - - if ($report) { echo "\r\n

- - - - - - - - - - - - - - - - -
\n"; echo "Summary

\n"; if ($phpFiles > 0) { echo "
\r\nPHP files examined: $phpFiles
\n"; echo "PHP files with suspicious code: $newSuspect
\n"; } if (sizeof($status) > 0) { echo "
\r\nFiles gone:
\n"; foreach ($status as $key=>$value) { echo "[$value] $key
\n"; } } } // /report if ($report) { echo "New files: $newFiles
\n"; echo "Changed sizes: $newSizes
\n"; echo "Files gone: ".sizeof($status)."
\n"; } // closing the HTML - - - - - - - - - - - - echo "\r\n\r\n\r\n"; ?>