PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
← All changes | includes/widgets/Login_Register_Form/Security_Manager.php +382 -0 51.1.2 → 51.1.86 View file →
@@ -1,0 +1,382 @@
1 +<?php
2 +
3 +namespace King_Addons\Widgets\Login_Register_Form;
4 +
5 +if (!defined('ABSPATH')) {
6 + exit; // Exit if accessed directly.
7 +}
8 +
9 +/**
10 + * Security Manager for Login Register Form widget
11 + * Handles rate limiting, file validation, and other security measures
12 + */
13 +class Security_Manager
14 +{
15 + /**
16 + * Rate limiting settings
17 + */
18 + const MAX_LOGIN_ATTEMPTS = 5;
19 + const MAX_REGISTER_ATTEMPTS = 3;
20 + const MAX_LOST_PASSWORD_ATTEMPTS = 3;
21 + const LOCKOUT_DURATION = 900; // 15 minutes in seconds
22 + const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'text/plain'];
23 + const MAX_FILE_SIZE = 5242880; // 5MB in bytes
24 +
25 + /**
26 + * Check if IP is rate limited for specific action
27 + */
28 + public static function is_rate_limited($action, $ip_address = null)
29 + {
30 + if (!$ip_address) {
31 + $ip_address = self::get_client_ip();
32 + }
33 +
34 + $transient_key = "king_addons_{$action}_attempts_" . md5($ip_address);
35 + $attempts = get_transient($transient_key);
36 +
37 + $max_attempts = self::get_max_attempts($action);
38 +
39 + return $attempts !== false && $attempts >= $max_attempts;
40 + }
41 +
42 + /**
43 + * Record a failed attempt
44 + */
45 + public static function record_failed_attempt($action, $ip_address = null)
46 + {
47 + if (!$ip_address) {
48 + $ip_address = self::get_client_ip();
49 + }
50 +
51 + $transient_key = "king_addons_{$action}_attempts_" . md5($ip_address);
52 + $attempts = get_transient($transient_key);
53 +
54 + if ($attempts === false) {
55 + $attempts = 0;
56 + }
57 +
58 + $attempts++;
59 + set_transient($transient_key, $attempts, self::LOCKOUT_DURATION);
60 +
61 + // Log security event
62 + // error_log("King Addons Security: Failed {$action} attempt #{$attempts} from IP {$ip_address}");
63 +
64 + return $attempts;
65 + }
66 +
67 + /**
68 + * Clear failed attempts (on successful login/registration)
69 + */
70 + public static function clear_failed_attempts($action, $ip_address = null)
71 + {
72 + if (!$ip_address) {
73 + $ip_address = self::get_client_ip();
74 + }
75 +
76 + $transient_key = "king_addons_{$action}_attempts_" . md5($ip_address);
77 + delete_transient($transient_key);
78 + }
79 +
80 + /**
81 + * Get remaining lockout time
82 + */
83 + public static function get_remaining_lockout_time($action, $ip_address = null)
84 + {
85 + if (!$ip_address) {
86 + $ip_address = self::get_client_ip();
87 + }
88 +
89 + $transient_key = "king_addons_{$action}_attempts_" . md5($ip_address);
90 + $expiration = get_option('_transient_timeout_' . $transient_key);
91 +
92 + if ($expiration === false) {
93 + return 0;
94 + }
95 +
96 + $remaining = $expiration - time();
97 + return max(0, $remaining);
98 + }
99 +
100 + /**
101 + * Validate uploaded file
102 + */
103 + public static function validate_file_upload($file_data)
104 + {
105 + // Check if file was uploaded
106 + if (empty($file_data['name']) || empty($file_data['tmp_name'])) {
107 + return [
108 + 'valid' => false,
109 + 'error' => esc_html__('No file uploaded.', 'king-addons')
110 + ];
111 + }
112 +
113 + // Check file size
114 + if ($file_data['size'] > self::MAX_FILE_SIZE) {
115 + return [
116 + 'valid' => false,
117 + 'error' => sprintf(
118 + esc_html__('File size exceeds maximum allowed size of %s.', 'king-addons'),
119 + size_format(self::MAX_FILE_SIZE)
120 + )
121 + ];
122 + }
123 +
124 + // Check MIME type
125 + $file_type = wp_check_filetype($file_data['name']);
126 + if (!$file_type['type'] || !in_array($file_type['type'], self::ALLOWED_FILE_TYPES)) {
127 + return [
128 + 'valid' => false,
129 + 'error' => esc_html__('File type not allowed. Please upload images (JPG, PNG, GIF), PDF, or text files only.', 'king-addons')
130 + ];
131 + }
132 +
133 + // Additional security checks
134 + $real_mime = mime_content_type($file_data['tmp_name']);
135 + if ($real_mime && $real_mime !== $file_type['type']) {
136 + return [
137 + 'valid' => false,
138 + 'error' => esc_html__('File type mismatch detected. Upload rejected for security.', 'king-addons')
139 + ];
140 + }
141 +
142 + // Check for malicious content in text files (limit file size to prevent DoS)
143 + if (in_array($file_type['type'], ['text/plain', 'application/pdf'])) {
144 + // Security fix: Check file size before reading to prevent DoS
145 + $file_size = filesize($file_data['tmp_name']);
146 + if ($file_size > 1024 * 1024) { // 1MB limit for content scanning
147 + return [
148 + 'valid' => false,
149 + 'error' => esc_html__('File too large for content scanning.', 'king-addons')
150 + ];
151 + }
152 +
153 + $content = file_get_contents($file_data['tmp_name']);
154 + if (self::contains_malicious_content($content)) {
155 + return [
156 + 'valid' => false,
157 + 'error' => esc_html__('File contains suspicious content and cannot be uploaded.', 'king-addons')
158 + ];
159 + }
160 + }
161 +
162 + return ['valid' => true];
163 + }
164 +
165 + /**
166 + * Sanitize social login data
167 + */
168 + public static function sanitize_social_data($data, $provider)
169 + {
170 + $sanitized = [];
171 +
172 + // Basic required fields
173 + $sanitized['email'] = isset($data['email']) ? sanitize_email($data['email']) : '';
174 + $sanitized['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
175 + $sanitized['provider_id'] = isset($data['id']) ? sanitize_text_field($data['id']) : '';
176 +
177 + // Optional fields
178 + $sanitized['first_name'] = isset($data['given_name']) ? sanitize_text_field($data['given_name']) : '';
179 + $sanitized['last_name'] = isset($data['family_name']) ? sanitize_text_field($data['family_name']) : '';
180 +
181 + // Picture URL with strict validation
182 + if (isset($data['picture'])) {
183 + $picture_url = esc_url_raw($data['picture']);
184 + // Additional validation for picture URL
185 + if (filter_var($picture_url, FILTER_VALIDATE_URL) && self::is_safe_image_url($picture_url)) {
186 + $sanitized['picture'] = $picture_url;
187 + } else {
188 + $sanitized['picture'] = '';
189 + }
190 + } else {
191 + $sanitized['picture'] = '';
192 + }
193 +
194 + // Validate email domain for additional security
195 + if (!empty($sanitized['email']) && !self::is_safe_email_domain($sanitized['email'])) {
196 + // error_log("King Addons Security: Suspicious email domain from {$provider}: {$sanitized['email']}");
197 + }
198 +
199 + return $sanitized;
200 + }
201 +
202 + /**
203 + * Check for suspicious patterns in registration data
204 + */
205 + public static function detect_suspicious_registration($data)
206 + {
207 + $suspicious_patterns = [
208 + // Common spam patterns
209 + '/\b(viagra|cialis|casino|poker|lottery|winner|congratulations)\b/i',
210 + // Suspicious email patterns
211 + '/\b\d{10,}@/', // Long numeric sequences in email
212 + // Bot-like usernames
213 + '/^(user|test|admin)\d+$/i',
214 + ];
215 +
216 + $text_to_check = implode(' ', [
217 + $data['username'] ?? '',
218 + $data['email'] ?? '',
219 + $data['first_name'] ?? '',
220 + $data['last_name'] ?? ''
221 + ]);
222 +
223 + foreach ($suspicious_patterns as $pattern) {
224 + if (preg_match($pattern, $text_to_check)) {
225 + // error_log("King Addons Security: Suspicious registration pattern detected: {$pattern}");
226 + return true;
227 + }
228 + }
229 +
230 + return false;
231 + }
232 +
233 + /**
234 + * Enhanced password strength validation
235 + */
236 + public static function validate_password_strength($password)
237 + {
238 + $strength = [
239 + 'score' => 0,
240 + 'feedback' => [],
241 + 'valid' => true
242 + ];
243 +
244 + // Basic length check
245 + if (strlen($password) < 8) {
246 + $strength['valid'] = false;
247 + $strength['feedback'][] = esc_html__('Password must be at least 8 characters long.', 'king-addons');
248 + return $strength;
249 + }
250 +
251 + // Check for character variety
252 + $patterns = [
253 + 'lowercase' => '/[a-z]/',
254 + 'uppercase' => '/[A-Z]/',
255 + 'numbers' => '/\d/',
256 + 'special' => '/[!@#$%^&*(),.?":{}|<>]/'
257 + ];
258 +
259 + foreach ($patterns as $type => $pattern) {
260 + if (preg_match($pattern, $password)) {
261 + $strength['score']++;
262 + }
263 + }
264 +
265 + // Check against common passwords
266 + if (self::is_common_password($password)) {
267 + $strength['valid'] = false;
268 + $strength['feedback'][] = esc_html__('This password is too common. Please choose a more unique password.', 'king-addons');
269 + }
270 +
271 + // Length bonus
272 + if (strlen($password) >= 12) {
273 + $strength['score']++;
274 + }
275 +
276 + // Determine if password is strong enough
277 + if ($strength['score'] < 3) {
278 + $strength['valid'] = false;
279 + $strength['feedback'][] = esc_html__('Password should contain a mix of uppercase, lowercase, numbers, and special characters.', 'king-addons');
280 + }
281 +
282 + return $strength;
283 + }
284 +
285 + /**
286 + * Private helper methods
287 + */
288 + private static function get_client_ip()
289 + {
290 + $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
291 +
292 + foreach ($ip_keys as $key) {
293 + if (!empty($_SERVER[$key])) {
294 + $ip = trim($_SERVER[$key]);
295 + // Handle comma-separated IPs (from load balancers)
296 + if (strpos($ip, ',') !== false) {
297 + $ip = trim(explode(',', $ip)[0]);
298 + }
299 + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
300 + return $ip;
301 + }
302 + }
303 + }
304 +
305 + return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
306 + }
307 +
308 + private static function get_max_attempts($action)
309 + {
310 + switch ($action) {
311 + case 'login':
312 + return self::MAX_LOGIN_ATTEMPTS;
313 + case 'register':
314 + return self::MAX_REGISTER_ATTEMPTS;
315 + case 'lostpassword':
316 + return self::MAX_LOST_PASSWORD_ATTEMPTS;
317 + default:
318 + return 3;
319 + }
320 + }
321 +
322 + private static function contains_malicious_content($content)
323 + {
324 + $malicious_patterns = [
325 + '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi',
326 + '/javascript:/i',
327 + '/data:text\/html/i',
328 + '/\bon\w+\s*=/i', // Event handlers like onclick
329 + '/eval\s*\(/i',
330 + '/exec\s*\(/i'
331 + ];
332 +
333 + foreach ($malicious_patterns as $pattern) {
334 + if (preg_match($pattern, $content)) {
335 + return true;
336 + }
337 + }
338 +
339 + return false;
340 + }
341 +
342 + private static function is_safe_image_url($url)
343 + {
344 + // Only allow images from trusted domains
345 + $trusted_domains = [
346 + 'lh3.googleusercontent.com', // Google profile pictures
347 + 'platform-lookaside.fbsbx.com', // Facebook profile pictures
348 + 'graph.facebook.com', // Facebook graph API
349 + 'scontent.xx.fbcdn.net' // Facebook CDN
350 + ];
351 +
352 + $parsed_url = parse_url($url);
353 + $domain = $parsed_url['host'] ?? '';
354 +
355 + return in_array($domain, $trusted_domains);
356 + }
357 +
358 + private static function is_safe_email_domain($email)
359 + {
360 + // Check against known suspicious domains
361 + $suspicious_domains = [
362 + 'guerrillamail.com',
363 + '10minutemail.com',
364 + 'mailinator.com',
365 + 'tempmail.org'
366 + ];
367 +
368 + $domain = substr(strrchr($email, "@"), 1);
369 + return !in_array(strtolower($domain), $suspicious_domains);
370 + }
371 +
372 + private static function is_common_password($password)
373 + {
374 + $common_passwords = [
375 + 'password', '123456', '123456789', 'qwerty', 'abc123',
376 + 'password123', 'admin', 'letmein', 'welcome', 'monkey',
377 + 'dragon', 'master', 'sunshine', 'princess', 'football'
378 + ];
379 +
380 + return in_array(strtolower($password), $common_passwords);
381 + }
382 +}