PluginProbe
WebTotem Security / 2.4.25
WebTotem Security v2.4.25
3.0.2 3.0.1 3.0.0 trunk 1.0 1.1 1.2 1.3 1.3.1 1.3.2 1.3.3 2.0 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.1 2.2.2 2.2.3 All 110 releases
wt-security / lib / modules / logs / Scan.php

Scan.php in WebTotem Security 2.4.25, at lib/modules/logs/Scan.php

447 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('WEBTOTEM_INIT') || WEBTOTEM_INIT !== true) {
4 if (!headers_sent()) {
5 header('HTTP/1.1 403 Forbidden');
6 }
7 die("Protected By WebTotem!");
8 }
9
10 require_once 'FileInfo.php';
11
12 /**
13 * WebTotem scan class for WordPress.
14 */
15 class WebTotemScan {
16 /**
17 *
18 */
19 public static function initialize() {
20 if(WebTotemOption::getOption('scan_init')){
21 $time_start = microtime(true);
22
23 $max_execution_time = ini_get('max_execution_time');
24 if($max_execution_time < 300){
25 if (function_exists('set_time_limit')) @set_time_limit(300);
26 @ini_set('max_execution_time', '300');
27 }
28 $max_execution_time = ini_get('max_execution_time');
29
30 $scan_temp = json_decode(WebTotemOption::getOption('scan_temp'), true) ?: [];
31
32 if(empty($scan_temp)){
33 $scan_temp = [
34 'current_scan' => 'scanDB',
35 'need_to_scan' => [],
36 'links' => [],
37 ];
38 }
39
40 $scan_running = json_decode(WebTotemOption::getOption('scan_running'), true) ?: ['status' => 'stop'];
41 $seconds_from_previous_start = $time_start - ($scan_running['time_start'] ?? $time_start);
42 if($scan_running['status'] == 'stop' || $seconds_from_previous_start > $max_execution_time ){
43
44 WebTotemOption::setOptions(['scan_running' => ['status' => 'run', 'time_start' => $time_start]]);
45
46 if($scan_temp['current_scan'] == 'scanDB'){
47 self::scanDB($scan_temp, $max_execution_time, $time_start);
48 WebTotemOption::setOptions(['scan_running' => ['status' => 'stop']]);
49 return;
50 }
51
52 if($scan_temp['current_scan'] == 'scanFiles') {
53 self::scanFiles($scan_temp, $max_execution_time, $time_start);
54 WebTotemOption::setOptions(['scan_running' => ['status' => 'stop']]);
55 return;
56 }
57
58 if($scan_temp['current_scan'] == 'checkConfidentialFiles') {
59 self::checkConfidentialFiles($scan_temp, $max_execution_time, $time_start);
60 WebTotemOption::setOptions(['scan_running' => ['status' => 'stop']]);
61 return;
62 }
63
64 if($scan_temp['current_scan'] == 'crawler') {
65 WebTotemCrawler::init($scan_temp);
66 WebTotemOption::setOptions(['scan_running' => ['status' => 'stop']]);
67 return;
68 }
69
70 }
71
72 }
73
74 }
75
76 /**
77 * Database scanning, search for links, scripts and iframe tags,
78 * formation of an array of data on them
79 */
80 public static function scanDB($scan_temp, $max_execution_time, $time_start ) {
81 $tables = $scan_temp['need_to_scan'] ?: self::getTables();
82 $links = $scan_temp['links'] ?: [];
83
84 $needles = ['%href%', '%<iframe%', '%.js%'];
85
86 foreach ($tables['posts'] as $key => $table) {
87 $rows = self::getRows($table, ['post_content' => $needles], 'guid');
88
89 foreach ($rows as $row) {
90 $links[] = ['link' => $row->guid, 'page' => __('DB scan', 'wtotem'), 'is_internal' => true];;
91 }
92
93 unset($tables['posts'][$key]);
94
95 $time_end = microtime(true);
96 if (($time_end - $time_start) > $max_execution_time - 5) {
97 WebTotemOption::setOptions([
98 'scan_temp' => [
99 'current_scan' => 'scanDB',
100 'need_to_scan' => $tables,
101 'links' => $links,
102 ]
103 ]);
104 return;
105 }
106
107 }
108
109 foreach ($tables['comments'] as $relation => $table) {
110 $rows = self::getRows($table, ['comment_content' => $needles], 'guid');
111
112 $posts_ids = array_column($rows, 'comment_post_ID');
113 $posts_rows = self::getRows($relation, ['ID' => $posts_ids]);
114 $posts_rows = WebTotem::arrayMapIndex(WebTotem::convertObjectToArray($posts_rows), 'ID');
115
116 foreach ($rows as $row) {
117 $links[] = ['link' => $posts_rows[$row->comment_post_ID]['guid'], 'page' => __('DB scan', 'wtotem'), 'is_internal' => true];
118 }
119
120 unset($tables['comments'][$relation]);
121
122 $time_end = microtime(true);
123 if (($time_end - $time_start) > $max_execution_time - 5) {
124 WebTotemOption::setOptions([
125 'scan_temp' => [
126 'current_scan' => 'scanDB',
127 'need_to_scan' => $tables,
128 'links' => $links,
129 ]
130 ]);
131 return;
132 }
133 }
134
135 WebTotemOption::setOptions([
136 'scan_temp' => [
137 'current_scan' => 'scanFiles',
138 'need_to_scan' => [],
139 'links' => $links,
140 ]
141 ]);
142
143 }
144
145 /**
146 * Getting values from the table.
147 *
148 * @param array $options
149 * Array options.
150 * @param string $table
151 * Table name.
152 * @param string $fields
153 * Required fields.
154 *
155 * @return array
156 */
157 private static function getRows($table, $options = false, $fields = false) {
158 global $wpdb;
159 $table_name = self::add_prefix($table);
160
161 if ($options) {
162 foreach ($options as $key => $value) {
163 if (is_array($value)) {
164 foreach ($value as $val) {
165 $where[] = $key . " LIKE '" . $val . "'";
166 }
167 } else {
168 $where[] = $key . " LIKE '" . $value . "'";
169 }
170 }
171 }
172 $where = isset($where) ? 'WHERE (' . implode(' OR ', $where) . ')' : '';
173 if(strpos($table, 'posts') !== false) {
174 $where .= $where ? " AND " : "WHERE ";
175 $where .= "post_status = 'publish'";
176 }
177
178 $fields = $fields ?: '*';
179 $rows = $wpdb->get_results("SELECT $fields FROM $table_name $where");
180
181 return (array)$rows ?: [];
182 }
183
184 /**
185 * Get an array of tables
186 */
187 private static function getTables() {
188 $tables = [
189 'posts' => [],
190 'comments' => []
191 ];
192
193 if (WebTotem::isMultiSite()) {
194 $blogs = self::getRows(self::add_prefix('blogs'));
195 foreach ($blogs as $blog) {
196 $tables['posts'][] = $blog['blog_id'] . '_posts';
197 $tables['comments'][$blog['blog_id'] . '_posts'] = $blog['blog_id'] . '_comments';
198 }
199 }
200 return $tables;
201 }
202
203 /**
204 * Returns the table with the site prefix added.
205 *
206 * @param string $table
207 * Table name.
208 * @return string
209 */
210 public static function add_prefix($table) {
211 global $wpdb;
212 return $wpdb->prefix . $table;
213 }
214
215 /**
216 * Files scanning, search for links, scripts and iframe tags,
217 * formation of an array of data on them
218 */
219 public static function scanFiles($scan_temp, $max_execution_time, $time_start) {
220
221 $tree = $scan_temp['need_to_scan'] ?? [];
222 $links = $scan_temp['links'] ?? [];
223
224 $site_url = get_site_url();
225 $fileInfo = new WebTotemFileInfo();
226 $abspath = ABSPATH;
227
228 if(empty($tree)){
229 // Adding files of active plugins
230 if (WebTotem::isMultiSite()) {
231 $all_plugs = array_keys(get_site_option('active_sitewide_plugins'));
232 } else {
233 $all_plugs = get_option('active_plugins');
234 }
235 foreach ($all_plugs as $value) {
236 $plugin = explode('/', $value);
237 $tree = array_merge($tree, $fileInfo->getDirectoryTree(WP_PLUGIN_DIR . '/' . $plugin[0]));
238 }
239
240 // Adding files of active theme
241 $tree = array_merge($tree, $fileInfo->getDirectoryTree(get_template_directory()));
242 }
243
244 foreach ($tree as $key => $file_path) {
245 $content = $fileInfo::fileContent($file_path);
246 if(self::hasMatches($content)){
247 $link = $site_url . str_replace($abspath, '/', $file_path);
248 $links[] = ['link' => $link, 'page' => __('File scan', 'wtotem'), 'is_internal' => true];
249 }
250 unset($tree[$key]);
251
252 $time_end = microtime(true);
253 if (($time_end - $time_start) > $max_execution_time - 5) {
254 WebTotemOption::setOptions([
255 'scan_temp' => [
256 'current_scan' => 'scanFiles',
257 'need_to_scan' => $tree,
258 'links' => $links,
259 ]
260 ]);
261 return;
262 }
263
264 }
265
266 WebTotemOption::setOptions([
267 'scan_temp' => [
268 'current_scan' => 'checkConfidentialFiles',
269 'need_to_scan' => [],
270 'ready_to_save' => false,
271 'links' => $links,
272 ]
273 ]);
274 }
275
276
277 /**
278 * Get matches.
279 *
280 * @param string $content
281 *
282 * @return bool
283 */
284 private static function hasMatches($content) {
285 $pattern = '/(<a.*?href=["\'](([\da-z\.-\/]+)([\/\w\.-\?\%\&]*)*\/?)["\'].*?>|<script.*?src=["\'](.*?)["\'].*?>|<iframe.*?src=["\'](.*?)["\'].*?>|onclick="[^"]*location[^"][^\'"]+\'([^\']+)\')/i';
286 if (preg_match($pattern, $content)) {
287 return true;
288 }
289 return false;
290 }
291
292 /**
293 * Files scanning, search for confidential files.
294 */
295 public static function checkConfidentialFiles($scan_temp, $max_execution_time, $time_start) {
296
297 $files = $scan_temp['need_to_scan'] ?? [];
298 $files_data = $scan_temp['confidential_files'] ?? [];
299 $root_path = ABSPATH;
300
301 if(empty($files) and !$scan_temp['ready_to_save']){
302 $patterns = [
303 '.user.ini',
304 'wp-config.php.bak',
305 'wp-config.php.bak.a2',
306 'wp-config.php.swo',
307 'wp-config.php.save',
308 'wp-config.php~',
309 'wp-config.old',
310 '.wp-config.php.swp',
311 'wp-config.bak',
312 'wp-config.save',
313 'wp-config.php_bak',
314 'wp-config.php.swp',
315 'wp-config.php.old',
316 'wp-config.php.original',
317 'wp-config.php.orig',
318 'wp-config.txt',
319 'wp-config.original',
320 'wp-config.orig',
321 '*.bak',
322 '*.back',
323 '*.backup',
324 '*.old',
325 ];
326
327 $mask = implode(',', $patterns);
328 $files = self::glob_tree_search($root_path, '{' . $mask . '}',false);
329 $files = array_merge(self::glob_tree_search($root_path . '/wp-content/', '{' . $mask . '}'), $files);
330 }
331
332
333 foreach ($files as $file_path) {
334 $url = site_url(str_replace($root_path, '', $file_path));
335
336 if (WebTotem::isPubliclyAccessible($url, $file_path)) {
337 $array = explode(DIRECTORY_SEPARATOR, $file_path);
338 $name = array_pop($array);
339 $files_data[] = [
340 'path' => $file_path,
341 'name' => $name,
342 'size' => filesize($file_path),
343 'modified_at' => date("Y-m-d H:i:s", filectime($file_path)),
344 'url' => $url,
345 ];
346 }
347
348 $time_end = microtime(true);
349 if (($time_end - $time_start) > $max_execution_time - 5) {
350 WebTotemOption::setOptions([
351 'scan_temp' => [
352 'current_scan' => 'checkConfidentialFiles',
353 'need_to_scan' => $files,
354 'links' => $scan_temp['links'],
355 'confidential_files' => $files_data,
356 ]
357 ]);
358 return;
359 }
360
361 }
362
363 if($scan_temp['ready_to_save']){
364 if($files_data){
365 self::saveData($files_data);
366 }
367 } else {
368 WebTotemOption::setOptions([
369 'scan_temp' => [
370 'current_scan' => 'checkConfidentialFiles',
371 'need_to_scan' => [],
372 'links' => $scan_temp['links'],
373 'ready_to_save' => true,
374 'confidential_files' => $files_data,
375 ]
376 ]);
377 return;
378 }
379
380 WebTotemOption::setOptions([
381 'scan_temp' => [
382 'current_scan' => 'crawler',
383 'need_to_scan' => [],
384 'ready_to_save' => false,
385 'links' => $scan_temp['links'],
386 'confidential_files' => [],
387 ]
388 ]);
389
390 }
391
392 /**
393 * Save data.
394 *
395 * @param array $data
396 * Array matches data.
397 */
398 private static function saveData($data) {
399
400 WebTotemDB::deleteData([], 'confidential_files');
401 $values = '';
402 foreach ($data as $file) {
403 $values .= sprintf("('%s','%s','%s','%s','%s','%s'),",
404 date("Y-m-d H:i:s"),
405 urlencode($file['path']),
406 urlencode($file['name']),
407 $file['size'],
408 $file['modified_at'],
409 $file['url']
410 );
411 }
412
413 $values = substr_replace($values, ";", -1);
414
415 $columns = '(created_at, path, name, size, modified_at, url)';
416
417 WebTotemDB::setRows('confidential_files', $columns, $values);
418 }
419
420 /**
421 * Search through all subdirectories using recursion.
422 *
423 * @param string $path
424 * The initial directory of the search.
425 * @param string $mask
426 * Search mask.
427 *
428 * @return array
429 * Array of file paths found by mask.
430 */
431 public static function glob_tree_search($path, $mask, $recursively = true) {
432 $out = [];
433 foreach (glob($path . $mask, GLOB_BRACE) as $file_path) {
434 $out[] = $file_path;
435 }
436
437 if ($recursively) {
438 foreach (glob($path . '/*', GLOB_ONLYDIR) as $dir) {
439 $out = array_merge($out, self::glob_tree_search($dir, $mask));
440 }
441 }
442
443 return $out;
444 }
445
446 }
447