PluginProbe
WebTotem Security / 2.4.21
WebTotem Security v2.4.21
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.21, at lib/modules/logs/Scan.php

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