PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.14
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.14
51.1.87 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 All 41 releases
king-addons / includes / widgets / Login_Register_Form / Security_Manager.php

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

373 lines 11.8 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
143 if (in_array($file_type['type'], ['text/plain', 'application/pdf'])) {
144 $content = file_get_contents($file_data['tmp_name']);
145 if (self::contains_malicious_content($content)) {
146 return [
147 'valid' => false,
148 'error' => esc_html__('File contains suspicious content and cannot be uploaded.', 'king-addons')
149 ];
150 }
151 }
152
153 return ['valid' => true];
154 }
155
156 /**
157 * Sanitize social login data
158 */
159 public static function sanitize_social_data($data, $provider)
160 {
161 $sanitized = [];
162
163 // Basic required fields
164 $sanitized['email'] = isset($data['email']) ? sanitize_email($data['email']) : '';
165 $sanitized['name'] = isset($data['name']) ? sanitize_text_field($data['name']) : '';
166 $sanitized['provider_id'] = isset($data['id']) ? sanitize_text_field($data['id']) : '';
167
168 // Optional fields
169 $sanitized['first_name'] = isset($data['given_name']) ? sanitize_text_field($data['given_name']) : '';
170 $sanitized['last_name'] = isset($data['family_name']) ? sanitize_text_field($data['family_name']) : '';
171
172 // Picture URL with strict validation
173 if (isset($data['picture'])) {
174 $picture_url = esc_url_raw($data['picture']);
175 // Additional validation for picture URL
176 if (filter_var($picture_url, FILTER_VALIDATE_URL) && self::is_safe_image_url($picture_url)) {
177 $sanitized['picture'] = $picture_url;
178 } else {
179 $sanitized['picture'] = '';
180 }
181 } else {
182 $sanitized['picture'] = '';
183 }
184
185 // Validate email domain for additional security
186 if (!empty($sanitized['email']) && !self::is_safe_email_domain($sanitized['email'])) {
187 error_log("King Addons Security: Suspicious email domain from {$provider}: {$sanitized['email']}");
188 }
189
190 return $sanitized;
191 }
192
193 /**
194 * Check for suspicious patterns in registration data
195 */
196 public static function detect_suspicious_registration($data)
197 {
198 $suspicious_patterns = [
199 // Common spam patterns
200 '/\b(viagra|cialis|casino|poker|lottery|winner|congratulations)\b/i',
201 // Suspicious email patterns
202 '/\b\d{10,}@/', // Long numeric sequences in email
203 // Bot-like usernames
204 '/^(user|test|admin)\d+$/i',
205 ];
206
207 $text_to_check = implode(' ', [
208 $data['username'] ?? '',
209 $data['email'] ?? '',
210 $data['first_name'] ?? '',
211 $data['last_name'] ?? ''
212 ]);
213
214 foreach ($suspicious_patterns as $pattern) {
215 if (preg_match($pattern, $text_to_check)) {
216 error_log("King Addons Security: Suspicious registration pattern detected: {$pattern}");
217 return true;
218 }
219 }
220
221 return false;
222 }
223
224 /**
225 * Enhanced password strength validation
226 */
227 public static function validate_password_strength($password)
228 {
229 $strength = [
230 'score' => 0,
231 'feedback' => [],
232 'valid' => true
233 ];
234
235 // Basic length check
236 if (strlen($password) < 8) {
237 $strength['valid'] = false;
238 $strength['feedback'][] = esc_html__('Password must be at least 8 characters long.', 'king-addons');
239 return $strength;
240 }
241
242 // Check for character variety
243 $patterns = [
244 'lowercase' => '/[a-z]/',
245 'uppercase' => '/[A-Z]/',
246 'numbers' => '/\d/',
247 'special' => '/[!@#$%^&*(),.?":{}|<>]/'
248 ];
249
250 foreach ($patterns as $type => $pattern) {
251 if (preg_match($pattern, $password)) {
252 $strength['score']++;
253 }
254 }
255
256 // Check against common passwords
257 if (self::is_common_password($password)) {
258 $strength['valid'] = false;
259 $strength['feedback'][] = esc_html__('This password is too common. Please choose a more unique password.', 'king-addons');
260 }
261
262 // Length bonus
263 if (strlen($password) >= 12) {
264 $strength['score']++;
265 }
266
267 // Determine if password is strong enough
268 if ($strength['score'] < 3) {
269 $strength['valid'] = false;
270 $strength['feedback'][] = esc_html__('Password should contain a mix of uppercase, lowercase, numbers, and special characters.', 'king-addons');
271 }
272
273 return $strength;
274 }
275
276 /**
277 * Private helper methods
278 */
279 private static function get_client_ip()
280 {
281 $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
282
283 foreach ($ip_keys as $key) {
284 if (!empty($_SERVER[$key])) {
285 $ip = trim($_SERVER[$key]);
286 // Handle comma-separated IPs (from load balancers)
287 if (strpos($ip, ',') !== false) {
288 $ip = trim(explode(',', $ip)[0]);
289 }
290 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
291 return $ip;
292 }
293 }
294 }
295
296 return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
297 }
298
299 private static function get_max_attempts($action)
300 {
301 switch ($action) {
302 case 'login':
303 return self::MAX_LOGIN_ATTEMPTS;
304 case 'register':
305 return self::MAX_REGISTER_ATTEMPTS;
306 case 'lostpassword':
307 return self::MAX_LOST_PASSWORD_ATTEMPTS;
308 default:
309 return 3;
310 }
311 }
312
313 private static function contains_malicious_content($content)
314 {
315 $malicious_patterns = [
316 '/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi',
317 '/javascript:/i',
318 '/data:text\/html/i',
319 '/\bon\w+\s*=/i', // Event handlers like onclick
320 '/eval\s*\(/i',
321 '/exec\s*\(/i'
322 ];
323
324 foreach ($malicious_patterns as $pattern) {
325 if (preg_match($pattern, $content)) {
326 return true;
327 }
328 }
329
330 return false;
331 }
332
333 private static function is_safe_image_url($url)
334 {
335 // Only allow images from trusted domains
336 $trusted_domains = [
337 'lh3.googleusercontent.com', // Google profile pictures
338 'platform-lookaside.fbsbx.com', // Facebook profile pictures
339 'graph.facebook.com', // Facebook graph API
340 'scontent.xx.fbcdn.net' // Facebook CDN
341 ];
342
343 $parsed_url = parse_url($url);
344 $domain = $parsed_url['host'] ?? '';
345
346 return in_array($domain, $trusted_domains);
347 }
348
349 private static function is_safe_email_domain($email)
350 {
351 // Check against known suspicious domains
352 $suspicious_domains = [
353 'guerrillamail.com',
354 '10minutemail.com',
355 'mailinator.com',
356 'tempmail.org'
357 ];
358
359 $domain = substr(strrchr($email, "@"), 1);
360 return !in_array(strtolower($domain), $suspicious_domains);
361 }
362
363 private static function is_common_password($password)
364 {
365 $common_passwords = [
366 'password', '123456', '123456789', 'qwerty', 'abc123',
367 'password123', 'admin', 'letmein', 'welcome', 'monkey',
368 'dragon', 'master', 'sunshine', 'princess', 'football'
369 ];
370
371 return in_array(strtolower($password), $common_passwords);
372 }
373 }