SHARE
TWEET

abonk

a guest Aug 4th, 2012 194 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <?php
  2. /*      lookforbadguys.php               2012-04-09
  3.         Copyright (C)2012 Karen Chun, Steven Whitney.
  4.         Initially published by http://25yearsofprogramming.com.
  5.  
  6.         This program is free software; you can redistribute it and/or
  7.         modify it under the terms of the GNU General Public License (GPL)
  8.         Version 3 as published by the Free Software Foundation.
  9.         This program is distributed in the hope that it will be useful,
  10.         but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.         GNU General Public License for more details.
  13.         You should have received a copy of the GNU General Public License
  14.         along with this program; if not, write to the Free Software
  15.         Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  16.  
  17. --Purpose: iterate through server files looking for hacker code snippets, backdoor scripts,
  18.   suspicious .htaccess code, suspicious file names.
  19.   Suspicious things to search for are stored in easily modifiable lists of regular expressions.
  20.  
  21. --Tested with PHP 5.2 and 5.3. It might work with earlier versions.
  22. --It is designed for use in either Linux or Windows.
  23.   On my system, it runs much slower in Windows.
  24.  
  25. --Not all things it finds are hacks. Not all hacks are found.
  26. --You should also search manually for weird files (such as .php files) in your image directories,
  27.   especially if your .htaccess has redirects or was made executable.
  28. --Some searches are commented out because they can give too many false positives.
  29.  
  30. ----------
  31. CHANGELOG:
  32.  
  33. --2011-03-08 First published
  34.  
  35. --2011-09-08 Steven Whitney
  36.   1. Rewrote the recursive directory search function FindAndProcessFiles().
  37.   2. Added ability to exit with 'Forbidden' message unless request is from a specific IP address.
  38.   3. Changed malicious snippet regexes to allow for any whitespace, not just spaces, between function name and "(".
  39.   4. Renamed $SuspiciousFileAndPathNames array to $SuspiciousFileNames because path names are not tested.
  40.   5. Revised comments.
  41.   6. Published under GPL3 license.
  42.  
  43. --2011-09-10 Steven Whitney
  44.   1. Added global variables to store counts of files/directories processed, and functions to reset/report them.
  45.      Added a global array variable to store the list of files that matched the regex(es).
  46.   2. Broke apart FindAndProcessFiles() into two functions:
  47.      
  48.      1) BuildFileList() only traverses the filesystem and builds the list of files matching the regex.
  49.      This makes it a general purpose file-find search function like the Linux "find" utility program.
  50.      The regex for selecting filenames can either be a single regex string or an array of them.
  51.      If it is an array of regexes, a file is added to the list if its name matches any of them.
  52.      
  53.      2) The new FindAndProcessFiles() calls BuildFileList() to build the file list, sorts the list,
  54.      and then applies the handler function to each file in the list.
  55.  
  56. --2011-09-13 Steven Whitney
  57.   1. Added GetCanonicalPath() and $UseAbsoluteFilePaths to control whether paths are absolute or relative.
  58.   2. Reorganized code blocks so that important user-configuration settings are at the top,
  59.      data arrays and handler functions for each search are grouped together,
  60.      and support functions are all at the end.
  61.   3. Added $FullpathExcludeRegexes and code using it, to allow excluding files from examination.
  62.      FindAndProcessFiles() and BuildFileList() both take 1 more argument.
  63.      A file is added to the file list if its name matches the include regexes,
  64.      UNLESS its fullpath also matches any of the $FullpathExcludeRegexes.
  65.      dirs are always traversed, but individual files in them can all be excluded from examination.
  66.      This allows dirs to be excluded with or without excluding their subdirs.
  67.      Example exclusion: '#/tiny_mce/.*\.php$#i'
  68.   4. In the search routines, one set of variables is reused instead of using different variable names,
  69.      to make it clear that there are 3 routines basically doing the same thing.
  70.      Only the data and handler functions change.
  71.   5. maliciouscodesnippets() renamed to FindMaliciousCodeSnippets.
  72.      In snippet search output, each file is only listed once.
  73.      After that, a list of all threats found, with initial portion of the matching strings for visual review.
  74.      Moved the special cases (RewriteRule, AddHandler, <script, <iframe) into normal snippet search array.
  75.          Moved the lookforbadguys.php exclusion into the fullpath exclusion array.
  76.   6. Changed default script execution time limit from unlimited to 5 minutes.
  77.   7. Changed CleanColorText() to allow numeric colors: #FFFFFF.
  78.   8. Some <script and <iframe detection is now enabled by default, with some of the
  79.      most common safe sources of them as exceptions.
  80.  
  81. --2011-09-14 Steven Whitney
  82.   1. In FindMaliciousCodeSnippets(), moved the option to print each filename processed to end of function
  83.      and changed its method.
  84.   2. In BuildFileList(), moved the test for whether a file matches the fullpath exclusion
  85.      so that it is only performed if the file has matched the inclusion criteria
  86.          (this fixed an inefficiency in previous version).
  87.  
  88. --2011-09-27 Steven Whitney
  89.   1. Revised the base64_decode regex to show more matched text, if present.
  90.   2. Revised the backtick operator regex to be less greedy and show individual occurrences.
  91.  
  92. --2012-04-09 Steven Whitney
  93.   1. Added regexes to $SuspiciousSnippets array to find preg_replace with /e (eval) modifier.
  94.   2. New user configuration option: date_default_timezone_set().
  95.   3. Output lines showing filenames now also show the file's last-modified timestamp.
  96.   4. New search routine can report all files modified within a specified date/time range.
  97.   5. Output colors are defined in an array to make it easier to change them.
  98.  
  99. ----
  100.  
  101. */
  102.  
  103. # ================================================================================
  104. # USER CONFIGURATION SECTION
  105. # ================================================================================
  106.  
  107. /*     
  108. The next line only allows the script to run if the request came from your IP address.
  109. It allows you to put the script in a public folder but prevent others from running it.
  110. Change the IP address to yours. (127.0.0.1 is localhost.)
  111. */
  112.  
  113. if($_SERVER['REMOTE_ADDR'] !== '39.211.212.164') exit('Forbidden');  // ganti ipnya dengan ip komputer kita, supaya hanya bisa di jalankan oleh kita sendiri
  114.  
  115.  
  116. /*
  117. Searches will be done in this directory and all dirs inside it.
  118. The default of './' means current directory, where this script is now.
  119. Thus, to search everything inside public_html, that's where this file should be put.
  120. To search outside public_html, or to search a folder other than where this script is stored,
  121. change this to the full pathname, such as /home/userid/ or /home/userid/public_html/somefolder/.
  122. Always use forward slashes for the path. Windows example: C:/wamp/apache2/htdocs/test/
  123. */
  124.  
  125. $StartPath = './';
  126.  
  127.  
  128. # TRUE  = report shows full file paths such as /home/userid/public_html/blog/...
  129. # FALSE = report shows relative file paths such as ./blog/...
  130.  
  131. $UseAbsoluteFilePaths = TRUE;
  132.  
  133.  
  134. # These set maximum execution time, in seconds. The script can take a while.
  135. # These have no effect if you run PHP in "safe mode" (safe mode is usually undesirable).
  136. # Set to '0' for unlimited.
  137.  
  138. ini_set('max_execution_time', '300');
  139. ini_set('set_time_limit', '300');
  140.  
  141. ini_set('display_errors', '1');         # 1=TRUE, ensure that you see errors such as time-outs.
  142.  
  143.  
  144. /*
  145. The timezone must be given a value (any legal value) to avoid a PHP warning
  146. every time the date() function is called.
  147. Ideally, you should enter in the line below the correct timezone
  148. that your server uses for its file timestamps.
  149. The PHP manual at http://www.php.net/manual/en/timezones.php
  150. has list of supported timezone strings.
  151. */
  152.  
  153. date_default_timezone_set('Asia/Jakarta');
  154.  
  155.  
  156. /*
  157. The program can optionally search for files with suspicious last-modified timestamps.
  158. To use that feature, define here the time window you consider suspicious,
  159. such as the period during which you know files were being modified by a hack.
  160. The search for suspicious timestamps is not performed
  161. unless you define a more recent time window here than the examples shown.
  162.  
  163. REQUIRED FORMAT is "YYYY-MM-DD HH:MM:SS"
  164. */
  165.  
  166. $TimeRangeStart = '1980-04-09 05:01:05';
  167. $TimeRangeEnd   = '1980-04-10 23:59:59';
  168.  
  169.  
  170. # TEXT COLORS IN AN ARRAY, TO MAKE IT EASIER TO CHANGE THEM.
  171.  
  172. $Colors = array
  173. (
  174.         'timestamp'  => '#808080',      # FILE TIMESTAMPS
  175.         'filename'   => 'blue',         # FILE PATHS AND NAMES
  176.         'suspicious' => 'red',          # WARNINGS, AND REGULAR EXPRESSIONS
  177.         'status'     => 'green',        # STATUS MESSAGES
  178.         'snippet'    => 'black'         # TEXT OF SUSPICIOUS SNIPPETS
  179. );
  180.  
  181. # ================================================================================
  182. # GLOBAL VARIABLES
  183. # ================================================================================
  184.  
  185. # Besides being useful, reporting the counts helps ensure that
  186. # new recursion methods work the same as the old.
  187.  
  188. $FilesCount = 0;
  189. $FilesMatchedCount = 0;
  190. $DirectoriesCount = 0;
  191. $DirectoriesMatchedCount = 0;
  192.  
  193. # This array must be global because the function that builds it is re-entrant.
  194.  
  195. $AllFilesToProcess = array();  
  196.  
  197.  
  198. ?>
  199. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  200. <head>
  201. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  202. <meta http-equiv="Content-Language" content="en-us">
  203. <title>Looking for bad guys</title>
  204. </head>
  205.  
  206. <body>
  207. <p>Looking for bad guys. </p>
  208. <p>This script looks for traces of malicious code including code injections,
  209. modified .htaccess that makes images executable, and so on.</p>
  210. <p>
  211.  
  212. <?php
  213.  
  214. $RealPath = GetCanonicalPath($StartPath);
  215. if($RealPath === FALSE)
  216.         exit(CleanColorText("Cannot continue. The starting directory is inaccessible to PHP.", $Colors['suspicious']) . "<br>");
  217. if($UseAbsoluteFilePaths)
  218.         $StartPath = $RealPath;
  219.  
  220.  
  221. # ================================================================================
  222. # START OF SEARCH ROUTINES.
  223. # ================================================================================
  224. /*
  225. This program does two things: 1) finds files, and 2) does something with each one.
  226.  
  227. When designing a search, the two questions to ask are:
  228.  
  229. 1) Which types of files (by their names) do I want to find or perform an action on?
  230. 2) What action do I want to do on each one?
  231.  
  232. Each search requires these data items to be defined:
  233.  
  234. 1) An array that is a list of Perl-Compatible Regular Expressions (PCRE) of filenames to match.
  235.    The program searches directories for all the filenames that match any of the regexes.
  236.  
  237. 2) Another array that is a list of PCREs of fullpaths NOT to match.
  238.    This allows excluding files in certain directories.
  239.    If a file's NAME matches any regex in list 1)
  240.    and its PATH+NAME does NOT match any regexes list 2) (the exclusions),
  241.    its name gets passed to the handler function.
  242.  
  243. 3) The handler function. It can perform any action you want on the file whose name is given to it.
  244.    Some of the handler functions below merely report that the filename is suspicious, but do nothing else.
  245.    Another handler searches the file extensively for malicious snippets and reports each one found.
  246.    You could write a handler that automatically cleans the snippet out of the file,
  247.    or even deletes the file automatically. The handler can do anything.
  248.  
  249. */
  250.  
  251. # ================================================================================
  252. # 1) SUSPICIOUS FILENAMES.
  253. # Files with these strings in their *names* will be reported as suspicious.
  254. # There is currently no method provided to check for suspiciously named folders.
  255. # ================================================================================
  256. # FILENAMES TO MATCH
  257.  
  258. $FileMatchRegexes = array
  259. (
  260. #       '/root/i',
  261. #       '/kit/i',
  262.         '/c(99|100)/i',
  263.         '/r57/i',
  264.         '/gifimg/i'
  265. );
  266. # AND FULLPATHS TO EXCLUDE FROM EXAMINATION
  267.  
  268. $FullpathExcludeRegexes = array
  269. (
  270.         '#lookforbadguys\.php$#i'
  271. );
  272.  
  273. # --------------------------------------------------------------------------------
  274. # HANDLER FUNCTION - THIS IS THE ACTION PERFORMED ON A FILE WHOSE NAME IS A MATCH.
  275.  
  276. function badnames($filename)
  277. {
  278.         global $Colors;
  279.        
  280.         echo
  281.                 CleanColorText(date('Y-m-d H:i:s ', filemtime($filename)), $Colors['timestamp']) .
  282.                 CleanColorText($filename, $Colors['filename']) .
  283.                 " is a " .
  284.                 CleanColorText('suspicious file name', $Colors['suspicious']) . ".<br>";
  285. }  
  286.  
  287. # --------------------------------------------------------------------------------
  288. # THIS CODE ACTUALLY DOES THE SEARCH.
  289.  
  290. echo CleanColorText("Searching for files with suspicious names...", $Colors['status']) . "<br>";
  291.  
  292. FindAndProcessFiles($StartPath, $FileMatchRegexes, $FullpathExcludeRegexes, 'badnames');
  293.  
  294.  
  295. # ================================================================================
  296. # 2) WORDPRESS PHARMA HACK SUSPICIOUS FILENAMES.
  297. # Files matching these names will be reported as possible pharma hack files.
  298. # Regexes are based on the naming conventions described at
  299. # http://www.pearsonified.com/2010/04/wordpress-pharma-hack.php
  300. # ================================================================================
  301. # FILENAMES TO MATCH
  302.  
  303. $FileMatchRegexes = array
  304. (
  305.         '/^\..*(cache|bak|old)\.php/i', # HIDDEN FILES WITH PSEUDO-EXTENSIONS IN THE MIDDLE OF THE FILENAME
  306.         '/^db-.*\.php/i',
  307.  
  308.         # Permit the standard WordPress files that start with class-, but flag all others as suspicious.
  309.         # The (?!) is called a negative lookahead assertion. It means "not followed by..."
  310.  
  311.         '/^class-(?!snoopy|smtp|feed|pop3|IXR|phpmailer|json|simplepie|phpass|http|oembed|ftp-pure|wp-filesystem-ssh2|wp-filesystem-ftpsockets|ftp|wp-filesystem-ftpext|pclzip|wp-importer|wp-upgrader|wp-filesystem-base|ftp-sockets|wp-filesystem-direct)\.php/i'
  312. );
  313. # AND FULLPATHS TO EXCLUDE FROM EXAMINATION
  314.  
  315. $FullpathExcludeRegexes = array
  316. (
  317.         '#lookforbadguys\.php$#i'
  318. );
  319.  
  320. # --------------------------------------------------------------------------------
  321. # HANDLER FUNCTION - THIS IS THE ACTION PERFORMED ON A FILE WHOSE NAME IS A MATCH.
  322. function pharma($filename)
  323. {
  324.         global $Colors;
  325.  
  326.         echo
  327.                 CleanColorText(date('Y-m-d H:i:s ', filemtime($filename)), $Colors['timestamp']) .
  328.                 CleanColorText($filename, $Colors['filename']) .
  329.                 " is most likely a " .
  330.                 CleanColorText('pharma hack', $Colors['suspicious']) . ".<br>";
  331. }
  332.  
  333. # --------------------------------------------------------------------------------
  334. # THIS CODE ACTUALLY DOES THE SEARCH.
  335.  
  336. echo
  337.         "<br>" .
  338.         CleanColorText("Searching for files with names related to Wordpress pharma hack...", $Colors['status']) .
  339.         "<br>";
  340.  
  341. FindAndProcessFiles($StartPath, $FileMatchRegexes, $FullpathExcludeRegexes, 'pharma');
  342.  
  343.  
  344. # ================================================================================
  345. # 3) MALICIOUS CODE SNIPPETS.
  346. # Search text files for snippets of malicious code and report all that are found.
  347. # ================================================================================
  348. # FILENAMES TO MATCH
  349. # Ideally, this list should contain all common extensions of text files
  350. # that can become hazardous when malicious text is injected into them.
  351.  
  352. $FileMatchRegexes = array
  353. (
  354.         '/\.htaccess$/i',
  355.         '/\.php[45]?$/i',
  356.         '/\.html?$/i',
  357.         '/\.aspx?$/i',
  358.         '/\.inc$/i',
  359.         '/\.cfm$/i',
  360.         '/\.js$/i',
  361.         '/\.txt$/i',
  362.         '/\.css$/i'
  363. );
  364. # AND FULLPATHS TO EXCLUDE FROM EXAMINATION
  365.  
  366. $FullpathExcludeRegexes = array
  367. (
  368.         '#lookforbadguys\.php$#i'
  369. );
  370.  
  371. # --------------------------------------------------------------------------------
  372. # HANDLER FUNCTION - THIS IS THE ACTION PERFORMED ON A FILE WHOSE NAME IS A MATCH.
  373.  
  374. function FindMaliciousCodeSnippets($filename)
  375. {
  376.         global $Colors;
  377.  
  378.         if(!is_readable($filename))
  379.         {
  380.                 echo "Warning: Unable to read " . CleanColorText($filename, $Colors['filename']) .
  381.                         ". Check it manually and check its access permissions.<br>";
  382.                 return;
  383.         }
  384.  
  385.         # READ THE FILE INTO A STRING, WITH LINE ENDS REMOVED AND WHITESPACE COMPRESSED.
  386.         $file = file_get_contents($filename);  
  387.         $file = preg_replace('/\s+/', ' ', $file);  
  388.  
  389.         # The file is searched for each of these snippets of suspicious text.
  390.         # These are regular expressions with the required /DELIMITERS/ and with metachars escaped.
  391.         # /i at the end means case insensitive.
  392.         # PHP function names are case-insensitive.
  393.         # If your regex itself contains / chars, you can use a different
  394.         # char as a delimiter like this: '#delimited#i' to avoid confusion.
  395.        
  396.         $SuspiciousSnippets = array
  397.         (
  398.                 # POTENTIALLY SUSPICIOUS CODE
  399.  
  400.                 '/edoced_46esab/i',
  401.                 '/passthru\s*\(/i',
  402.                 '/shell_exec\s*\(/i',
  403.                 '/document\.write\s*\(unescape\s*\(/i',
  404.  
  405.                 # THESE CAN GIVE MANY FALSE POSITIVES WHEN CHECKING WORDPRESS AND OTHER CMS.
  406.                 # NONETHELESS, THEY CAN BE IMPORTANT TO FIND, ESPECIALLY BASE64_DECODE.
  407.  
  408.                 # THIS IS MUCH MORE SUSPICIOUS IF THE MATCHED TEXT CONTAINS THE EVAL() CODE.
  409.  
  410.                 '/(eval\s*\(.{0,40})?base64_decode\s*\(/i',
  411.  
  412.                 '/system\s*\(/i',              
  413.  
  414.                 # --------------------         
  415.                 # OCCURRENCES OF POSSIBLE PCRE PATTERNS WITH
  416.                 # THE -e (EVAL) PATTERN MODIFIER THAT IS USED BY PREG_REPLACE.
  417.  
  418.                 # (1) VARIABLE DEFINITIONS CONTAINING PCRE PATTERNS USING THE 'e' OPTION,
  419.                 # FOLLOWED FAIRLY CLOSELY BY A CALL TO PREG_REPLACE.
  420.                 # THE 2 VARIATIONS ALLOW FOR SINGLE AND DOUBLE-QUOTES IN THE VARIABLE DECLARATION.
  421.  
  422.                 '#\$\S+\s*=\s*[\x22]([^A-Za-z0-9[:space:]\x5C\x22])[^\x22]{0,75}\1[imsxADSUXJu]*e.{0,75}[pP][rR][eE][gG]_[rR][eE][pP][lL][aA][cC][eE]\s*\(#',   # CASE-SENSITIVE REQUIRED
  423.  
  424.                 '#\$\S+\s*=\s*[\x27]([^A-Za-z0-9[:space:]\x5C\x27])[^\x27]{0,75}\1[imsxADSUXJu]*e.{0,75}[pP][rR][eE][gG]_[rR][eE][pP][lL][aA][cC][eE]\s*\(#',   # CASE-SENSITIVE REQUIRED
  425.  
  426.                 # (2) PREG_REPLACE CALLS THAT USE THE -e OPTION.
  427.                 # THE 2 VARIATIONS ALLOW FOR SINGLE AND DOUBLE-QUOTES AROUND THE PCRE PATTERN.
  428.  
  429.                 '#[pP][rR][eE][gG]_[rR][eE][pP][lL][aA][cC][eE]\s*\(\s*[\x22]([^A-Za-z0-9[:space:]\x5C])[^\x22]{0,75}\1[imsxADSUXJu]*e#',       # CASE-SENSITIVE REQUIRED
  430.  
  431.                 '#[pP][rR][eE][gG]_[rR][eE][pP][lL][aA][cC][eE]\s*\(\s*[\x27]([^A-Za-z0-9[:space:]\x5C])[^\x27]{0,75}\1[imsxADSUXJu]*e#',       # CASE-SENSITIVE REQUIRED
  432.                 # --------------------         
  433.  
  434.                 # PHP BACKTICK OPERATOR INVOKES SYSTEM FUNCTIONS, SAME AS system(),
  435.                 # BUT THIS TEST CAN PRODUCE MANY FALSE POSITIVES BECAUSE
  436.                 # BACKTICKS ARE ALSO DATABASE,TABLE,FIELD NAME DELIMITERS IN SQL QUERIES.
  437.                 # MATCHED TEXT IS SUSPICIOUS IF IT CONTAINS OPERATING SYSTEM COMMANDS.
  438.                 # USUALLY NOT SUSPICIOUS IF IT CONTAINS DATABASE TABLE OR FIELD NAMES.
  439.  
  440.                 '/`[^`]+`/',           
  441.  
  442.                 '/phpinfo\s*\(/i',
  443.  
  444.                                                                 # THIS SET GENERATES MANY FALSE POSITIVES
  445. #               '/chmod\s*\(/i',
  446. #               '/mkdir\s*\(/i',
  447. #               '/fopen\s*\(/i',
  448. #               '/fclose\s*\(/i',
  449. #               '/readfile\s*\(/i',
  450.  
  451.                                                                 # THESE WERE PREVIOUSLY SPECIAL CASES; NOW MOVED INTO THIS ARRAY.
  452.                 '/RewriteRule\s/i',             # SUSPICIOUS IF THE DESTINATION IS A DIFFERENT SITE OR SUSPICIOUS FILE.
  453.                 '/AddHandler\s/i',              # THIS CAN MAKE IMAGE OR OTHER FILES EXECUTABLE.
  454.  
  455.  
  456.                 # JAVASCRIPT SNIPPETS WHOSE SRC= REFERENCES AN HTTP:// SOURCE OTHER THAN ONES KNOWN TO BE SAFE.
  457.                 # EVEN WITH EXCEPTIONS, THIS CAN GIVE MANY FALSE POSITIVES.
  458.                 '@<script[^>]+src=[\x22\x27]?http://(?!(www\.(google-analytics|gmodules)\.com|pagead2\.googlesyndication\.com/pagead/|(ws\.|((www|cls)\.assoc-))amazon\.com/))[^>]*>@i',                       
  459.  
  460.  
  461.                 # IFRAMES, WITH A KNOWN-HARMLESS EXCLUSION.
  462.                 # IFRAME SEARCH CAN GIVE MANY FALSE POSITIVES IN SOME WEBSITES.
  463.  
  464.                 '@<iframe[^>]+src=[\x22\x27]?http://(?!(rcm\.amazon\.com/))[^>]*>@i',                  
  465.  
  466.  
  467.                 # SUSPICIOUS NAMES. SOME HACKERS SIGN THEIR SCRIPTS. MANY NAMES COULD BE PUT INTO THIS LIST.
  468.                 # HERE IS A GENERIC EXAMPLE OF TEXT FROM A DEFACED WEB PAGE.
  469.  
  470.                 '/hacked by\s/i',
  471.  
  472.                 # OTHER SUSPICIOUS TEXT STRINGS
  473.  
  474.                 '/web[\s-]*shell/i',    # TO FIND BACKDOOR WEB SHELL SCRIPTS.
  475.                 '/c(99|100)/i',                 # THE NAMES OF SOME POPULAR WEB SHELLS.
  476.                 '/r57/i',
  477.                
  478.                 # YOU COULD/SHOULD ADD TO THIS LIST SOME REGULAR EXPRESSIONS TO MATCH THE NAMES OF
  479.                 # MALICIOUS DOMAINS AND IP ADDRESSES MENTIONED IN YOUR
  480.                 # GOOGLE SAFE BROWSING DIAGNOSTIC REPORT.
  481.                 # SOME EXAMPLES:
  482.  
  483.                 '/gumblar\.cn/i',
  484.                 '/martuz\.cn/i',
  485.                 '/beladen\.net/i',
  486.                 '/gooqle/i',                    # NOTE THIS HAS A Q IN IT.
  487. #               '/127\.0\.0\.1/',               # COMMENTED-OUT EXAMPLE OF AN IP ADDRESS REGEX
  488.  
  489.                 # THESE 2 ARE THE WORDPRESS CODE INJECTION IN FRONT OF EVERY INDEX.PHP AND SOME OTHERS
  490.  
  491.                 '/_analist/i',                  # EACH LIST ENTRY MUST BE TERMINATED WITH A COMMA...
  492.                 '/anaiytics/i'                  # EXCEPT THE LAST ENTRY MUST NOT HAVE A COMMA.
  493.  
  494.                
  495.         );
  496.  
  497.         # ACCUMULATES ALL THE WARNING MESSAGES FOR THIS FILE.
  498.         $OutputText = array
  499.         (
  500.                 CleanColorText(date('Y-m-d H:i:s ', filemtime($filename)), $Colors['timestamp']) .
  501.                 CleanColorText($filename, $Colors['filename'])
  502.         );
  503.  
  504.         # SEARCH THE FILE FOR EACH OF THE ABOVE SNIPPETS.
  505.         foreach($SuspiciousSnippets as $snippet)
  506.         {
  507.                 $matches = array();
  508.                 if($matchcount = preg_match_all($snippet, $file, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE))  
  509.                 {
  510.                         $i = 0;
  511.                         foreach($matches[0] as $occurrence)     # $occurrence is an array itself 0=>string, 1=>offset
  512.                         {
  513.                                 $i++;
  514.                                 # THE 80 CHARACTERS AFTER START OF MATCH INSTANCE
  515.                                 $s = substr($file, $occurrence[1], 80);
  516.                                 $newline = (($i === 1) ? '<br><br>' : '<br>');
  517.                                 $OutputText[] = $newline .
  518.                                                                 CleanColorText("Regex ($i of $matchcount): ", $Colors['snippet']) .
  519.                                                                 CleanColorText($snippet, $Colors['suspicious']) .
  520.                                                                 CleanColorText(": " . $s, $Colors['snippet']);
  521.                         }
  522.                 }
  523.         }
  524.        
  525.         # REPORT ALL THREAT MESSAGES AT ONCE, IF THERE WERE ANY.
  526.         # TO PRINT EVERY FILENAME EXAMINED, MAKE THE THRESHOLD 0.
  527.         if(count($OutputText) > 1)
  528.         {
  529.                 foreach($OutputText as $s)
  530.                         echo $s;
  531.                 echo '<br><br>';
  532.         }
  533.        
  534. }
  535.  
  536. # --------------------------------------------------------------------------------
  537. # THIS CODE ACTUALLY DOES THE SEARCH.
  538.  
  539. echo
  540.         "<br>" .
  541.         CleanColorText("Searching for files containing suspicious code or other text...", $Colors['status']) .
  542.         "<br>";
  543.  
  544. FindAndProcessFiles($StartPath, $FileMatchRegexes, $FullpathExcludeRegexes, 'FindMaliciousCodeSnippets');
  545.  
  546. # ================================================================================
  547. # 4) SUSPICIOUS TIMESTAMPS.
  548. # If you have found hacked files with timestamps showing that they were all modified
  549. # at about the same date and time, you can use this routine to locate other files
  550. # that were modified at about the same time.
  551. # Define the suspicious time range in the User Configuration section near top of this script.
  552. # Files with timestamps within that date/time range will be reported as suspicious.
  553. # ================================================================================
  554. # FILENAMES TO MATCH
  555.  
  556. $FileMatchRegexes = array
  557. (
  558.         '/\.htaccess$/i',
  559.         '/\.php[45]?$/i',
  560.         '/\.html?$/i',
  561.         '/\.aspx?$/i',
  562.         '/\.inc$/i',
  563.         '/\.cfm$/i',
  564.         '/\.js$/i',
  565.         '/\.txt$/i',
  566.         '/\.css$/i'
  567. );
  568. # AND FULLPATHS TO EXCLUDE FROM EXAMINATION
  569.  
  570. $FullpathExcludeRegexes = array
  571. (
  572.         '#lookforbadguys\.php$#i'
  573. );
  574.  
  575. # --------------------------------------------------------------------------------
  576. # HANDLER FUNCTION - THIS IS THE ACTION PERFORMED ON A FILE WHOSE NAME IS A MATCH.
  577.  
  578. function SuspiciousTimestamp($filename)
  579. {
  580.         global $TimeRangeStart, $TimeRangeEnd, $Colors;
  581.  
  582.         $lastmod = date('Y-m-d H:i:s', filemtime($filename));
  583.         if(($lastmod >= $TimeRangeStart) && ($lastmod <= $TimeRangeEnd))
  584.         {
  585.                 echo
  586.                         CleanColorText($lastmod . ' ', $Colors['timestamp']) .
  587.                         CleanColorText($filename, $Colors['filename']) .
  588.                         " has a " .
  589.                         CleanColorText('suspicious timestamp', $Colors['suspicious']) . ".<br>";
  590.         }
  591. }  
  592.  
  593. # --------------------------------------------------------------------------------
  594. # THIS CODE ACTUALLY DOES THE SEARCH.
  595.  
  596. # TO AVOID WASTING TIME WHEN A TIMESTAMP SEARCH ISN'T NEEDED,
  597. # THIS CODE DOES NOT RUN UNTIL A MORE RECENT TIME WINDOW HAS BEEN DEFINED
  598. # IN THE USER CONFIGURATION SECTION NEAR THE TOP OF THE SCRIPT.
  599.  
  600. if(substr($TimeRangeStart, 0, 4) > '1980')
  601. {
  602.         echo
  603.                 "<br>" .
  604.                 CleanColorText("Searching for files with timestamps in the suspicious date/time range...",
  605.                         $Colors['status']) .
  606.                 "<br>";
  607.  
  608.         FindAndProcessFiles($StartPath, $FileMatchRegexes, $FullpathExcludeRegexes, 'SuspiciousTimestamp');
  609. }
  610.  
  611. # ================================================================================
  612. # END OF THE SEARCH ROUTINES
  613. # ================================================================================
  614. # ================================================================================
  615. # FUNCTION LIBRARY
  616. # --------------------------------------------------------------------------------
  617. # Output text in specified color, cleaning it with htmlentities().
  618. # Malicious text snippets could by definition be hazardous, so
  619. # always use this to put text on the web page  
  620. # unless it is going into a text (input) box or textarea.
  621.  
  622. function CleanColorText($text, $color)
  623. {
  624.         $outputcolor = 'black';
  625.         $color = trim($color);
  626.         if(preg_match('/^(red|blue|green|black|#[0-9A-F]{6})$/i', $color))
  627.                 $outputcolor = $color;
  628.         return '<span style="color:' . $outputcolor . ';">' . htmlentities($text, ENT_QUOTES) . '</span>';
  629. }
  630.  
  631. # --------------------------------------------------------------------------------
  632.  
  633. function ResetCounts()
  634. {
  635.         global $FilesCount, $FilesMatchedCount,
  636.                         $DirectoriesCount, $DirectoriesMatchedCount, $AllFilesToProcess, $Colors;
  637.  
  638.         $FilesCount = $FilesMatchedCount = $DirectoriesCount = $DirectoriesMatchedCount = 0;
  639.         $AllFilesToProcess = array();
  640. }
  641.  
  642. # --------------------------------------------------------------------------------
  643.  
  644. function ShowCounts()
  645. {
  646.         global $FilesCount, $FilesMatchedCount,
  647.                         $DirectoriesCount, $DirectoriesMatchedCount, $Colors;
  648.  
  649.         $s =    "Files encountered = $FilesCount" . ', ' .
  650.                         "Matching regex and processed = $FilesMatchedCount" . '; ' .
  651.                         "Directories encountered = $DirectoriesCount" . ', ' .
  652.                         "Matched and processed = $DirectoriesMatchedCount";
  653.  
  654.         echo CleanColorText($s, $Colors['status']) . "<br>";
  655. }
  656.  
  657. # --------------------------------------------------------------------------------
  658. # Returns path translated to canonical absolute filesystem path,
  659. # or FALSE if it fails (path does not exist or PHP cannot enter/read it).
  660.  
  661. function GetCanonicalPath($path)
  662. {
  663.         # CLEAN IT UP AND CONVERT TO STANDARD PHP FORMAT (/)
  664.         $path = str_replace('\\', '/', $path);
  665.         $path = rtrim($path, '/');
  666.         $path .= '/';
  667.  
  668.         $RealPath = realpath($path);    # FALSE IF PHP CANNOT READ ANY DIR IN HIERARCHY
  669.         if($RealPath === FALSE)
  670.                 return FALSE;
  671.  
  672.         $RealPath = str_replace('\\', '/', $RealPath);
  673.         $RealPath = rtrim($RealPath, '/');
  674.         $RealPath .= '/';
  675.  
  676.         return $RealPath;
  677. }
  678.  
  679. # --------------------------------------------------------------------------------
  680. /*
  681. Recursively search the starting directory and all below it to find files whose names
  682. match the given regex(es).
  683.  
  684. Since this performs no action on the files found, it is now a generic file-finder
  685. like the Linux "find" command. You can do whatever you want with the list once it's built.
  686.  
  687. $FileMatchRegexes can be either a string or an array. Passing them all at once
  688. allows the filesystem to be traversed only once to find all matches (20+% faster).
  689. */
  690.  
  691. function BuildFileList($StartDir, $FileMatchRegexes, $FullpathExcludeRegexes)
  692. {
  693.         # NOTE THAT THIS FUNCTION REQUIRES THE GLOBAL VARIABLES DECLARED EARLIER.
  694.         global $FilesCount, $FilesMatchedCount,
  695.                         $DirectoriesCount, $DirectoriesMatchedCount,
  696.                         $AllFilesToProcess, $Colors;
  697.  
  698.         # CHANGE BACKSLASHES TO FORWARD, WHICH IS OK IN PHP, EVEN IN WINDOWS.
  699.         # THEN REMOVE ANY TRAILING SLASHES AND ADD EXACTLY ONE.
  700.         $StartDir = str_replace('\\', '/', $StartDir);
  701.         $StartDir = rtrim($StartDir, '/');
  702.         $StartDir .= '/';
  703.  
  704.         # ENSURE THAT THE CURRENT DIRECTORY EXISTS AND IS READABLE BY PHP.
  705.         if(!is_dir($StartDir))
  706.         {
  707.                 echo "Warning: Directory does not exist: " . CleanColorText($StartDir, $Colors['filename']) . "<br>";
  708.                 return;
  709.         }
  710.         $DirectoriesCount++;            # COUNT IT AS A DIRECTORY (READABLE OR NOT)
  711.         if(!is_readable($StartDir))
  712.         {
  713.                 echo CleanColorText("Warning: Directory is not readable by PHP: ", $Colors['suspicious']) .
  714.                                 CleanColorText($StartDir, $Colors['filename']) .
  715.                                 ". Check its owner/group permissions.<br>";
  716.                 return;
  717.         }
  718.  
  719.         # THE DIR IS READABLE, SO IT WILL BE PROCESSED.
  720.         # A DIR IS NEVER ACTUALLY EXCLUDED FROM PROCESSING UNLESS IT CAN'T BE READ.
  721.         # ONLY FILES ARE AFFECTED BY THE EXCLUSION RULES.
  722.         $DirectoriesMatchedCount++;    
  723.  
  724.         # IF THESE ARE NOT ARRAYS, TURN THEM INTO ARRAYS.
  725.         if(!is_array($FileMatchRegexes))
  726.                 $FileMatchRegexes = array($FileMatchRegexes);
  727.         if(!is_array($FullpathExcludeRegexes))
  728.                 $FullpathExcludeRegexes = array($FullpathExcludeRegexes);
  729.  
  730.         # DETERMINE IF EACH ENTRY IN THE CURRENT DIRECTORY IS A CANDIDATE FOR INCLUSION IN THE FILE LIST.
  731.         $dir = dir($StartDir);
  732.         while(($filename = $dir->read()) !== FALSE)
  733.         {
  734.                 $fullname = $dir->path . $filename;
  735.                 if(is_file($fullname))
  736.                 {
  737.                         $FilesCount++;  # ADD IT TO THE COUNT OF *ALL* FILES, PROCESSED OR NOT.
  738.  
  739.                         # IF ITS NAME MATCHES ANY OF THE REGEXES, IT MIGHT GO INTO THE LIST...
  740.                         $matches = 0;
  741.                         foreach($FileMatchRegexes as $regex)
  742.                         {
  743.                                 if(preg_match($regex, $filename))
  744.                                 {
  745.                                         $matches = 1;
  746.                                         # UNLESS ITS FULLPATH MATCHES ANY OF THE EXCLUSION REGEXES.
  747.                                         foreach($FullpathExcludeRegexes as $exclude)
  748.                                         {
  749.                                                 if(preg_match($exclude, $fullname))
  750.                                                 {
  751.                                                         $matches = 0;
  752.                                                         break;
  753.                                                 }
  754.                                         }
  755.                                         break;
  756.                                 }
  757.                         }
  758.                         if($matches)
  759.                         {
  760.                                 $FilesMatchedCount++;
  761.                                 $AllFilesToProcess[] = $fullname;
  762.                         }
  763.                 }
  764.                 else if(is_dir($fullname))
  765.                 {
  766.                         # ELSE IF IT IS A DIRECTORY AND NOT THE CURRENT ONE OR ITS PARENT,
  767.                         # RECURSIVELY CALL THIS FUNCTION TO PROCESS ALL *ITS* ENTRIES
  768.                         # BEFORE CONTINUING WITH THE CURRENT DIRECTORY.
  769.  
  770.                         if(($filename !== '.') && ($filename !== '..'))
  771.                                 BuildFileList($fullname, $FileMatchRegexes, $FullpathExcludeRegexes);
  772.                 }
  773.         }
  774.         $dir->close();
  775. }
  776.  
  777. # --------------------------------------------------------------------------------
  778. # BUILD A MASTER LIST OF ALL THE FILES TO PROCESS,
  779. # THEN SORT THE ARRAY AND PROCESS ALL ITS ENTRIES AT ONCE.
  780.  
  781. function FindAndProcessFiles($StartDir, $FileMatchRegexes, $FullpathExcludeRegexes, $FileHandlerFunction)
  782. {
  783.         global $AllFilesToProcess;
  784.  
  785.         ResetCounts(); 
  786.         BuildFileList($StartDir, $FileMatchRegexes, $FullpathExcludeRegexes);
  787.         sort($AllFilesToProcess, SORT_STRING);
  788.         foreach($AllFilesToProcess as $filename)
  789.         {
  790.                 call_user_func($FileHandlerFunction, $filename);
  791.         }
  792.         ShowCounts();
  793. }
  794.  
  795. # --------------------------------------------------------------------------------
  796. # END FUNCTION LIBRARY
  797. # ================================================================================
  798.  
  799. echo "<br>" . CleanColorText("Done!", $Colors['status']) . "<br>";
  800.  
  801. ?>
  802.  
  803. </p>
  804. </body>
  805. </html>
RAW Paste Data
Top