├── README.md ├── sficscan.php └── LICENSE.md /README.md: -------------------------------------------------------------------------------- 1 | SFIC 2 | ==== 3 | 4 | Simple File Integrity Checker 5 | 6 | This simple script created to help web admins be aware of intrusion of a malicious code to a web site. It scans specified directory and notifies web admin by e-mail in case of changes between scans. 7 | 8 | it supposed to run on a regular basis using CRON or occasionally using a web browser. 9 | -------------------------------------------------------------------------------- /sficscan.php: -------------------------------------------------------------------------------- 1 |
";
12 |
13 | // Start scan from the directory when the script resides or specify address
14 | $scandir = dirname($_SERVER['SCRIPT_FILENAME']);
15 | // $scandir = '/home/users/d/dima/';
16 |
17 | // Detect server name or specify manually
18 | if(isset($_SERVER['HTTP_HOST'])) {
19 | $servername = $_SERVER['HTTP_HOST'];
20 | } elseif(isset($_SERVER['SERVER_NAME'])) {
21 | $servername = $_SERVER['SERVER_NAME'];
22 | } else {
23 | $servername ="Unknown";
24 | }
25 |
26 | $datafilename = "data.sfic";
27 | $logfilename = "sfic.log";
28 |
29 | date_default_timezone_set("UTC");
30 | /**
31 | * Exclude File List - Separate each entry with a semicolon ;
32 | * Full filename including path and extension. [CASE INSENSITIVE]
33 | */
34 | $excludeFileList = "administrator/components/com_sh404sef/security/sh404SEF_AntiFlood_Data.dat;error_log;backup.zip";
35 |
36 | /**
37 | * Exclude Extension List - Separate each entry with a semicolon ;
38 | * Only extension type. [CASE INSENSITIVE]
39 | * Do not leave trailing semicolon!
40 | */
41 | $excludeExtensionList = "sfic;log;bak;xls";
42 |
43 | /**
44 | * Exclude directory List - Separate each entry with a semicolon ;
45 | * Only relative dir name including trailing dir separator. [CASE INSENSITIVE]
46 | * Do not leave trailing semicolon!
47 | */
48 | $excludeDirList = "cache/";
49 |
50 | /**
51 | * Default comparison mode:
52 | * attributes - by modification timestamp and size
53 | * content - by file content
54 | */
55 | $defaultmode = "attributes";
56 |
57 | /**
58 | * Set emailAddressToAlert variable if you want an email alert from the server.
59 | */
60 | $emailAddressToAlert = "admin@example.com";
61 | $emailSubject = "Files on the '$servername' Web server have changed";
62 |
63 | $debug = true;
64 | /**
65 | * Scan Password - This value has to be sent each time to run the code.
66 | * Please change from the default password to anything you like
67 | */
68 | $scanPassword = "pass";
69 |
70 | /**
71 | * Scan Password - This value has to be sent each time to run the code.
72 | * Please change from the default password to anything you like
73 | */
74 |
75 | function slog($string) {
76 | global $logfilename;
77 | $loghandle=fopen($logfilename,"a");
78 | fwrite($loghandle,$string);
79 | fclose($loghandle);
80 | }
81 | /**********************************************************
82 | * Start with logic of scanning and checking the code files
83 | ***********************************************************/
84 |
85 | //Key steps in scan
86 | //STEP 1 - Check if the password is OK. Currently disabled due to a bug
87 | if (strcmp ( $_REQUEST["password"], $scanPassword ) != 0 )
88 | {
89 | echo "Failed to start as password is incorrect!";
90 | if(!$debug) exit(0);
91 | }
92 |
93 | //STEP 2 - Check if user has sent the mode (otherwise use default mode)
94 |
95 | $mode = $defaultMode;
96 |
97 | $availableModes = ['attributes', 'content'];
98 | if(isset($_REQUEST['mode']) && in_array($_REQUEST['mode'], $availableModes)){
99 | $mode = $_REQUEST['mode'];
100 | }
101 |
102 | //STEP 2 - prepare exclusion data
103 |
104 | if (isset($excludeDirList)) {
105 | $offdir=explode(';',strtolower($excludeDirList));
106 | } else {
107 | $offdir=array();
108 | }
109 |
110 | if (isset($excludeFileList)) {
111 | $offfile=explode(';',strtolower($excludeFileList));
112 | } else {
113 | $offfile=array();
114 | }
115 |
116 | if (isset($excludeExtensionList)) {
117 | $offext=explode(';',strtolower($excludeExtensionList));
118 | } else {
119 | $offext=array();
120 | }
121 |
122 |
123 | //STEP 3 - Check if previously saved data exists and use it
124 |
125 | if(substr($scandir,strlen($scandir)-1)!==DIRECTORY_SEPARATOR) $scandir.=DIRECTORY_SEPARATOR;
126 |
127 | $olddata=array();
128 | if (file_exists($scandir.$datafilename)) {
129 | $datafile=fopen($scandir.$datafilename,"r");
130 | if ($datafile) {
131 | while (($buffer = fgets($datafile)) !== false) {
132 | $line=explode("\t",str_replace("\n","",$buffer));
133 | $entry=array(
134 | "namehash" => $line[0],
135 | "checkhash" => $line[1],
136 | "date" => $line[2],
137 | "size" => $line[3],
138 | "name" => $line[4],
139 | "ext" => $line[5],
140 | );
141 | $path_parts = pathinfo(strtolower($entry['name']));
142 | $processpath=true;
143 | foreach ($offdir as $dir)
144 | if($dir==substr($entry["name"],0,min(strlen($dir),strlen($entry["name"]))))
145 | $processpath=false;
146 | $fpath=substr($entry['name'],0,strlen($entry['name'])-strlen($path_parts['basename']));
147 | if(!(in_array(strtolower($entry['ext']),$offext)) && $processpath && !(in_array(strtolower($entry['name']),$offfile))) {
148 | $olddata[$line[0]]=$entry;
149 | }
150 | }
151 | fclose($datafile);
152 | }
153 | }
154 | if(count($olddata)>0) $oldsettings=array_shift($olddata);
155 |
156 | if (!file_exists($scandir)) {
157 | slog("Directory $scandir does not exist.\n");
158 | exit (2);
159 | }
160 | $changed=array();
161 | $deleted=array();
162 | $added=array();
163 | $newdata=array();
164 | slog(date("Y-m-d H:i:s")." Processing '$scandir'\n");
165 | $it = new RecursiveDirectoryIterator($scandir);
166 | $iterator = new RecursiveIteratorIterator($it);
167 | $fff = iterator_to_array($iterator, true);
168 |
169 | foreach($fff as $filename) {
170 | $shortname=substr($filename, strlen($scandir));
171 | $justname=basename($filename);
172 | $fpath=substr($shortname,0,strlen($shortname)-strlen($justname));
173 | $path_parts = pathinfo($filename);
174 | $extension=strtolower($path_parts ['extension']);
175 |
176 | $processpath=true;
177 | foreach ($offdir as $dir)
178 | if($dir==substr($shortname,0,min(strlen($dir),strlen($shortname))))
179 | $processpath=false;
180 |
181 | if(!in_array(strtolower($extension),$offext) && $processpath && !in_array(strtolower($shortname),$offfile)) {
182 |
183 | switch ($mode) {
184 | case 'attributes':
185 | $fhash=md5(filesize($filename).filemtime($filename));
186 | break;
187 | case 'content':
188 | $fhash=md5_file($filename);
189 | break;
190 | default:
191 | $fhash="Wrong mode specified";
192 | }
193 |
194 | $filedata=array(
195 | "namehash" => md5($shortname),
196 | "checkhash" => $fhash,
197 | "date" => date("Y-m-d H:i:s",filemtime($filename)),
198 | "size" => filesize($filename),
199 | "name" => $shortname,
200 | "ext" => $extension,
201 | );
202 |
203 | $newdata[$filedata["namehash"]]=$filedata;
204 |
205 | if(isset($olddata[$filedata["namehash"]])) {
206 | if($olddata[$filedata["namehash"]]["checkhash"]==$filedata["checkhash"]) {
207 | } else {
208 | $changed[$filedata["namehash"]]["old"]=$olddata[$filedata["namehash"]];
209 | $changed[$filedata["namehash"]]["new"]=$filedata;
210 | }
211 | unset($olddata[$filedata["namehash"]]);
212 | } else {
213 | if(stripos($filedata["checkhash"],"excluded")===false) $added[$filedata["namehash"]]=$filedata;
214 | }
215 |
216 | }
217 | }
218 | If(count($olddata)>0) {
219 | foreach($olddata as $index=>$filedata) {
220 | if(stripos($filedata["checkhash"],"excluded")===false) $deleted[$index]=$filedata;
221 | }
222 |
223 | }
224 |
225 | //STEP 4 - Notify admin in case of changes
226 | $changes ="";
227 | if(count($changed)>0) {
228 | $changes .= "Changed:\n";
229 | foreach($changed as $filedata) $changes .= " ".$filedata["old"]["name"]." (".$filedata["old"]["date"]."), ".$filedata["old"]["size"]." -> ".$filedata["new"]["size"]." bytes\n";
230 | $changes .= "\n";
231 | }
232 | if(count($added)>0) {
233 | $changes .= "Added:\n";
234 | foreach($added as $filedata) $changes .= " ".$filedata["name"]." (".$filedata["date"]."), ".$filedata["size"]." bytes\n";
235 | $changes .= "\n";
236 | }
237 | if(count($deleted)>0) {
238 | $changes .= "Deleted:\n";
239 | foreach($deleted as $filedata) $changes .= " ".$filedata["name"]." (".$filedata["date"]."), ".$filedata["size"]." bytes\n";
240 | $changes .= "\n";
241 | }
242 | echo $changes;
243 | $summary=count($newdata)." files scanned, ".count($changed)." changed, ".count($added)." added, ".count($deleted)." deleted\n";
244 |
245 | if(count($changed)+count($added)+count($deleted)>0) {
246 | if($emailAddressToAlert <> ""){
247 | $headers = "Return-path: $emailAddressToAlert\r\n";
248 | $headers .= "Reply-to: $emailAddressToAlert\r\n";
249 | $headers .= "Content-Type: text/plain\r\n";
250 | $headers .= "Content-Transfer-Encoding: 7bit\r\n";
251 | $headers .= "From: $emailAddressToAlert\r\n";
252 | $headers .= "X-Priority: 3\r\n";
253 | $headers .= "MIME-Version: 1.0\r\n";
254 | $headers .= "Organization: $servername\r\n";
255 | $headers .= "\n\n";
256 | //Add the new hash value to the email
257 | $emailBody = "Some files in the '$scandir' folder have changed since ".$oldsettings["date"].".\n".$summary."\n".
258 | $changes. "\nScanned in ".(microtime(true)-$time_start)." seconds.\n";
259 |
260 | mail($emailAddressToAlert, $emailSubject, $emailBody, $headers); //Simple mail function for alert.
261 |
262 | }
263 |
264 | }
265 | slog($summary);
266 |
267 | // Write new data to a file
268 |
269 | $datafile=fopen($scandir.$datafilename,"w");
270 | fwrite($datafile,"---\t---\t".date("Y-m-d H:i:s")."\t---\t".$scandir."\t".$mode."\n");
271 | foreach($newdata as $filedata) fwrite($datafile,$filedata["namehash"]."\t".$filedata["checkhash"]."\t".$filedata["date"]."\t".$filedata["size"]."\t".$filedata["name"]."\t".$filedata["ext"]."\n");
272 | fclose($datafile);
273 | slog(date("Y-m-d H:i:s")." Done in ".(microtime(true)-$time_start)." seconds!\n\n");
274 | echo "\nDone in ".(microtime(true)-$time_start)." seconds!