PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.37
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.37
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 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Login_Register_Form / Security_Manager.php

Security_Manager.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.37, at includes/widgets/Login_Register_Form/Security_Manager.php

382 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }