PluginProbe
WebTotem Security / 3.0.0
WebTotem Security v3.0.0
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 2.2.4 All 109 releases
wt-security / lib / modules / logs / Crawler.php

Crawler.php in WebTotem Security 3.0.0, at lib/modules/logs/Crawler.php

368 lines 12.0 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 /**
11 * WebTotem page scan class for Wordpress.
12 */
13 class WebTotemCrawler
14 {
15 /**
16 * Running a single iteration
17 *
18 * @param array $scan_temp
19 * The data of the current scan.
20 */
21 public static function init($scan_temp) {
22
23 $crawler_temp = json_decode(WebTotemOption::getOption('crawler_temp'), true) ?: [];
24
25 $i = 1;
26 if (!$crawler_temp) {
27 $pre_scan = self::pre_scan();
28
29 $crawler_temp['internal']['new'] = $pre_scan['internal'];
30 $crawler_temp['external'] = WebTotem::arrayUniqueKey($pre_scan['external'], 'link');
31 $crawler_temp['scripts'] = WebTotem::arrayUniqueKey($pre_scan['scripts'], 'link');
32 $crawler_temp['iframes'] = WebTotem::arrayUniqueKey( $pre_scan['iframes'], 'link');
33 $crawler_temp['exclude'] = $pre_scan['exclude'];
34
35 $crawler_temp['internal']['new'] = WebTotem::arrayUniqueKey( array_merge($crawler_temp['internal']['new'], $scan_temp['links']), 'link');
36
37 $i++;
38 }
39
40 $visited = [];
41
42 foreach ($crawler_temp['internal']['new'] as $key => $item) {
43 if($result = self::explore_page($item['link'], $crawler_temp['exclude'])) {
44
45 $crawler_temp['internal']['visited'][] = $item;
46
47 $crawler_temp['internal']['new'] = WebTotem::arrayUniqueKey(array_merge($crawler_temp['internal']['new'] ?? [], $result['internal'] ?? []), 'link');
48 $crawler_temp['external'] = WebTotem::arrayUniqueKey(array_merge($crawler_temp['external'] ?? [], $result['external'] ?? []), 'link');
49 $crawler_temp['scripts'] = WebTotem::arrayUniqueKey(array_merge($crawler_temp['scripts'] ?? [], $result['scripts'] ?? []), 'link');
50 $crawler_temp['iframes'] = WebTotem::arrayUniqueKey(array_merge($crawler_temp['iframes'] ?? [], $result['iframes'] ?? []), 'link');
51 $crawler_temp['exclude'] = array_merge($crawler_temp['exclude'] ?? [], $result['exclude'] ?? []);
52 }
53
54 $visited[] = $key;
55
56 if ($i >= 5) break;
57 $i++;
58 }
59
60 foreach ($visited as $key){
61 unset($crawler_temp['internal']['new'][$key]);
62 }
63 WebTotemOption::setOptions(['crawler_temp' => $crawler_temp]);
64
65 if (empty($crawler_temp['internal']['new'])) {
66
67 if($scan_temp['ready_to_save']){
68
69 if(isset($crawler_temp['internal']['visited']) and $crawler_temp['external']){
70 $links = array_merge($crawler_temp['internal']['visited'], $crawler_temp['external']);
71 } elseif (isset($crawler_temp['internal']['visited'])){
72 $links = $crawler_temp['internal']['visited'];
73 } else {
74 $links = $crawler_temp['external'];
75 }
76
77 $data = [
78 'links' => $links ?? [],
79 'scripts' => $crawler_temp['scripts'] ?? [],
80 'iframes' => $crawler_temp['iframes'] ?? [],
81 ];
82
83 self::saveData($data);
84
85 WebTotemOption::setOptions(['crawler_temp' => '']);
86 WebTotemOption::setOptions(['scan_temp' => '']);
87 WebTotemOption::setOptions(['scan_init' => 0]);
88
89 // Resetting the task in the cron.
90 wp_clear_scheduled_hook('webtotem_daily_cron');
91 wp_schedule_event(time() + 86395, 'daily', 'webtotem_daily_cron');
92 } else {
93 WebTotemOption::setOptions([
94 'scan_temp' => [
95 'current_scan' => 'crawler',
96 'links' => [],
97 'ready_to_save' => true,
98 ]
99 ]);
100 }
101
102 }
103
104 }
105
106 /**
107 * Preliminary scan of the site.
108 *
109 * @return array
110 */
111 private static function pre_scan() {
112 $site_url = get_site_url();
113 $internal = [];
114 $exclude = [$site_url];
115
116 // Scanning the file robots.txt
117 if(file_exists(ABSPATH . 'robots.txt')){
118 $robotsTxt = file_get_contents(ABSPATH . 'robots.txt');
119 $lines = explode("\n", $robotsTxt);
120 $robots_urls = [];
121
122 foreach ($lines as $line) {
123 if (strpos($line, 'Disallow:') === 0 || strpos($line, 'Allow:') === 0) {
124 $url = trim(substr($line, strpos($line, ':') + 1));
125 $exclude[] = $url;
126 $robots_urls[] = (string)$url->loc;
127 }
128 }
129
130 foreach ($robots_urls as $link) {
131 if (substr($link, 0, 1) == "#") {
132 continue;
133 }
134 $link = untrailingslashit($link);
135 $internal[] = ['link' => $link, 'page' => $site_url . '/robots.txt', 'is_internal' => self::isInternal($link)];
136 }
137 }
138
139
140 // Adding links from popular sitemaps plugins
141 $sitemaps = [
142 $site_url . '/sitemaps.xml',
143 $site_url . '/index.php?xml_sitemap=params=.',
144 $site_url . '/?sitemap=1',
145 $site_url . '/sitemap_index.xml',
146 ];
147
148 foreach ($sitemaps as $link) {
149 $internal[] = ['link' => $link, 'page' => __('by sitemap plugins', 'wtotem'), 'is_internal' => true];
150 }
151
152 // Scanning the file sitemap.xml
153 $xml = simplexml_load_file(ABSPATH . 'sitemap.xml');
154
155 if($xml){
156 $sitemap_urls = [];
157 foreach ($xml->url as $url) {
158 $exclude[] = (string)$url->loc;
159 $sitemap_urls[] = (string)$url->loc;
160 }
161
162 foreach ($sitemap_urls as $link) {
163 $link = untrailingslashit($link);
164 if (substr($link, 0, 1) == "#") {
165 continue;
166 }
167 $internal[] = ['link' => $link, 'page' => $site_url . '/sitemap.xml', 'is_internal' => self::isInternal($link)];
168 }
169 }
170
171 // Scanning the main page
172 $result = self::explore_page($site_url, $exclude);
173
174 $internal = array_merge($internal, $result['internal']);
175 $external = array_unique($result['external']);
176 $exclude = array_merge($exclude, $result['exclude'] ?? []);
177
178 return [
179 'internal' => $internal ?: [],
180 'external' => $external ?: [],
181 'scripts' => $result['scripts'] ?: [],
182 'iframes' => $result['iframes'] ?: [],
183 'exclude' => $exclude ?: [],
184 ];
185
186 }
187
188 /**
189 * Get and explore the content of the page.
190 *
191 * @param string $url
192 * Link to the page.
193 * @param string $exclude
194 * Links that have already been checked.
195 *
196 * @return array|bool
197 */
198 private static function explore_page($url, $exclude = []) {
199
200 if(!$url){
201 return false;
202 }
203
204 $headers = get_headers($url);
205
206 if ($headers === false || strpos($headers[0], '200 OK') === false) {
207 return false;
208 }
209
210 // Initializing the cURL session
211 $curl = curl_init();
212
213 // Setting the parameters of the cURL session
214 curl_setopt($curl, CURLOPT_URL, $url); // Setting the URL
215 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // We return the result as a string
216 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // Follow the redirects
217 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // Disabling SSL certificate verification
218
219 // Execute the request and get the content of the page
220 $content = curl_exec($curl);
221
222 // Checking for errors when executing the request
223 if (curl_errno($curl)) {
224 WebTotemOption::setNotification('error', __('Request execution error: ', 'wtotem')) . curl_error($curl);
225 }
226
227 // Closing the cURL session
228 curl_close($curl);
229
230 // Checking the content for matches with the template, using regular expressions
231 return self::getMatches($content, $url, $exclude);
232 }
233
234 /**
235 * We are looking for links, scripts and frames on the page.
236 *
237 * @param string $content
238 * The content of the page being checked.
239 * @param string $url
240 * Link to the page.
241 * @param array $exclude
242 * Links that have already been checked.
243 *
244 * @return array
245 */
246 private static function getMatches($content, $url, $exclude) {
247
248 $matches = [
249 'internal' => [],
250 'external' => [],
251 'exclude' => [],
252 'scripts' => [],
253 'iframes' => [],
254 ];
255
256 if($content){
257 // Get all the matches.
258 $pattern = '/(<a.*?href=["\']([^"\']+)["\'].*?>|<script.*?src=["\']([^"\']+)["\'].*?>|<iframe.*?src=["\']([^"\']+)["\'].*?>|onclick=["\']?[^"\']*location.*?["\']?([^"\']+))/i';
259 preg_match_all($pattern, $content, $all_matches);
260
261 $array = [
262 'links' => [],
263 'scripts' => [],
264 'iframes' => [],
265 ];
266
267 // Divide by categories.
268 foreach ($all_matches[0] as $match) {
269 preg_match_all('/<a.*?href=(["](.*?)["]|[\'](.*?)[\']).*?>/i', $match, $links_matches);
270 if (isset($links_matches[2])) $array['links'] = array_merge($array['links'], $links_matches[2]);
271 preg_match_all('/onclick="[^"]*location[^"][^\'"]+\'([^\']+)\'/i', $match, $links_2_matches);
272 if (isset($links_2_matches[2])) $array['links'] = array_merge($array['links'], $links_2_matches[2]);
273 preg_match_all('/<script.*?src=(["](.*?)["]|[\'](.*?)[\']).*?>/i', $match, $js_matches);
274 if (isset($js_matches[2])) $array['scripts'] = array_merge($array['scripts'], $js_matches[2]);
275 preg_match_all('/<iframe.*?src=(["](.*?)["]|[\'](.*?)[\']).*?>/i', $match, $iframe_matches);
276 if (isset($iframe_matches[2])) $array['iframes'] = array_merge($array['iframes'], $iframe_matches[2]);
277 }
278
279 foreach ($array['links'] as $link) {
280 if($link){
281 if (self::isInternal($link)) {
282 if (substr($link, 0, 1) != "#" and !in_array($link, $exclude)) {
283 $matches['internal'][] = ['link' => $link, 'page' => $url, 'is_internal' => true];
284 $matches['exclude'][] = $link;
285 $exclude[] = $link;
286 }
287 } else {
288 if(!in_array($link, $exclude)){
289 $matches['external'][] = ['link' => $link, 'page' => $url, 'is_internal' => false];
290 $matches['exclude'][] = $link;
291 $exclude[] = $link;
292 }
293 }
294 }
295
296 }
297
298 foreach (array_unique($array['scripts']) as $script) {
299 if($script){
300 $matches['scripts'][] = ['link' => $script, 'page' => $url, 'is_internal' => self::isInternal($script)];
301 }
302 }
303 foreach (array_unique($array['iframes']) as $iframe) {
304 if($iframe){
305 $matches['iframes'][] = ['link' => $iframe, 'page' => $url, 'is_internal' => self::isInternal($iframe)];
306 }
307 }
308
309 }
310
311 return $matches;
312 }
313
314 /**
315 * We check whether the link is internal or external.
316 *
317 * @param string $string
318 * The link being checked.
319 *
320 * @return bool
321 */
322 private static function isInternal($string): bool {
323 $current_domain_parts = parse_url(get_home_url());
324 $current_domain = $current_domain_parts['host'];
325
326 if (substr($string, 0, 5) == "https"
327 || substr($string, 0, 4) == "http"
328 || substr($string, 0, 2) == "//") {
329
330 if (strpos($string, $current_domain) === false) {
331 return false;
332 }
333 }
334
335 return true;
336 }
337
338 /**
339 * Save data.
340 *
341 * @param array $data
342 * Array matches data.
343 */
344 private static function saveData($data) {
345
346 WebTotemDB::deleteData([], 'scan_logs');
347 $values = '';
348 foreach ($data as $data_type => $links) {
349 foreach ($links as $datum) {
350 $values .= sprintf("('%s','%s','%s','%s','%s'),",
351 date("Y-m-d H:i:s"),
352 $data_type,
353 $datum['page'],
354 addslashes($datum['link']),
355 $datum['is_internal']
356 );
357 }
358 }
359
360 $values = substr_replace($values, ";", -1);
361
362 $columns = '(created_at, data_type, source, content, is_internal)';
363
364 WebTotemDB::setRows('scan_logs', $columns, $values);
365 }
366
367 }
368