Changeset 3488388
- Timestamp:
- 03/22/2026 07:54:44 PM (7 days ago)
- Location:
- freelancebo-sentra-control/trunk
- Files:
-
- 11 edited
-
admin/views/settings.php (modified) (1 diff)
-
freelancebo-sentra-control.php (modified) (6 diffs)
-
includes/class-sentra-api-client.php (modified) (1 diff)
-
includes/class-sentra-event-queue.php (modified) (1 diff)
-
includes/class-sentra-heartbeat.php (modified) (3 diffs)
-
includes/modules/class-sentra-auto-patcher.php (modified) (5 diffs)
-
includes/modules/class-sentra-firewall.php (modified) (3 diffs)
-
includes/modules/class-sentra-ip-blocker.php (modified) (1 diff)
-
includes/modules/class-sentra-login-guard.php (modified) (4 diffs)
-
includes/modules/class-sentra-malware-scanner.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
freelancebo-sentra-control/trunk/admin/views/settings.php
r3485719 r3488388 62 62 </td> 63 63 </tr> 64 <tr> 65 <th scope="row"><label for="sentra_auto_patch_enabled"><?php echo esc_html__( 'Auto-Patching', 'freelancebo-sentra-control' ); ?></label></th> 66 <td> 67 <label> 68 <input type="checkbox" id="sentra_auto_patch_enabled" name="sentra_auto_patch_enabled" value="1" 69 <?php checked(get_option('sentra_auto_patch_enabled', '0'), '1'); ?> /> 70 <?php echo esc_html__( 'Enable automatic patching from server (without manual approval)', 'freelancebo-sentra-control' ); ?> 71 </label> 72 <p class="description"> 73 <?php echo esc_html__( 'When disabled (default), patches pushed from the server require manual approval in the Auto-Patch page.', 'freelancebo-sentra-control' ); ?> 74 </p> 75 </td> 76 </tr> 77 <tr> 78 <th scope="row"><label for="sentra_ssl_verify"><?php echo esc_html__( 'SSL Verification', 'freelancebo-sentra-control' ); ?></label></th> 79 <td> 80 <label> 81 <input type="checkbox" id="sentra_ssl_verify" name="sentra_ssl_verify" value="1" 82 <?php checked(get_option('sentra_ssl_verify', '1'), '1'); ?> /> 83 <?php echo esc_html__( 'Verify SSL certificates when connecting to Sentra server', 'freelancebo-sentra-control' ); ?> 84 </label> 85 <p class="description" style="color: #b32d2e;"> 86 <?php echo esc_html__( 'Warning: Disabling SSL verification is insecure and should only be used for testing with self-signed certificates.', 'freelancebo-sentra-control' ); ?> 87 </p> 88 </td> 89 </tr> 64 90 </table> 65 91 -
freelancebo-sentra-control/trunk/freelancebo-sentra-control.php
r3488339 r3488388 4 4 * Plugin URI: https://freelancebo.it 5 5 * Description: WordPress security agent - connects to FreelanceBo Sentra Control central console for WAF, malware scanning, brute force protection, and file integrity monitoring. 6 * Version: 2. 3.16 * Version: 2.4.0 7 7 * Author: Freelancebo 8 8 * License: GPL-2.0-or-later … … 12 12 if (!defined('ABSPATH')) exit; 13 13 14 define("SENTRA_VERSION", "2. 3.1");14 define("SENTRA_VERSION", "2.4.0"); 15 15 define('SENTRA_PLUGIN_DIR', plugin_dir_path(__FILE__)); 16 16 define('SENTRA_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 157 157 ]); 158 158 159 register_setting('sentra_settings', 'sentra_auto_patch_enabled', ['sanitize_callback' => 'sanitize_text_field', 'default' => '0']); 160 register_setting('sentra_settings', 'sentra_ssl_verify', ['sanitize_callback' => 'sanitize_text_field', 'default' => '1']); 161 159 162 // AJAX test connection 160 163 add_action('wp_ajax_sentra_test_connection', [$this, 'ajax_test_connection']); … … 202 205 203 206 /** 204 * Allow self-signed SSL for the Sentra server URL .207 * Allow self-signed SSL for the Sentra server URL (only when SSL verify is disabled). 205 208 */ 206 209 public function allow_sentra_ssl($args, $url) { 207 210 $server = get_option('sentra_server_url', ''); 208 211 if ($server && strpos($url, rtrim($server, '/')) === 0) { 209 $args['sslverify'] = false; 210 $args['reject_unsafe_urls'] = false; 212 if (get_option('sentra_ssl_verify', '1') !== '1') { 213 $args['sslverify'] = false; 214 $args['reject_unsafe_urls'] = false; 215 } 211 216 } 212 217 return $args; … … 344 349 'target' => sanitize_text_field($patch_data['target'] ?? ''), 345 350 'severity' => sanitize_text_field($patch_data['severity'] ?? 'medium'), 346 'source_finding' => $ patch_data['source_finding'] ?? [],351 'source_finding' => $this->sanitize_recursive($patch_data['source_finding'] ?? []), 347 352 'patch_payload' => $patch_data['patch_payload'] ?? [], 348 353 ]; … … 478 483 479 484 if (isset($_POST['body'])) { 480 $raw_body = json_decode( sanitize_text_field(wp_unslash($_POST['body'])), true);485 $raw_body = json_decode(wp_unslash($_POST['body']), true); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_recursive below handles sanitization 481 486 if (is_array($raw_body)) { 482 487 $body = $this->sanitize_recursive($raw_body); -
freelancebo-sentra-control/trunk/includes/class-sentra-api-client.php
r3485668 r3488388 33 33 $signature = hash_hmac('sha256', $sign_string, $this->api_secret); 34 34 35 $sslverify = get_option('sentra_ssl_verify', '1') === '1'; 36 35 37 $args = [ 36 38 'method' => strtoupper($method), 37 39 'timeout' => 15, 38 'sslverify' => false,39 'reject_unsafe_urls' => false,40 'sslverify' => $sslverify, 41 'reject_unsafe_urls' => $sslverify, 40 42 'headers' => [ 41 43 'Content-Type' => 'application/json', -
freelancebo-sentra-control/trunk/includes/class-sentra-event-queue.php
r3487458 r3488388 92 92 private function get_client_ip() { 93 93 $remote_addr = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1')); 94 $trusted_proxies = array_filter(array_map('trim', explode(',', get_option('sentra_trusted_proxies', '')))); 94 $trusted_proxies = array_filter( 95 array_map('trim', explode(',', get_option('sentra_trusted_proxies', ''))), 96 function($ip) { return filter_var($ip, FILTER_VALIDATE_IP) !== false; } 97 ); 95 98 96 99 // Only trust proxy headers if request comes from a trusted proxy -
freelancebo-sentra-control/trunk/includes/class-sentra-heartbeat.php
r3488339 r3488388 66 66 } 67 67 68 // Anti-replay: reject duplicate signatures within 2 minutes 69 $nonce_key = 'sentra_nonce_' . md5($signature); 70 if (get_transient($nonce_key)) { 71 return new WP_Error('auth_failed', __( 'Replay detected', 'freelancebo-sentra-control' ), ['status' => 401]); 72 } 73 set_transient($nonce_key, 1, 120); 74 68 75 return true; 69 76 } … … 74 81 */ 75 82 public function rest_run_pending( $request ) { 83 // Rate limiting: 1 request per 10 seconds per IP 84 $rate_key = 'sentra_rest_rate_' . md5(isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''); 85 if (get_transient($rate_key)) { 86 return new WP_REST_Response(['error' => 'Rate limited'], 429); 87 } 88 set_transient($rate_key, 1, 10); 89 76 90 // Always send heartbeat when server pushes 77 91 $this->send_heartbeat(); … … 201 215 } 202 216 } 203 -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-auto-patcher.php
r3487530 r3488388 61 61 } 62 62 63 // Anti-replay: reject duplicate signatures within 2 minutes 64 $nonce_key = 'sentra_nonce_' . md5($signature); 65 if (get_transient($nonce_key)) { 66 return new WP_Error('auth_failed', 'Replay detected', ['status' => 401]); 67 } 68 set_transient($nonce_key, 1, 120); 69 63 70 return true; 64 71 } … … 68 75 */ 69 76 public function rest_auto_patch($request) { 77 // Rate limiting: 1 request per 10 seconds per IP 78 $rate_key = 'sentra_rest_patch_rate_' . md5(isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''); 79 if (get_transient($rate_key)) { 80 return new WP_REST_Response(['error' => 'Rate limited'], 429); 81 } 82 set_transient($rate_key, 1, 10); 83 70 84 $this->process_pending_patches(); 71 85 return new WP_REST_Response(['status' => 'ok'], 200); … … 99 113 */ 100 114 public function process_pending_patches() { 115 if (get_option('sentra_auto_patch_enabled', '0') !== '1') { 116 return; // Auto-patching disabled, admin must approve manually 117 } 118 101 119 $pending = $this->api->get('/agent/pending-patches'); 102 120 if (!is_array($pending) || empty($pending) || isset($pending['error'])) { … … 432 450 if (!$this->validate_php_syntax($content)) { 433 451 return ['status' => 'error', 'message' => 'PHP syntax error after patch, not applied']; 452 } 453 454 // Allow filtering/blocking of surgical patches 455 $approved = apply_filters('sentra_surgical_patch_approved', true, $file_path, $content); 456 if (!$approved) { 457 return ['status' => 'skipped', 'message' => 'Patch requires manual approval']; 434 458 } 435 459 … … 727 751 file_put_contents($index, "<?php // Silence is golden\n"); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents 728 752 } 753 // Add nginx protection note 754 $nginx_note_file = $dir . '/.nginx-deny'; 755 if (!file_exists($nginx_note_file)) { 756 $nginx_note = "# If using Nginx, add this to your server block:\n# location ~* /wp-content/(sentra-backups|sentra-quarantine)/ { deny all; }\n"; 757 @file_put_contents($nginx_note_file, $nginx_note); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents 758 } 729 759 } 730 760 } -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-firewall.php
r3485789 r3488388 51 51 $content_type = isset($_SERVER['CONTENT_TYPE']) ? sanitize_text_field(wp_unslash($_SERVER['CONTENT_TYPE'])) : ''; 52 52 if (stripos($content_type, 'multipart/form-data') === false) { 53 $body = file_get_contents('php://input'); 54 if (strlen($body) > 10000) { 55 $body = substr($body, 0, 10000); 53 $content_length = isset($_SERVER['CONTENT_LENGTH']) ? intval($_SERVER['CONTENT_LENGTH']) : 0; 54 if ($content_length > 10240) { 55 $body = ''; // Skip reading very large bodies 56 } else { 57 $body = substr(@file_get_contents('php://input'), 0, 10240); 56 58 } 57 59 } … … 121 123 $content_type = isset($_SERVER['CONTENT_TYPE']) ? sanitize_text_field(wp_unslash($_SERVER['CONTENT_TYPE'])) : ''; 122 124 if (stripos($content_type, 'multipart/form-data') === false) { 123 $body = file_get_contents('php://input'); 124 if (strlen($body) > 10000) { 125 $body = substr($body, 0, 10000); 125 $content_length = isset($_SERVER['CONTENT_LENGTH']) ? intval($_SERVER['CONTENT_LENGTH']) : 0; 126 if ($content_length > 10240) { 127 $body = ''; // Skip reading very large bodies 128 } else { 129 $body = substr(@file_get_contents('php://input'), 0, 10240); 126 130 } 127 131 } … … 174 178 175 179 if (isset($result['firewall_rules'])) { 176 update_option('sentra_firewall_rules', $result['firewall_rules'], false); 180 $rules = $result['firewall_rules']; 181 if (is_array($rules)) { 182 foreach ($rules as $key => $rule) { 183 if (isset($rule['pattern']) && @preg_match('/' . $rule['pattern'] . '/', '') === false) { 184 unset($rules[$key]); // Remove invalid regex 185 } 186 if (isset($rule['pattern']) && strlen($rule['pattern']) > 500) { 187 unset($rules[$key]); // Remove overly long patterns 188 } 189 } 190 $rules = array_values($rules); // Re-index 191 } 192 update_option('sentra_firewall_rules', $rules, false); 177 193 } 178 194 if (isset($result['blocked_ips'])) { -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-ip-blocker.php
r3485648 r3488388 69 69 private function get_client_ip() { 70 70 $remote_addr = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1')); 71 $trusted_proxies = array_filter(array_map('trim', explode(',', get_option('sentra_trusted_proxies', '')))); 71 $trusted_proxies = array_filter( 72 array_map('trim', explode(',', get_option('sentra_trusted_proxies', ''))), 73 function($ip) { return filter_var($ip, FILTER_VALIDATE_IP) !== false; } 74 ); 72 75 73 76 // Only trust proxy headers if request comes from a trusted proxy -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-login-guard.php
r3485648 r3488388 30 30 } 31 31 32 $attempts = get_transient('sentra_login_attempts_' . md5($ip));33 $user_attempts = get_transient('sentra_login_attempts_user_' . md5($username));32 $attempts = get_transient('sentra_login_attempts_' . wp_hash($ip)); 33 $user_attempts = get_transient('sentra_login_attempts_user_' . wp_hash($username)); 34 34 35 35 if (($attempts && $attempts >= $this->max_attempts) || ($user_attempts && $user_attempts >= $this->max_attempts)) { … … 54 54 public function record_failed_login($username, $error = null) { 55 55 $ip = $this->get_client_ip(); 56 $key = 'sentra_login_attempts_' . md5($ip);56 $key = 'sentra_login_attempts_' . wp_hash($ip); 57 57 $attempts = (int) get_transient($key); 58 58 $attempts++; 59 59 set_transient($key, $attempts, $this->lockout_duration); 60 60 61 $user_key = 'sentra_login_attempts_user_' . md5($username);61 $user_key = 'sentra_login_attempts_user_' . wp_hash($username); 62 62 $user_attempts = (int) get_transient($user_key); 63 63 $user_attempts++; … … 126 126 public function record_successful_login($user_login, $user) { 127 127 $ip = $this->get_client_ip(); 128 delete_transient('sentra_login_attempts_' . md5($ip));129 delete_transient('sentra_login_attempts_user_' . md5($user_login));128 delete_transient('sentra_login_attempts_' . wp_hash($ip)); 129 delete_transient('sentra_login_attempts_user_' . wp_hash($user_login)); 130 130 131 131 // Deduplicate: skip if same user+IP already logged in recently (30 min) … … 149 149 private function get_client_ip() { 150 150 $remote_addr = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1')); 151 $trusted_proxies = array_filter(array_map('trim', explode(',', get_option('sentra_trusted_proxies', '')))); 151 $trusted_proxies = array_filter( 152 array_map('trim', explode(',', get_option('sentra_trusted_proxies', ''))), 153 function($ip) { return filter_var($ip, FILTER_VALIDATE_IP) !== false; } 154 ); 152 155 153 156 // Only trust proxy headers if request comes from a trusted proxy -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-malware-scanner.php
r3488330 r3488388 88 88 ]; 89 89 90 private $chunk_size = 500; // files per tick90 private $chunk_size = 2000; // files per tick 91 91 92 92 public function __construct(Sentra_API_Client $api) { -
freelancebo-sentra-control/trunk/readme.txt
r3488339 r3488388 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 2. 3.17 Stable tag: 2.4.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 74 74 75 75 == Changelog == 76 77 = 2.4.0 = 78 * Security: SSL verification now configurable with default ON (C3) 79 * Security: Auto-patching from server now disabled by default, requires admin approval (C4) 80 * Security: Added filter hook for surgical patch approval (C5) 81 * Security: HMAC nonce anti-replay protection on REST API endpoints (H8) 82 * Security: Firewall regex rules validated on sync (H10) 83 * Security: Nginx deny rule file added to backup/quarantine directories (H11) 84 * Security: Recursive sanitization on source_finding in auto-patch (M11) 85 * Security: Rate limiting on REST API endpoints (M14) 86 * Security: Login guard uses wp_hash instead of md5 for transient keys (M15) 87 * Security: Content length check before reading php://input in firewall (M16) 88 * Improved: Malware scanner file limit increased from 500 to 2000 (L1) 89 * Fixed: JSON body double-sanitization in AJAX proxy (L3) 90 * Security: Trusted proxy IPs validated with FILTER_VALIDATE_IP (L5) 76 91 77 92 = 2.3.1 = … … 209 224 == Upgrade Notice == 210 225 226 = 2.4.0 = 227 Security hardening: SSL verify default ON, auto-patch requires approval, HMAC anti-replay, rate limiting, firewall regex validation. Recommended for all users. 228 211 229 = 2.3.1 = 212 230 Fixes heartbeat reliability on low-traffic sites. Recommended for all users.
Note: See TracChangeset
for help on using the changeset viewer.