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 / Crawler.php

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

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