Changeset 3391994
- Timestamp:
- 11/08/2025 01:45:40 AM (5 weeks ago)
- Location:
- ht-security
- Files:
-
- 24 added
- 2 edited
-
tags/1.3.3 (added)
-
tags/1.3.3/ht-security.php (added)
-
tags/1.3.3/includes (added)
-
tags/1.3.3/includes/admin-page.php (added)
-
tags/1.3.3/includes/cve-check.php (added)
-
tags/1.3.3/includes/file-permissions.php (added)
-
tags/1.3.3/includes/login-alerts.php (added)
-
tags/1.3.3/includes/maintenance-mode.php (added)
-
tags/1.3.3/includes/plugin-indicators.php (added)
-
tags/1.3.3/includes/security-headers.php (added)
-
tags/1.3.3/includes/settings.php (added)
-
tags/1.3.3/includes/user-enumeration.php (added)
-
tags/1.3.3/readme.txt (added)
-
tags/1.3.3/security.txt (added)
-
trunk/ht-security.php (modified) (1 diff)
-
trunk/includes (added)
-
trunk/includes/admin-page.php (added)
-
trunk/includes/cve-check.php (added)
-
trunk/includes/file-permissions.php (added)
-
trunk/includes/login-alerts.php (added)
-
trunk/includes/maintenance-mode.php (added)
-
trunk/includes/plugin-indicators.php (added)
-
trunk/includes/security-headers.php (added)
-
trunk/includes/settings.php (added)
-
trunk/includes/user-enumeration.php (added)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ht-security/trunk/ht-security.php
r3391451 r3391994 1 1 <?php 2 /* 3 Plugin Name: HT Security 4 Description: Suite de Segurança completa: Cabeçalhos de segurança, alertas por e-mail, verificação do Core, bloqueio de enumeração de usuários, modo de manutenção e auditoria de permissões. 5 Version: 1.3.2 6 Requires at least: 6.5 7 Requires PHP: 8.2 8 Tested up to: 6.8 9 Author: WPFastSec 10 Author URI: https://wpfastsec.com 11 License: GPLv2 or later 12 Text Domain: ht-security 13 */ 2 /** 3 * Plugin Name: HT Security 4 * Description: Suite de Segurança completa: Cabeçalhos de segurança, alertas por e-mail, verificação do Core, bloqueio de enumeração de usuários, modo de manutenção e auditoria de permissões. 5 * Version: 1.3.3 6 * Requires at least: 6.5 7 * Requires PHP: 8.2 8 * Tested up to: 6.8 9 * Author: WPFastSec 10 * Author URI: https://wpfastsec.com 11 * License: GPLv2 or later 12 * Text Domain: ht-security 13 * 14 * @package HT_Security 15 * @since 1.0.0 16 */ 14 17 15 18 if ( ! defined( 'ABSPATH' ) ) { 16 exit; 19 exit; // Exit if accessed directly. 17 20 } 18 21 19 22 /** 20 * Injeta cabeçalhos de segurança no front-end.23 * Define constantes do plugin. 21 24 */ 22 add_filter( 'wp_headers', 'htsec_security_headers' ); 23 function htsec_security_headers( $headers ) { 24 if ( ! get_option( 'htsec_enable_headers', 1 ) ) { 25 return $headers; 26 } 27 28 $defaults = [ 29 'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload', 30 'X-Frame-Options' => 'SAMEORIGIN', 31 'X-Content-Type-Options' => 'nosniff', 32 'Referrer-Policy' => 'strict-origin-when-cross-origin', 33 'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()', 34 'Content-Security-Policy' => 'upgrade-insecure-requests;', 35 ]; 36 37 return array_merge( $headers, $defaults ); 38 } 25 define( 'HT_SECURITY_VERSION', '1.3.3' ); 26 define( 'HT_SECURITY_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 27 define( 'HT_SECURITY_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 39 28 40 29 /** 41 * Registra seções e campos de configuração. 30 * Carrega os módulos do plugin. 31 * 32 * Cada módulo é responsável por uma funcionalidade específica, 33 * mantendo o código organizado e facilitando a manutenção. 34 * 35 * @since 1.3.3 42 36 */ 43 add_action( 'admin_init', 'htsec_register_settings' ); 44 function htsec_register_settings() { 45 register_setting( 'htsec_settings_group', 'htsec_enable_headers', [ 46 'type' => 'boolean', 47 'sanitize_callback' => 'rest_sanitize_boolean', 48 'default' => 1, 49 ] ); 50 register_setting( 'htsec_settings_group', 'htsec_enable_login_alerts', [ 51 'type' => 'boolean', 52 'sanitize_callback' => 'rest_sanitize_boolean', 53 'default' => 0, 54 ] ); 55 register_setting( 'htsec_settings_group', 'htsec_alert_email', [ 56 'type' => 'string', 57 'sanitize_callback' => 'sanitize_email', 58 ] ); 59 register_setting( 'htsec_settings_group', 'htsec_disable_user_enumeration', [ 60 'type' => 'boolean', 61 'sanitize_callback' => 'rest_sanitize_boolean', 62 'default' => 0, 63 ] ); 64 register_setting( 'htsec_settings_group', 'htsec_maintenance_mode', [ 65 'type' => 'boolean', 66 'sanitize_callback' => 'rest_sanitize_boolean', 67 'default' => 0, 68 ] ); 69 register_setting( 'htsec_settings_group', 'htsec_maintenance_ips', [ 70 'type' => 'string', 71 'sanitize_callback' => 'sanitize_textarea_field', 72 'default' => '', 73 ] ); 74 register_setting( 'htsec_settings_group', 'htsec_nvd_api_key', [ 75 'type' => 'string', 76 'sanitize_callback' => 'sanitize_text_field', 77 'default' => '', 78 ] ); 79 register_setting( 'htsec_settings_group', 'htsec_enable_cve_alerts', [ 80 'type' => 'boolean', 81 'sanitize_callback' => 'rest_sanitize_boolean', 82 'default' => 0, 83 ] ); 84 register_setting( 'htsec_settings_group', 'htsec_show_plugin_badges', [ 85 'type' => 'boolean', 86 'sanitize_callback' => 'rest_sanitize_boolean', 87 'default' => 0, 88 ] ); 37 function htsec_load_modules() { 38 $modules = [ 39 'security-headers', // Cabeçalhos de segurança HTTP. 40 'settings', // Registro de configurações e campos. 41 'login-alerts', // Alertas de login bem-sucedido/falho. 42 'user-enumeration', // Proteção contra enumeração de usuários. 43 'maintenance-mode', // Modo de manutenção com whitelist de IPs. 44 'file-permissions', // Auditoria de permissões de arquivos. 45 'cve-check', // Verificação de vulnerabilidades CVE. 46 'admin-page', // Página administrativa e feedback. 47 'plugin-indicators', // Indicadores de vulnerabilidades em plugins. 48 ]; 89 49 90 add_settings_section( 91 'htsec_main_section', 92 esc_html__( 'Configurações de Segurança', 'ht-security' ), 93 null, 94 'ht-security' 95 ); 50 foreach ( $modules as $module ) { 51 $file = HT_SECURITY_PLUGIN_DIR . 'includes/' . $module . '.php'; 96 52 97 add_settings_field( 98 'htsec_enable_headers', 99 esc_html__( 'Ativar Cabeçalhos de Segurança', 'ht-security' ), 100 function() { 101 $value = get_option( 'htsec_enable_headers', 1 ); 102 printf( 103 '<input type="checkbox" name="htsec_enable_headers" value="1" %s> %s', 104 checked( 1, $value, false ), 105 esc_html__( 'Sim', 'ht-security' ) 106 ); 107 }, 108 'ht-security', 109 'htsec_main_section' 110 ); 111 112 add_settings_field( 113 'htsec_enable_login_alerts', 114 esc_html__( 'Alertas de Login', 'ht-security' ), 115 function() { 116 $value = get_option( 'htsec_enable_login_alerts', 0 ); 117 printf( 118 '<input type="checkbox" name="htsec_enable_login_alerts" value="1" %s> %s', 119 checked( 1, $value, false ), 120 esc_html__( 'Enviar e-mail em logins bem-sucedidos e falhos', 'ht-security' ) 121 ); 122 }, 123 'ht-security', 124 'htsec_main_section' 125 ); 126 127 add_settings_field( 128 'htsec_alert_email', 129 esc_html__( 'E-mail para Alertas', 'ht-security' ), 130 function() { 131 $value = get_option( 'htsec_alert_email', get_option( 'admin_email' ) ); 132 printf( 133 '<input type="email" name="htsec_alert_email" value="%s" class="regular-text">', 134 esc_attr( $value ) 135 ); 136 }, 137 'ht-security', 138 'htsec_main_section' 139 ); 140 141 add_settings_field( 142 'htsec_disable_user_enumeration', 143 esc_html__( 'Desativar Enumeração de Usuários', 'ht-security' ), 144 function() { 145 $value = get_option( 'htsec_disable_user_enumeration', 0 ); 146 printf( 147 '<input type="checkbox" name="htsec_disable_user_enumeration" value="1" %s> %s', 148 checked( 1, $value, false ), 149 esc_html__( 'Bloquear enumeração de usuários via API REST', 'ht-security' ) 150 ); 151 echo '<p class="description">' . esc_html__( 'Impede que usuários sejam listados através da API REST do WordPress.', 'ht-security' ) . '</p>'; 152 }, 153 'ht-security', 154 'htsec_main_section' 155 ); 156 157 add_settings_field( 158 'htsec_maintenance_mode', 159 esc_html__( 'Modo de Manutenção', 'ht-security' ), 160 function() { 161 $value = get_option( 'htsec_maintenance_mode', 0 ); 162 printf( 163 '<input type="checkbox" name="htsec_maintenance_mode" value="1" %s> %s', 164 checked( 1, $value, false ), 165 esc_html__( 'Ativar modo de manutenção', 'ht-security' ) 166 ); 167 echo '<p class="description">' . esc_html__( 'Apenas IPs autorizados poderão acessar o site.', 'ht-security' ) . '</p>'; 168 }, 169 'ht-security', 170 'htsec_main_section' 171 ); 172 173 add_settings_field( 174 'htsec_maintenance_ips', 175 esc_html__( 'IPs Autorizados', 'ht-security' ), 176 function() { 177 $value = get_option( 'htsec_maintenance_ips', '' ); 178 printf( 179 '<textarea name="htsec_maintenance_ips" rows="5" cols="50" class="regular-text">%s</textarea>', 180 esc_textarea( $value ) 181 ); 182 echo '<p class="description">' . esc_html__( 'Digite um IP por linha. Estes IPs poderão acessar o site em modo de manutenção.', 'ht-security' ) . '</p>'; 183 $current_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); 184 if ( $current_ip ) { 185 /* translators: %s: Current user IP address */ 186 echo '<p class="description">' . sprintf( esc_html__( 'Seu IP atual: %s', 'ht-security' ), esc_html( $current_ip ) ) . '</p>'; 187 } 188 }, 189 'ht-security', 190 'htsec_main_section' 191 ); 192 193 add_settings_field( 194 'htsec_nvd_api_key', 195 esc_html__( 'API Key NVD (Opcional)', 'ht-security' ), 196 function() { 197 $value = get_option( 'htsec_nvd_api_key', '' ); 198 printf( 199 '<input type="text" name="htsec_nvd_api_key" value="%s" class="regular-text">', 200 esc_attr( $value ) 201 ); 202 echo '<p class="description">' . esc_html__( 'API Key do National Vulnerability Database. Sem API Key: 5 requisições/30s. Com API Key: 50 requisições/30s.', 'ht-security' ) . '</p>'; 203 echo '<p class="description">' . esc_html__( 'Obtenha sua API Key em: https://nvd.nist.gov/developers/request-an-api-key', 'ht-security' ) . '</p>'; 204 }, 205 'ht-security', 206 'htsec_main_section' 207 ); 208 209 add_settings_field( 210 'htsec_enable_cve_alerts', 211 esc_html__( 'Alertas de Vulnerabilidades', 'ht-security' ), 212 function() { 213 $value = get_option( 'htsec_enable_cve_alerts', 0 ); 214 printf( 215 '<input type="checkbox" name="htsec_enable_cve_alerts" value="1" %s> %s', 216 checked( 1, $value, false ), 217 esc_html__( 'Enviar e-mail quando vulnerabilidades CVE forem detectadas', 'ht-security' ) 218 ); 219 echo '<p class="description">' . esc_html__( 'Verificação automática a cada 12 horas.', 'ht-security' ) . '</p>'; 220 }, 221 'ht-security', 222 'htsec_main_section' 223 ); 224 225 add_settings_field( 226 'htsec_show_plugin_badges', 227 esc_html__( 'Indicadores na Página de Plugins', 'ht-security' ), 228 function() { 229 $value = get_option( 'htsec_show_plugin_badges', 0 ); 230 printf( 231 '<input type="checkbox" name="htsec_show_plugin_badges" value="1" %s> %s', 232 checked( 1, $value, false ), 233 esc_html__( 'Exibir badges de segurança na página de plugins', 'ht-security' ) 234 ); 235 echo '<p class="description">' . esc_html__( 'Mostra indicadores verdes/vermelhos em cada plugin indicando status de vulnerabilidades.', 'ht-security' ) . '</p>'; 236 }, 237 'ht-security', 238 'htsec_main_section' 239 ); 53 if ( file_exists( $file ) ) { 54 require_once $file; 55 } else { 56 // Log de erro se algum módulo não for encontrado. 57 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 58 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 59 error_log( sprintf( 'HT Security: Módulo "%s" não encontrado em %s', $module, $file ) ); 60 } 61 } 62 } 240 63 } 241 242 /** 243 * Adiciona página de configurações no menu. 244 */ 245 add_action( 'admin_menu', function() { 246 add_options_page( 247 esc_html__( 'HT Security', 'ht-security' ), 248 esc_html__( 'HT Security', 'ht-security' ), 249 'manage_options', 250 'ht-security', 251 'htsec_plugin_settings_page' 252 ); 253 } ); 254 255 /** 256 * Adiciona estilos CSS personalizados na página de administração. 257 */ 258 add_action( 'admin_head', 'htsec_admin_styles' ); 259 function htsec_admin_styles() { 260 $screen = get_current_screen(); 261 if ( isset( $screen->id ) && 'settings_page_ht-security' === $screen->id ) { 262 ?> 263 <style> 264 .htsec-alert-success { 265 padding: 15px; 266 background-color: #d4edda; 267 border: 1px solid #c3e6cb; 268 border-radius: 4px; 269 margin: 20px 0; 270 } 271 .htsec-alert-success p { 272 margin: 0; 273 color: #155724; 274 font-weight: bold; 275 } 276 .htsec-alert-danger { 277 padding: 15px; 278 background-color: #f8d7da; 279 border: 1px solid #f5c6cb; 280 border-radius: 4px; 281 margin: 20px 0; 282 } 283 .htsec-alert-danger p { 284 margin: 0; 285 color: #721c24; 286 font-weight: bold; 287 } 288 .htsec-vuln-card { 289 border: 1px solid #dee2e6; 290 border-radius: 8px; 291 padding: 20px; 292 margin-bottom: 15px; 293 background-color: #fff; 294 box-shadow: 0 2px 4px rgba(0,0,0,0.1); 295 transition: box-shadow 0.3s ease; 296 } 297 .htsec-vuln-card:hover { 298 box-shadow: 0 4px 8px rgba(0,0,0,0.15); 299 } 300 .htsec-vuln-header { 301 display: flex; 302 justify-content: space-between; 303 align-items: flex-start; 304 margin-bottom: 15px; 305 flex-wrap: wrap; 306 gap: 10px; 307 } 308 .htsec-vuln-title { 309 margin: 0; 310 font-size: 16px; 311 color: #333; 312 } 313 .htsec-severity-badge { 314 padding: 6px 14px; 315 border-radius: 4px; 316 font-size: 12px; 317 font-weight: bold; 318 text-transform: uppercase; 319 letter-spacing: 0.5px; 320 } 321 .htsec-vuln-detail { 322 margin-bottom: 12px; 323 } 324 .htsec-vuln-detail strong { 325 color: #555; 326 } 327 .htsec-vuln-description { 328 background-color: #f8f9fa; 329 padding: 12px; 330 border-left: 3px solid #007cba; 331 margin-top: 5px; 332 color: #666; 333 line-height: 1.6; 334 } 335 .htsec-cve-link { 336 color: #007cba; 337 text-decoration: none; 338 font-weight: 500; 339 } 340 .htsec-cve-link:hover { 341 text-decoration: underline; 342 } 343 .htsec-cvss-score { 344 background-color: #e9ecef; 345 padding: 2px 8px; 346 border-radius: 3px; 347 font-family: monospace; 348 font-weight: bold; 349 } 350 .htsec-check-info { 351 background-color: #f0f6fc; 352 border-left: 4px solid #0969da; 353 padding: 12px 16px; 354 margin: 20px 0; 355 } 356 .htsec-progress-item { 357 padding: 8px 12px; 358 background-color: #f8f9fa; 359 border-left: 3px solid #0969da; 360 margin-bottom: 8px; 361 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 362 font-size: 13px; 363 } 364 .htsec-stats-box { 365 background-color: #fff; 366 border: 1px solid #ddd; 367 border-radius: 4px; 368 padding: 15px; 369 margin: 15px 0; 370 } 371 .htsec-stats-box strong { 372 color: #0073aa; 373 } 374 #htsec-feedback-section { 375 background-color: #fff; 376 border: 1px solid #ddd; 377 border-radius: 4px; 378 padding: 20px; 379 margin: 20px 0; 380 } 381 #htsec-feedback-form textarea { 382 width: 100%; 383 border: 1px solid #ddd; 384 border-radius: 4px; 385 padding: 10px; 386 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 387 resize: vertical; 388 } 389 #htsec-feedback-form textarea:focus { 390 border-color: #0073aa; 391 outline: none; 392 box-shadow: 0 0 0 1px #0073aa; 393 } 394 .htsec-feedback-success { 395 padding: 12px 15px; 396 background-color: #d4edda; 397 border: 1px solid #c3e6cb; 398 border-radius: 4px; 399 color: #155724; 400 } 401 .htsec-feedback-error { 402 padding: 12px 15px; 403 background-color: #f8d7da; 404 border: 1px solid #f5c6cb; 405 border-radius: 4px; 406 color: #721c24; 407 } 408 #htsec-send-feedback-btn:disabled, 409 #htsec-send-feedback-btn.disabled { 410 opacity: 0.6; 411 cursor: not-allowed; 412 } 413 </style> 414 <?php 415 } 416 } 417 418 /** 419 * Adiciona JavaScript para o formulário de feedback. 420 */ 421 add_action( 'admin_footer', 'htsec_feedback_script' ); 422 function htsec_feedback_script() { 423 $screen = get_current_screen(); 424 if ( isset( $screen->id ) && 'settings_page_ht-security' === $screen->id ) { 425 ?> 426 <script type="text/javascript"> 427 jQuery(document).ready(function($) { 428 // Mostrar formulário de feedback 429 $('#htsec-show-feedback-btn').on('click', function() { 430 $('#htsec-feedback-form').slideDown(); 431 $(this).hide(); 432 $('#htsec-feedback-message').focus(); 433 }); 434 435 // Cancelar feedback 436 $('#htsec-cancel-feedback-btn').on('click', function() { 437 $('#htsec-feedback-form').slideUp(); 438 $('#htsec-show-feedback-btn').show(); 439 $('#htsec-feedback-message').val(''); 440 $('#htsec-feedback-response').html(''); 441 }); 442 443 // Enviar feedback 444 $('#htsec-send-feedback-btn').on('click', function() { 445 var $btn = $(this); 446 var $textarea = $('#htsec-feedback-message'); 447 var $response = $('#htsec-feedback-response'); 448 var message = $textarea.val().trim(); 449 450 // Validar mensagem 451 if (message === '') { 452 $response.html('<div class="htsec-feedback-error"><?php echo esc_js( __( 'Por favor, digite uma mensagem antes de enviar.', 'ht-security' ) ); ?></div>'); 453 return; 454 } 455 456 if (message.length < 10) { 457 $response.html('<div class="htsec-feedback-error"><?php echo esc_js( __( 'A mensagem deve ter pelo menos 10 caracteres.', 'ht-security' ) ); ?></div>'); 458 return; 459 } 460 461 // Desabilitar botão e textarea 462 $btn.prop('disabled', true).text('<?php echo esc_js( __( 'Enviando...', 'ht-security' ) ); ?>'); 463 $textarea.prop('disabled', true); 464 $response.html(''); 465 466 // Enviar via AJAX 467 $.post(ajaxurl, { 468 action: 'htsec_send_feedback', 469 message: message, 470 nonce: '<?php echo esc_js( wp_create_nonce( 'htsec_send_feedback' ) ); ?>' 471 }, function(response) { 472 if (response.success) { 473 $response.html('<div class="htsec-feedback-success">' + response.data.message + '</div>'); 474 $textarea.val(''); 475 476 // Ocultar formulário após 3 segundos 477 setTimeout(function() { 478 $('#htsec-feedback-form').slideUp(); 479 $('#htsec-show-feedback-btn').show(); 480 $response.html(''); 481 }, 3000); 482 } else { 483 $response.html('<div class="htsec-feedback-error">' + response.data.message + '</div>'); 484 } 485 }).fail(function() { 486 $response.html('<div class="htsec-feedback-error"><?php echo esc_js( __( 'Erro ao enviar feedback. Por favor, tente novamente mais tarde.', 'ht-security' ) ); ?></div>'); 487 }).always(function() { 488 // Reabilitar botão e textarea 489 $btn.prop('disabled', false).text('<?php echo esc_js( __( 'Enviar Feedback', 'ht-security' ) ); ?>'); 490 $textarea.prop('disabled', false); 491 }); 492 }); 493 }); 494 </script> 495 <?php 496 } 497 } 498 499 /** 500 * Renderiza a página de configurações e faz a verificação de integridade do Core via API. 501 */ 502 function htsec_plugin_settings_page() { 503 echo '<div class="wrap">'; 504 echo '<h1>' . esc_html__( 'HT Security', 'ht-security' ) . '</h1>'; 505 echo '<form method="post" action="options.php">'; 506 settings_fields( 'htsec_settings_group' ); 507 do_settings_sections( 'ht-security' ); 508 509 // Nova funcionalidade: Verificação de integridade do Core do WordPress 510 echo '<h2>' . esc_html__( 'Verificação de Integridade do Core', 'ht-security' ) . '</h2>'; 511 512 $version = get_bloginfo( 'version' ); 513 $locale = defined( 'WP_LOCAL_PACKAGE' ) ? WP_LOCAL_PACKAGE : get_locale(); 514 $endpoint = add_query_arg( [ 515 'version' => rawurlencode( $version ), 516 'locale' => rawurlencode( $locale ), 517 ], 'https://api.wordpress.org/core/checksums/1.0/' ); 518 519 $response = wp_remote_get( $endpoint, [ 'timeout' => 15 ] ); 520 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 521 $endpoint = add_query_arg( [ 'version' => $version, 'locale' => 'en_US' ], 'https://api.wordpress.org/core/checksums/1.0/' ); 522 $response = wp_remote_get( $endpoint, [ 'timeout' => 15 ] ); 523 } 524 525 if ( is_wp_error( $response ) ) { 526 echo '<p>' . esc_html__( 'Não foi possível obter os checksums do Core.', 'ht-security' ) . '</p>'; 527 } else { 528 $data = json_decode( wp_remote_retrieve_body( $response ), true ); 529 530 if ( empty( $data['checksums'] ) || ! is_array( $data['checksums'] ) ) { 531 echo '<p>' . esc_html__( 'Dados de checksums inválidos.', 'ht-security' ) . '</p>'; 532 } else { 533 $checksums = $data['checksums']; 534 $mismatches = []; 535 536 foreach ( $checksums as $file => $expected_hash ) { 537 if ( 0 === strpos( $file, 'wp-content/' ) || 'wp-includes/version.php' === $file ) { 538 continue; 539 } 540 $path = ABSPATH . $file; 541 if ( ! file_exists( $path ) || md5_file( $path ) !== $expected_hash ) { 542 $mismatches[] = $file; 543 } 544 } 545 546 if ( empty( $mismatches ) ) { 547 echo '<p>' . esc_html__( 'O Core do WordPress está íntegro.', 'ht-security' ) . '</p>'; 548 } else { 549 echo '<p>' . esc_html__( 'Foram detectadas alterações no Core do WordPress:', 'ht-security' ) . '</p>'; 550 echo '<ul>'; 551 foreach ( $mismatches as $diff ) { 552 printf( '<li>%s</li>', esc_html( $diff ) ); 553 } 554 echo '</ul>'; 555 } 556 } 557 } 558 559 submit_button(); 560 echo '</form>'; 561 562 // Seção de Feedback 563 echo '<div id="htsec-feedback-section" style="margin-top: 40px;">'; 564 echo '<h2>' . esc_html__( 'Enviar Feedback', 'ht-security' ) . '</h2>'; 565 echo '<p class="description">' . esc_html__( 'Tem alguma sugestão, encontrou um bug ou quer compartilhar sua experiência? Envie-nos seu feedback!', 'ht-security' ) . '</p>'; 566 567 echo '<div id="htsec-feedback-form-container" style="margin-top: 20px;">'; 568 echo '<button type="button" id="htsec-show-feedback-btn" class="button button-secondary">'; 569 echo esc_html__( 'Abrir Formulário de Feedback', 'ht-security' ); 570 echo '</button>'; 571 572 echo '<div id="htsec-feedback-form" style="display: none; margin-top: 15px; max-width: 600px;">'; 573 echo '<textarea id="htsec-feedback-message" rows="6" class="large-text" placeholder="' . esc_attr__( 'Digite seu feedback aqui...', 'ht-security' ) . '"></textarea>'; 574 echo '<div style="margin-top: 10px;">'; 575 echo '<button type="button" id="htsec-send-feedback-btn" class="button button-primary">' . esc_html__( 'Enviar Feedback', 'ht-security' ) . '</button>'; 576 echo ' <button type="button" id="htsec-cancel-feedback-btn" class="button button-secondary">' . esc_html__( 'Cancelar', 'ht-security' ) . '</button>'; 577 echo '</div>'; 578 echo '<div id="htsec-feedback-response" style="margin-top: 15px;"></div>'; 579 echo '</div>'; 580 echo '</div>'; 581 echo '</div>'; 582 583 // Verificação de Vulnerabilidades CVE 584 echo '<div id="cve-check">'; 585 echo '<h2>' . esc_html__( 'Verificação de Vulnerabilidades (CVE)', 'ht-security' ) . '</h2>'; 586 htsec_display_cve_check(); 587 echo '</div>'; 588 589 // Auditoria de Permissões de Arquivos 590 echo '<h2>' . esc_html__( 'Auditoria de Permissões de Arquivos', 'ht-security' ) . '</h2>'; 591 htsec_display_file_permissions_audit(); 592 593 echo '</div>'; 594 } 595 596 /** 597 * Exibe a auditoria de permissões de arquivos. 598 */ 599 function htsec_display_file_permissions_audit() { 600 $files_to_check = [ 601 'wp-config.php' => [ 602 'path' => ABSPATH . 'wp-config.php', 603 'recommended' => '0640', 604 'max' => '0644', 605 'description' => esc_html__( 'Arquivo de configuração principal', 'ht-security' ), 606 ], 607 '.htaccess' => [ 608 'path' => ABSPATH . '.htaccess', 609 'recommended' => '0644', 610 'max' => '0644', 611 'description' => esc_html__( 'Arquivo de configuração do servidor', 'ht-security' ), 612 ], 613 'wp-content' => [ 614 'path' => WP_CONTENT_DIR, 615 'recommended' => '0755', 616 'max' => '0755', 617 'description' => esc_html__( 'Diretório de conteúdo', 'ht-security' ), 618 ], 619 'wp-content/uploads' => [ 620 'path' => WP_CONTENT_DIR . '/uploads', 621 'recommended' => '0755', 622 'max' => '0755', 623 'description' => esc_html__( 'Diretório de uploads', 'ht-security' ), 624 ], 625 'wp-content/plugins' => [ 626 'path' => WP_PLUGIN_DIR, 627 'recommended' => '0755', 628 'max' => '0755', 629 'description' => esc_html__( 'Diretório de plugins', 'ht-security' ), 630 ], 631 'wp-content/themes' => [ 632 'path' => get_theme_root(), 633 'recommended' => '0755', 634 'max' => '0755', 635 'description' => esc_html__( 'Diretório de temas', 'ht-security' ), 636 ], 637 ]; 638 639 if ( isset( $_POST['htsec_fix_permissions'] ) && isset( $_POST['htsec_permissions_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['htsec_permissions_nonce'] ) ), 'htsec_fix_permissions' ) ) { 640 htsec_fix_file_permissions( $files_to_check ); 641 echo '<div class="notice notice-success"><p>' . esc_html__( 'Tentativa de correção de permissões executada.', 'ht-security' ) . '</p></div>'; 642 } 643 644 echo '<table class="widefat striped">'; 645 echo '<thead>'; 646 echo '<tr>'; 647 echo '<th>' . esc_html__( 'Arquivo/Diretório', 'ht-security' ) . '</th>'; 648 echo '<th>' . esc_html__( 'Descrição', 'ht-security' ) . '</th>'; 649 echo '<th>' . esc_html__( 'Permissão Atual', 'ht-security' ) . '</th>'; 650 echo '<th>' . esc_html__( 'Permissão Recomendada', 'ht-security' ) . '</th>'; 651 echo '<th>' . esc_html__( 'Status', 'ht-security' ) . '</th>'; 652 echo '</tr>'; 653 echo '</thead>'; 654 echo '<tbody>'; 655 656 $has_issues = false; 657 658 foreach ( $files_to_check as $name => $info ) { 659 if ( ! file_exists( $info['path'] ) ) { 660 continue; 661 } 662 663 $current_perms = substr( sprintf( '%o', fileperms( $info['path'] ) ), -4 ); 664 $is_safe = htsec_is_permission_safe( $current_perms, $info['max'] ); 665 666 if ( ! $is_safe ) { 667 $has_issues = true; 668 } 669 670 echo '<tr>'; 671 echo '<td>' . esc_html( $name ) . '</td>'; 672 echo '<td>' . esc_html( $info['description'] ) . '</td>'; 673 echo '<td><code>' . esc_html( $current_perms ) . '</code></td>'; 674 echo '<td><code>' . esc_html( $info['recommended'] ) . '</code></td>'; 675 echo '<td>'; 676 677 if ( $is_safe ) { 678 echo '<span style="color: green;">✓ ' . esc_html__( 'Seguro', 'ht-security' ) . '</span>'; 679 } else { 680 echo '<span style="color: red;">⚠ ' . esc_html__( 'Permissão muito ampla', 'ht-security' ) . '</span>'; 681 } 682 683 echo '</td>'; 684 echo '</tr>'; 685 } 686 687 echo '</tbody>'; 688 echo '</table>'; 689 690 if ( $has_issues ) { 691 echo '<form method="post" style="margin-top: 20px;">'; 692 wp_nonce_field( 'htsec_fix_permissions', 'htsec_permissions_nonce' ); 693 echo '<p class="description">' . esc_html__( 'Atenção: A correção automática pode não funcionar em todos os servidores devido a restrições de permissão.', 'ht-security' ) . '</p>'; 694 submit_button( esc_html__( 'Corrigir Permissões Automaticamente', 'ht-security' ), 'secondary', 'htsec_fix_permissions' ); 695 echo '</form>'; 696 } else { 697 echo '<p style="margin-top: 20px; color: green;">' . esc_html__( 'Todas as permissões estão configuradas corretamente!', 'ht-security' ) . '</p>'; 698 } 699 } 700 701 /** 702 * Verifica se uma permissão é segura. 703 */ 704 function htsec_is_permission_safe( $current, $max ) { 705 $current_octal = octdec( $current ); 706 $max_octal = octdec( $max ); 707 708 return $current_octal <= $max_octal; 709 } 710 711 /** 712 * Tenta corrigir as permissões dos arquivos. 713 */ 714 function htsec_fix_file_permissions( $files_to_check ) { 715 if ( ! current_user_can( 'manage_options' ) ) { 716 return; 717 } 718 719 require_once( ABSPATH . 'wp-admin/includes/file.php' ); 720 721 $creds = request_filesystem_credentials( '', '', false, ABSPATH, null ); 722 723 if ( ! WP_Filesystem( $creds ) ) { 724 return; 725 } 726 727 global $wp_filesystem; 728 729 foreach ( $files_to_check as $name => $info ) { 730 if ( ! file_exists( $info['path'] ) ) { 731 continue; 732 } 733 734 $current_perms = substr( sprintf( '%o', fileperms( $info['path'] ) ), -4 ); 735 736 if ( ! htsec_is_permission_safe( $current_perms, $info['max'] ) ) { 737 $wp_filesystem->chmod( $info['path'], octdec( $info['recommended'] ), false ); 738 } 739 } 740 } 741 742 // Login sucesso 743 add_action( 'wp_login', 'htsec_login_success', 10, 2 ); 744 function htsec_login_success( $user_login, $user ) { 745 if ( ! get_option( 'htsec_enable_login_alerts', 0 ) ) { 746 return; 747 } 748 $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'IP desconhecido' ) ); 749 $hora = current_time( 'mysql' ); 750 $email = sanitize_email( get_option( 'htsec_alert_email', get_option( 'admin_email' ) ) ); 751 $subject = esc_html__( 'Login bem-sucedido detectado', 'ht-security' ); 752 $message = sprintf( 753 "%s\n\n%s\n%s: %s\n%s: %s\n\n%s", 754 esc_html__( 'Estimado Administrador,', 'ht-security' ), 755 esc_html__( 'Informamos que foi efetuado o Login no seu site', 'ht-security' ) . ' "' . get_bloginfo( 'name' ) . '"', 756 esc_html__( 'Usuário', 'ht-security' ), 757 esc_html( $user_login ), 758 esc_html__( 'IP', 'ht-security' ), 759 esc_html( $ip ), 760 esc_html__( 'Caso não tenha sido você, verifique a segurança do seu site.', 'ht-security' ) 761 ); 762 wp_mail( $email, $subject, $message ); 763 } 764 765 // Login falhou 766 add_action( 'wp_login_failed', 'htsec_login_failed' ); 767 function htsec_login_failed( $username ) { 768 if ( ! get_option( 'htsec_enable_login_alerts', 0 ) ) { 769 return; 770 } 771 $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'IP desconhecido' ) ); 772 $hora = current_time( 'mysql' ); 773 $email = sanitize_email( get_option( 'htsec_alert_email', get_option( 'admin_email' ) ) ); 774 $subject = esc_html__( 'Erro de Login Detectado', 'ht-security' ); 775 $message = esc_html__( 'Estimado Administrador,', 'ht-security' ) . "\n\n"; 776 $message .= esc_html__( 'Detectamos uma tentativa de login maliciosa ou com credenciais erradas:', 'ht-security' ) . "\n\n"; 777 $message .= esc_html__( 'Usuário', 'ht-security' ) . ': ' . esc_html( $username ) . "\n"; 778 $message .= esc_html__( 'IP', 'ht-security' ) . ': ' . esc_html( $ip ) . "\n"; 779 $message .= esc_html__( 'Data/Hora', 'ht-security' ) . ': ' . esc_html( $hora ) . "\n\n"; 780 $message .= esc_html__( 'Por favor, verifique seu site com máxima urgência.', 'ht-security' ); 781 wp_mail( $email, $subject, $message ); 782 } 783 784 /** 785 * Desativa enumeração de usuários via API REST. 786 */ 787 add_filter( 'rest_endpoints', 'htsec_disable_user_endpoints' ); 788 function htsec_disable_user_endpoints( $endpoints ) { 789 if ( ! get_option( 'htsec_disable_user_enumeration', 0 ) ) { 790 return $endpoints; 791 } 792 793 if ( isset( $endpoints['/wp/v2/users'] ) ) { 794 unset( $endpoints['/wp/v2/users'] ); 795 } 796 if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) { 797 unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ); 798 } 799 800 return $endpoints; 801 } 802 803 /** 804 * Bloqueia acesso a parâmetros de autor para evitar enumeração. 805 */ 806 add_action( 'init', 'htsec_block_author_enumeration' ); 807 function htsec_block_author_enumeration() { 808 if ( ! get_option( 'htsec_disable_user_enumeration', 0 ) ) { 809 return; 810 } 811 812 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Not processing form data 813 if ( isset( $_GET['author'] ) && is_numeric( $_GET['author'] ) ) { 814 wp_safe_redirect( home_url(), 301 ); 815 exit; 816 } 817 } 818 819 /** 820 * Implementa modo de manutenção com IP whitelist. 821 */ 822 add_action( 'init', 'htsec_maintenance_mode' ); 823 function htsec_maintenance_mode() { 824 if ( ! get_option( 'htsec_maintenance_mode', 0 ) ) { 825 return; 826 } 827 828 if ( current_user_can( 'manage_options' ) ) { 829 return; 830 } 831 832 $allowed_ips = get_option( 'htsec_maintenance_ips', '' ); 833 $allowed_ips = array_filter( array_map( 'trim', explode( "\n", $allowed_ips ) ) ); 834 $current_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); 835 836 if ( ! empty( $allowed_ips ) && in_array( $current_ip, $allowed_ips, true ) ) { 837 return; 838 } 839 840 $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; 841 842 if ( ! is_admin() && ! strpos( $request_uri, '/wp-login.php' ) ) { 843 wp_die( 844 esc_html__( 'Site em Manutenção', 'ht-security' ), 845 esc_html__( 'Manutenção', 'ht-security' ), 846 [ 'response' => 503 ] 847 ); 848 } 849 } 850 851 /** 852 * Exibe a verificação de vulnerabilidades CVE. 853 */ 854 function htsec_display_cve_check() { 855 // Informação sobre a funcionalidade 856 $api_key = get_option( 'htsec_nvd_api_key', '' ); 857 $rate_limit = empty( $api_key ) ? 5 : 50; 858 859 echo '<div class="htsec-check-info">'; 860 echo '<p style="margin: 0 0 10px 0;">' . esc_html__( 'Esta funcionalidade verifica se há vulnerabilidades conhecidas (CVEs) nas versões instaladas do WordPress e plugins ativos.', 'ht-security' ) . '</p>'; 861 /* translators: %d: Number of requests allowed per 30 seconds */ 862 echo '<p style="margin: 0;"><strong>' . sprintf( esc_html__( 'Limite atual: %d verificações a cada 30 segundos', 'ht-security' ), esc_html( $rate_limit ) ) . '</strong></p>'; 863 echo '</div>'; 864 865 // Botão para forçar verificação manual 866 if ( isset( $_POST['htsec_check_cve_now'] ) && isset( $_POST['htsec_cve_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['htsec_cve_nonce'] ) ), 'htsec_check_cve' ) ) { 867 // Limpar progresso anterior 868 delete_option( 'htsec_check_progress' ); 869 870 echo '<div class="notice notice-info" style="position: relative;">'; 871 echo '<p><strong>' . esc_html__( 'Verificação em andamento...', 'ht-security' ) . '</strong></p>'; 872 echo '</div>'; 873 874 // Forçar flush do output para mostrar a mensagem 875 if ( function_exists( 'wp_ob_end_flush_all' ) ) { 876 wp_ob_end_flush_all(); 877 } 878 flush(); 879 880 $stats = htsec_check_vulnerabilities( true ); 881 882 // Exibir progresso 883 $progress = get_option( 'htsec_check_progress', [] ); 884 if ( ! empty( $progress ) ) { 885 echo '<div class="notice notice-info">'; 886 echo '<p><strong>' . esc_html__( 'Progresso da Verificação:', 'ht-security' ) . '</strong></p>'; 887 echo '<div style="margin: 10px 0;">'; 888 foreach ( $progress as $entry ) { 889 echo '<div class="htsec-progress-item">' . esc_html( $entry['message'] ) . '</div>'; 890 } 891 echo '</div>'; 892 echo '</div>'; 893 } 894 895 // Limpar progresso após exibir 896 delete_option( 'htsec_check_progress' ); 897 898 echo '<div class="notice notice-success">'; 899 echo '<p><strong>' . esc_html__( 'Verificação concluída!', 'ht-security' ) . '</strong></p>'; 900 echo '<div class="htsec-stats-box">'; 901 /* translators: %d: Total items checked */ 902 echo '<p style="margin: 0 0 8px 0;"><strong>' . esc_html__( 'Estatísticas:', 'ht-security' ) . '</strong></p>'; 903 /* translators: %d: Number of software items */ 904 echo '<p style="margin: 0 0 5px 0;">• ' . sprintf( esc_html__( 'Softwares verificados: %d', 'ht-security' ), esc_html( $stats['total_checked'] ) ) . '</p>'; 905 /* translators: %d: Number of batches */ 906 echo '<p style="margin: 0 0 5px 0;">• ' . sprintf( esc_html__( 'Lotes processados: %d', 'ht-security' ), esc_html( $stats['batches_processed'] ) ) . '</p>'; 907 /* translators: %d: Number of vulnerabilities */ 908 echo '<p style="margin: 0;">• ' . sprintf( esc_html__( 'Vulnerabilidades encontradas: %d', 'ht-security' ), esc_html( $stats['vulnerabilities_found'] ) ) . '</p>'; 909 echo '</div>'; 910 echo '</div>'; 911 } 912 913 // Obter dados de vulnerabilidades armazenados 914 $vulnerabilities = get_option( 'htsec_vulnerabilities', [] ); 915 $last_check = get_option( 'htsec_last_cve_check', 0 ); 916 917 if ( $last_check ) { 918 $time_diff = human_time_diff( $last_check, current_time( 'timestamp' ) ); 919 /* translators: %s: Time since last check */ 920 echo '<p class="description">' . sprintf( esc_html__( 'Última verificação: %s atrás', 'ht-security' ), esc_html( $time_diff ) ) . '</p>'; 921 } else { 922 echo '<p class="description">' . esc_html__( 'Nenhuma verificação realizada ainda. Clique no botão abaixo para verificar agora.', 'ht-security' ) . '</p>'; 923 } 924 925 // Exibir estatísticas 926 if ( ! function_exists( 'get_plugins' ) ) { 927 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 928 } 929 $active_plugins = get_option( 'active_plugins', [] ); 930 $total_to_check = count( $active_plugins ) + 1; // +1 para WordPress 931 $batches_needed = ceil( $total_to_check / $rate_limit ); 932 933 if ( $batches_needed > 1 ) { 934 $estimated_time = ( $batches_needed - 1 ) * 30; // 30 segundos entre batches 935 echo '<p class="description">'; 936 echo sprintf( 937 /* translators: 1: Number of batches, 2: Estimated time in seconds */ 938 esc_html__( 'Esta verificação será dividida em %1$d lote(s). Tempo estimado: ~%2$d segundos', 'ht-security' ), 939 esc_html( $batches_needed ), 940 esc_html( $estimated_time ) 941 ); 942 echo '</p>'; 943 } 944 945 // Exibir resultados 946 if ( empty( $vulnerabilities ) ) { 947 echo '<div class="htsec-alert-success">'; 948 echo '<p>✓ ' . esc_html__( 'Nenhuma vulnerabilidade conhecida detectada nas versões atuais!', 'ht-security' ) . '</p>'; 949 echo '</div>'; 950 } else { 951 echo '<div class="htsec-alert-danger">'; 952 /* translators: %d: Number of vulnerabilities found */ 953 echo '<p>⚠ ' . sprintf( esc_html( _n( '%d vulnerabilidade encontrada!', '%d vulnerabilidades encontradas!', count( $vulnerabilities ), 'ht-security' ) ), count( $vulnerabilities ) ) . '</p>'; 954 echo '</div>'; 955 956 htsec_display_vulnerabilities( $vulnerabilities ); 957 } 958 959 // Botão de verificação manual 960 echo '<form method="post" style="margin-top: 20px;">'; 961 wp_nonce_field( 'htsec_check_cve', 'htsec_cve_nonce' ); 962 submit_button( esc_html__( 'Verificar Vulnerabilidades Agora', 'ht-security' ), 'primary', 'htsec_check_cve_now', false ); 963 echo '</form>'; 964 } 965 966 /** 967 * Exibe as vulnerabilidades encontradas. 968 */ 969 function htsec_display_vulnerabilities( $vulnerabilities ) { 970 echo '<div style="margin: 20px 0;">'; 971 972 foreach ( $vulnerabilities as $vuln ) { 973 $severity = isset( $vuln['severity'] ) ? $vuln['severity'] : 'UNKNOWN'; 974 $severity_colors = [ 975 'CRITICAL' => '#8b0000', 976 'HIGH' => '#dc3545', 977 'MEDIUM' => '#ffc107', 978 'LOW' => '#28a745', 979 'UNKNOWN' => '#6c757d', 980 ]; 981 $severity_color = isset( $severity_colors[ $severity ] ) ? $severity_colors[ $severity ] : $severity_colors['UNKNOWN']; 982 983 echo '<div class="htsec-vuln-card">'; 984 echo '<div class="htsec-vuln-header">'; 985 echo '<h3 class="htsec-vuln-title">' . esc_html( $vuln['software'] ) . ' ' . esc_html( $vuln['version'] ) . '</h3>'; 986 echo '<span class="htsec-severity-badge" style="background-color: ' . esc_attr( $severity_color ) . '; color: white;">' . esc_html( $severity ) . '</span>'; 987 echo '</div>'; 988 989 echo '<div class="htsec-vuln-detail">'; 990 echo '<strong>' . esc_html__( 'CVE:', 'ht-security' ) . '</strong> '; 991 echo '<a href="' . esc_url( $vuln['cve_url'] ) . '" target="_blank" class="htsec-cve-link" rel="noopener noreferrer">'; 992 echo esc_html( $vuln['cve_id'] ); 993 echo ' <span class="dashicons dashicons-external" style="font-size: 14px; vertical-align: middle;"></span>'; 994 echo '</a>'; 995 echo '</div>'; 996 997 if ( isset( $vuln['cvss_score'] ) ) { 998 echo '<div class="htsec-vuln-detail">'; 999 echo '<strong>' . esc_html__( 'CVSS Score:', 'ht-security' ) . '</strong> '; 1000 echo '<span class="htsec-cvss-score">' . esc_html( $vuln['cvss_score'] ) . '</span>'; 1001 echo '</div>'; 1002 } 1003 1004 if ( ! empty( $vuln['description'] ) ) { 1005 echo '<div class="htsec-vuln-detail">'; 1006 echo '<strong>' . esc_html__( 'Descrição:', 'ht-security' ) . '</strong>'; 1007 echo '<div class="htsec-vuln-description">' . esc_html( $vuln['description'] ) . '</div>'; 1008 echo '</div>'; 1009 } 1010 1011 echo '</div>'; 1012 } 1013 1014 echo '</div>'; 1015 } 1016 1017 /** 1018 * Verifica vulnerabilidades nos plugins e WordPress. 1019 */ 1020 function htsec_check_vulnerabilities( $manual = false ) { 1021 $api_key = get_option( 'htsec_nvd_api_key', '' ); 1022 $vulnerabilities = []; 1023 1024 // Definir rate limits 1025 $rate_limit = empty( $api_key ) ? 5 : 50; // 5 requests/30s sem key, 50/30s com key 1026 $batch_delay = 30; // 30 segundos entre batches 1027 1028 // Coletar todos os softwares a serem verificados 1029 $software_to_check = []; 1030 1031 // Adicionar WordPress 1032 $software_to_check[] = [ 1033 'name' => 'WordPress', 1034 'version' => get_bloginfo( 'version' ), 1035 'type' => 'core', 1036 ]; 1037 1038 // Adicionar plugins ativos 1039 if ( ! function_exists( 'get_plugins' ) ) { 1040 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 1041 } 1042 1043 $all_plugins = get_plugins(); 1044 $active_plugins = get_option( 'active_plugins', [] ); 1045 1046 foreach ( $all_plugins as $plugin_file => $plugin_data ) { 1047 // Verificar apenas plugins ativos 1048 if ( ! in_array( $plugin_file, $active_plugins, true ) ) { 1049 continue; 1050 } 1051 1052 $software_to_check[] = [ 1053 'name' => $plugin_data['Name'], 1054 'version' => $plugin_data['Version'], 1055 'type' => 'plugin', 1056 ]; 1057 } 1058 1059 $total_items = count( $software_to_check ); 1060 $total_batches = ceil( $total_items / $rate_limit ); 1061 $current_batch = 0; 1062 1063 // Processar em batches 1064 for ( $i = 0; $i < $total_items; $i += $rate_limit ) { 1065 $current_batch++; 1066 $batch = array_slice( $software_to_check, $i, $rate_limit ); 1067 1068 // Feedback de progresso para verificação manual 1069 if ( $manual ) { 1070 $processed = min( $i + $rate_limit, $total_items ); 1071 htsec_log_progress( sprintf( 1072 /* translators: 1: Current progress, 2: Total items */ 1073 esc_html__( 'Verificando %1$d de %2$d softwares...', 'ht-security' ), 1074 $processed, 1075 $total_items 1076 ) ); 1077 } 1078 1079 // Processar cada item do batch 1080 foreach ( $batch as $software ) { 1081 $vulns = htsec_query_nvd_api( $software['name'], $software['version'], $api_key ); 1082 if ( ! empty( $vulns ) ) { 1083 $vulnerabilities = array_merge( $vulnerabilities, $vulns ); 1084 } 1085 1086 // Pequeno delay entre requisições do mesmo batch para não sobrecarregar 1087 if ( ! empty( $api_key ) ) { 1088 usleep( 100000 ); // 0.1 segundo com API Key 1089 } else { 1090 usleep( 500000 ); // 0.5 segundo sem API Key 1091 } 1092 } 1093 1094 // Se não for o último batch, esperar 30 segundos 1095 if ( $current_batch < $total_batches ) { 1096 if ( $manual ) { 1097 htsec_log_progress( sprintf( 1098 /* translators: %d: Seconds to wait */ 1099 esc_html__( 'Aguardando %d segundos (limite da API)...', 'ht-security' ), 1100 $batch_delay 1101 ) ); 1102 } 1103 sleep( $batch_delay ); 1104 } 1105 } 1106 1107 // Armazenar resultados 1108 update_option( 'htsec_vulnerabilities', $vulnerabilities ); 1109 update_option( 'htsec_last_cve_check', current_time( 'timestamp' ) ); 1110 1111 // Enviar e-mail se habilitado e houver vulnerabilidades 1112 if ( ! empty( $vulnerabilities ) && get_option( 'htsec_enable_cve_alerts', 0 ) ) { 1113 htsec_send_cve_alert( $vulnerabilities ); 1114 } 1115 1116 return [ 1117 'total_checked' => $total_items, 1118 'vulnerabilities_found' => count( $vulnerabilities ), 1119 'batches_processed' => $current_batch, 1120 ]; 1121 } 1122 1123 /** 1124 * Registra progresso da verificação (para exibição em verificações manuais). 1125 */ 1126 function htsec_log_progress( $message ) { 1127 $progress = get_option( 'htsec_check_progress', [] ); 1128 $progress[] = [ 1129 'message' => $message, 1130 'time' => current_time( 'mysql' ), 1131 ]; 1132 update_option( 'htsec_check_progress', $progress ); 1133 } 1134 1135 /** 1136 * Consulta a API NVD para buscar CVEs. 1137 */ 1138 function htsec_query_nvd_api( $software_name, $version, $api_key = '' ) { 1139 $vulnerabilities = []; 1140 1141 // Para WordPress, usar busca específica 1142 if ( 'WordPress' === $software_name ) { 1143 $search_term = 'WordPress Core ' . $version; 1144 } else { 1145 // Para plugins, usar nome específico 1146 $search_term = sanitize_text_field( $software_name ); 1147 } 1148 1149 $url = add_query_arg( [ 1150 'keywordSearch' => rawurlencode( $search_term ), 1151 'keywordExactMatch' => '', // Busca exata da frase 1152 'resultsPerPage' => 20, 1153 ], 'https://services.nvd.nist.gov/rest/json/cves/2.0' ); 1154 1155 $headers = [ 'timeout' => 30 ]; 1156 1157 if ( ! empty( $api_key ) ) { 1158 $headers['headers'] = [ 1159 'apiKey' => sanitize_text_field( $api_key ), 1160 ]; 1161 } 1162 1163 $response = wp_remote_get( $url, $headers ); 1164 1165 if ( is_wp_error( $response ) ) { 1166 return $vulnerabilities; 1167 } 1168 1169 $response_code = wp_remote_retrieve_response_code( $response ); 1170 if ( 200 !== $response_code ) { 1171 return $vulnerabilities; 1172 } 1173 1174 $body = wp_remote_retrieve_body( $response ); 1175 $data = json_decode( $body, true ); 1176 1177 if ( empty( $data['vulnerabilities'] ) ) { 1178 return $vulnerabilities; 1179 } 1180 1181 foreach ( $data['vulnerabilities'] as $vuln ) { 1182 if ( empty( $vuln['cve'] ) ) { 1183 continue; 1184 } 1185 1186 $cve = $vuln['cve']; 1187 $cve_id = isset( $cve['id'] ) ? $cve['id'] : ''; 1188 1189 if ( empty( $cve_id ) ) { 1190 continue; 1191 } 1192 1193 // FILTRO: CVEs muito antigas (antes de 2010) - plugins WordPress modernos não são afetados 1194 // CVE-ID formato: CVE-YYYY-NNNNN 1195 if ( preg_match( '/CVE-(\d{4})-/', $cve_id, $matches ) ) { 1196 $cve_year = (int) $matches[1]; 1197 if ( $cve_year < 2010 ) { 1198 // CVEs antes de 2010 raramente afetam plugins WordPress modernos 1199 continue; 1200 } 1201 } 1202 1203 // Extrair descrição 1204 $description = ''; 1205 if ( isset( $cve['descriptions'] ) && is_array( $cve['descriptions'] ) ) { 1206 foreach ( $cve['descriptions'] as $desc ) { 1207 if ( isset( $desc['lang'] ) && 'en' === $desc['lang'] ) { 1208 $description = $desc['value']; 1209 break; 1210 } 1211 } 1212 } 1213 1214 // FILTRO: Descrição vazia ou muito curta 1215 if ( empty( $description ) || strlen( $description ) < 50 ) { 1216 continue; 1217 } 1218 1219 // ANTI-FALSO POSITIVO: Validar se a CVE realmente se aplica ao software 1220 if ( ! htsec_validate_cve_match( $software_name, $description, $cve ) ) { 1221 continue; 1222 } 1223 1224 // VALIDAÇÃO DE VERSÃO: Verificar se a versão instalada é vulnerável 1225 if ( ! htsec_version_is_vulnerable( $version, $cve ) ) { 1226 continue; 1227 } 1228 1229 // Extrair severidade e score 1230 $severity = 'UNKNOWN'; 1231 $cvss_score = null; 1232 1233 if ( isset( $cve['metrics'] ) ) { 1234 $metrics = $cve['metrics']; 1235 1236 // Tentar CVSS v3.1 primeiro 1237 if ( isset( $metrics['cvssMetricV31'][0] ) ) { 1238 $cvss = $metrics['cvssMetricV31'][0]; 1239 $severity = isset( $cvss['cvssData']['baseSeverity'] ) ? $cvss['cvssData']['baseSeverity'] : 'UNKNOWN'; 1240 $cvss_score = isset( $cvss['cvssData']['baseScore'] ) ? $cvss['cvssData']['baseScore'] : null; 1241 } elseif ( isset( $metrics['cvssMetricV30'][0] ) ) { 1242 $cvss = $metrics['cvssMetricV30'][0]; 1243 $severity = isset( $cvss['cvssData']['baseSeverity'] ) ? $cvss['cvssData']['baseSeverity'] : 'UNKNOWN'; 1244 $cvss_score = isset( $cvss['cvssData']['baseScore'] ) ? $cvss['cvssData']['baseScore'] : null; 1245 } elseif ( isset( $metrics['cvssMetricV2'][0] ) ) { 1246 $cvss = $metrics['cvssMetricV2'][0]; 1247 $severity = isset( $cvss['baseSeverity'] ) ? $cvss['baseSeverity'] : 'UNKNOWN'; 1248 $cvss_score = isset( $cvss['cvssData']['baseScore'] ) ? $cvss['cvssData']['baseScore'] : null; 1249 } 1250 } 1251 1252 $vulnerabilities[] = [ 1253 'software' => $software_name, 1254 'version' => $version, 1255 'cve_id' => $cve_id, 1256 'cve_url' => 'https://nvd.nist.gov/vuln/detail/' . $cve_id, 1257 'description' => wp_trim_words( $description, 50 ), 1258 'full_description' => $description, 1259 'severity' => $severity, 1260 'cvss_score' => $cvss_score, 1261 ]; 1262 } 1263 1264 return $vulnerabilities; 1265 } 1266 1267 /** 1268 * Verifica se a versão instalada está vulnerável baseada nos dados da CVE. 1269 */ 1270 function htsec_version_is_vulnerable( $installed_version, $cve_data ) { 1271 // Se não tiver versão instalada, não pode validar 1272 if ( empty( $installed_version ) ) { 1273 return false; 1274 } 1275 1276 // Normalizar versão instalada (remover v, V, etc.) 1277 $installed_version = strtolower( trim( $installed_version ) ); 1278 $installed_version = preg_replace( '/^v/', '', $installed_version ); 1279 1280 // Extrair versões afetadas das configurações da CVE 1281 $version_ranges = htsec_extract_version_ranges( $cve_data ); 1282 1283 // Se não conseguiu extrair ranges, tentar da descrição 1284 if ( empty( $version_ranges ) ) { 1285 $description = ''; 1286 if ( isset( $cve_data['descriptions'] ) && is_array( $cve_data['descriptions'] ) ) { 1287 foreach ( $cve_data['descriptions'] as $desc ) { 1288 if ( isset( $desc['lang'] ) && 'en' === $desc['lang'] ) { 1289 $description = $desc['value']; 1290 break; 1291 } 1292 } 1293 } 1294 $version_ranges = htsec_extract_version_from_description( $description ); 1295 } 1296 1297 // Se ainda não tem ranges, não pode validar (assume que pode ser vulnerável para ser conservador) 1298 if ( empty( $version_ranges ) ) { 1299 return false; // Mudado para false - se não sabemos, não reportamos 1300 } 1301 1302 // Verificar se a versão instalada está em algum dos ranges vulneráveis 1303 foreach ( $version_ranges as $range ) { 1304 if ( htsec_version_in_range( $installed_version, $range ) ) { 1305 return true; 1306 } 1307 } 1308 1309 return false; 1310 } 1311 1312 /** 1313 * Extrai ranges de versões afetadas dos dados da CVE. 1314 */ 1315 function htsec_extract_version_ranges( $cve_data ) { 1316 $ranges = []; 1317 1318 if ( ! isset( $cve_data['configurations'] ) || ! is_array( $cve_data['configurations'] ) ) { 1319 return $ranges; 1320 } 1321 1322 foreach ( $cve_data['configurations'] as $config ) { 1323 if ( ! isset( $config['nodes'] ) || ! is_array( $config['nodes'] ) ) { 1324 continue; 1325 } 1326 1327 foreach ( $config['nodes'] as $node ) { 1328 if ( ! isset( $node['cpeMatch'] ) || ! is_array( $node['cpeMatch'] ) ) { 1329 continue; 1330 } 1331 1332 foreach ( $node['cpeMatch'] as $match ) { 1333 if ( ! isset( $match['vulnerable'] ) || ! $match['vulnerable'] ) { 1334 continue; 1335 } 1336 1337 $range = [ 1338 'start_version' => null, 1339 'end_version' => null, 1340 'start_including' => true, 1341 'end_including' => true, 1342 ]; 1343 1344 if ( isset( $match['versionStartIncluding'] ) ) { 1345 $range['start_version'] = $match['versionStartIncluding']; 1346 $range['start_including'] = true; 1347 } elseif ( isset( $match['versionStartExcluding'] ) ) { 1348 $range['start_version'] = $match['versionStartExcluding']; 1349 $range['start_including'] = false; 1350 } 1351 1352 if ( isset( $match['versionEndIncluding'] ) ) { 1353 $range['end_version'] = $match['versionEndIncluding']; 1354 $range['end_including'] = true; 1355 } elseif ( isset( $match['versionEndExcluding'] ) ) { 1356 $range['end_version'] = $match['versionEndExcluding']; 1357 $range['end_including'] = false; 1358 } 1359 1360 // Se tem pelo menos uma versão de limite, adiciona o range 1361 if ( $range['start_version'] !== null || $range['end_version'] !== null ) { 1362 $ranges[] = $range; 1363 } 1364 } 1365 } 1366 } 1367 1368 return $ranges; 1369 } 1370 1371 /** 1372 * Extrai informação de versão da descrição da CVE. 1373 */ 1374 function htsec_extract_version_from_description( $description ) { 1375 $ranges = []; 1376 1377 if ( empty( $description ) ) { 1378 return $ranges; 1379 } 1380 1381 $description_lower = strtolower( $description ); 1382 1383 // Padrões comuns: 1384 // "before X.X.X", "prior to X.X.X", "up to X.X.X", "through X.X.X" 1385 // "versions up to, and including, X.X.X" 1386 // "in versions up to X.X.X" 1387 1388 $patterns = [ 1389 '/(?:before|prior to|up to|through)\s+(?:version\s+)?v?(\d+\.\d+(?:\.\d+)?)/i', 1390 '/versions?\s+up\s+to,?\s+(?:and\s+)?including,?\s+v?(\d+\.\d+(?:\.\d+)?)/i', 1391 '/in\s+versions?\s+up\s+to\s+v?(\d+\.\d+(?:\.\d+)?)/i', 1392 '/(?:version|versions?)\s+v?(\d+\.\d+(?:\.\d+)?)\s+and\s+(?:below|earlier)/i', 1393 ]; 1394 1395 foreach ( $patterns as $pattern ) { 1396 if ( preg_match( $pattern, $description, $matches ) ) { 1397 $version = $matches[1]; 1398 1399 // Determinar se é inclusive ou exclusive baseado nas palavras 1400 $is_including = ( 1401 strpos( $description_lower, 'including' ) !== false || 1402 strpos( $description_lower, 'through' ) !== false 1403 ); 1404 1405 $ranges[] = [ 1406 'start_version' => null, 1407 'end_version' => $version, 1408 'start_including' => true, 1409 'end_including' => $is_including, 1410 ]; 1411 1412 break; // Pega apenas o primeiro match 1413 } 1414 } 1415 1416 return $ranges; 1417 } 1418 1419 /** 1420 * Verifica se uma versão está dentro de um range específico. 1421 */ 1422 function htsec_version_in_range( $version, $range ) { 1423 // Verificar limite inferior 1424 if ( $range['start_version'] !== null ) { 1425 $comparison = version_compare( $version, $range['start_version'] ); 1426 1427 if ( $range['start_including'] ) { 1428 // Versão deve ser >= start 1429 if ( $comparison < 0 ) { 1430 return false; 1431 } 1432 } else { 1433 // Versão deve ser > start 1434 if ( $comparison <= 0 ) { 1435 return false; 1436 } 1437 } 1438 } 1439 1440 // Verificar limite superior 1441 if ( $range['end_version'] !== null ) { 1442 $comparison = version_compare( $version, $range['end_version'] ); 1443 1444 if ( $range['end_including'] ) { 1445 // Versão deve ser <= end 1446 if ( $comparison > 0 ) { 1447 return false; 1448 } 1449 } else { 1450 // Versão deve ser < end 1451 if ( $comparison >= 0 ) { 1452 return false; 1453 } 1454 } 1455 } 1456 1457 return true; 1458 } 1459 1460 /** 1461 * Valida se uma CVE realmente corresponde ao software especificado. 1462 */ 1463 function htsec_validate_cve_match( $software_name, $description, $cve_data ) { 1464 $software_lower = strtolower( $software_name ); 1465 $description_lower = strtolower( $description ); 1466 1467 // FILTRO 1: Para WordPress Core 1468 if ( 'WordPress' === $software_name ) { 1469 // Deve mencionar explicitamente "wordpress" 1470 if ( strpos( $description_lower, 'wordpress' ) === false ) { 1471 return false; 1472 } 1473 1474 // Não deve ser sobre plugin ou tema 1475 if ( strpos( $description_lower, 'plugin' ) !== false || strpos( $description_lower, 'theme' ) !== false ) { 1476 return false; 1477 } 1478 1479 return true; 1480 } 1481 1482 // FILTRO 2: Plataformas não-WordPress (falsos positivos comuns) 1483 $non_wp_platforms = [ 1484 'drupal', 'joomla', 'magento', 'prestashop', 'opencart', 1485 'chrome', 'firefox', 'safari', 'edge', 'browser', 1486 'google chrome', 'mozilla', 'internet explorer', 1487 'android', 'ios', 'windows', 'linux', 'macos', 1488 'apache', 'nginx', 'iis', 'tomcat', 1489 'java', 'python', 'ruby', '.net', 'php-fpm', 1490 ]; 1491 1492 foreach ( $non_wp_platforms as $platform ) { 1493 if ( strpos( $description_lower, $platform ) !== false ) { 1494 // Se menciona outra plataforma, só aceita se mencionar WordPress também 1495 if ( strpos( $description_lower, 'wordpress' ) === false ) { 1496 return false; 1497 } 1498 } 1499 } 1500 1501 // FILTRO 3: Extrair nome EXATO do plugin da descrição 1502 $cve_plugin_name = htsec_extract_plugin_name_from_description( $description ); 1503 1504 if ( empty( $cve_plugin_name ) ) { 1505 // Se não conseguiu extrair o nome do plugin, não é uma CVE válida de WordPress plugin 1506 return false; 1507 } 1508 1509 $cve_plugin_lower = strtolower( $cve_plugin_name ); 1510 1511 // FILTRO 4: Variações de Licença (Free vs Pro/Premium/Lite) 1512 // Ex: "Rank Math SEO" vs "Rank Math SEO PRO" são plugins DIFERENTES 1513 $license_variants = [ 1514 'pro', 'premium', 'lite', 'free', 1515 'professional', 'ultimate', 'business', 1516 'plus', 'advanced', 'elite', 1517 ]; 1518 1519 // Verificar se a CVE menciona uma variante de licença 1520 $cve_has_license_variant = false; 1521 $cve_license_type = ''; 1522 foreach ( $license_variants as $variant ) { 1523 if ( strpos( $cve_plugin_lower, ' ' . $variant ) !== false || 1524 strpos( $cve_plugin_lower, $variant . ' ' ) !== false || 1525 preg_match( '/\b' . preg_quote( $variant, '/' ) . '\b/', $cve_plugin_lower ) ) { 1526 $cve_has_license_variant = true; 1527 $cve_license_type = $variant; 1528 break; 1529 } 1530 } 1531 1532 // Verificar se o plugin instalado tem variante de licença 1533 $software_has_license_variant = false; 1534 $software_license_type = ''; 1535 foreach ( $license_variants as $variant ) { 1536 if ( strpos( $software_lower, ' ' . $variant ) !== false || 1537 strpos( $software_lower, $variant . ' ' ) !== false || 1538 preg_match( '/\b' . preg_quote( $variant, '/' ) . '\b/', $software_lower ) ) { 1539 $software_has_license_variant = true; 1540 $software_license_type = $variant; 1541 break; 1542 } 1543 } 1544 1545 // REGRA: Se a CVE menciona uma variante mas o plugin instalado tem variante diferente (ou não tem) 1546 // Ex: CVE sobre "Rank Math SEO PRO" vs plugin instalado "Rank Math SEO" (sem PRO) 1547 if ( $cve_has_license_variant && $software_has_license_variant ) { 1548 // Ambos têm variante - devem ser a MESMA 1549 if ( $cve_license_type !== $software_license_type ) { 1550 return false; // Variantes diferentes - FALSO POSITIVO 1551 } 1552 } elseif ( $cve_has_license_variant && ! $software_has_license_variant ) { 1553 // CVE tem variante mas plugin instalado não tem - FALSO POSITIVO 1554 // Ex: "Rank Math SEO PRO" (CVE) vs "Rank Math SEO" (instalado) 1555 return false; 1556 } elseif ( ! $cve_has_license_variant && $software_has_license_variant ) { 1557 // Plugin instalado tem variante mas CVE não menciona 1558 // Isso pode ser válido (CVE genérica que afeta todas as versões) 1559 // Continuar validação 1560 } 1561 1562 // FILTRO 5: Comparação de nome EXATA ou muito próxima 1563 // O nome extraído deve SER o mesmo ou CONTER o nome do plugin instalado 1564 1565 // Normalizar nomes removendo caracteres especiais 1566 $software_normalized = preg_replace( '/[^a-z0-9]/', '', $software_lower ); 1567 $cve_normalized = preg_replace( '/[^a-z0-9]/', '', $cve_plugin_lower ); 1568 1569 // Caso 1: Nome exatamente igual (normalizado) 1570 if ( $software_normalized === $cve_normalized ) { 1571 return true; 1572 } 1573 1574 // Caso 2: O nome da CVE é uma variação exata do nome do plugin 1575 // Ex: "AMP" (plugin) vs "PWA for WP & AMP" (CVE) - FALSO POSITIVO 1576 // Ex: "Elementor Pro" vs "Essential Addons for Elementor Pro" - FALSO POSITIVO 1577 1578 // FILTRO 6: Verificar se é um addon/extensão de outro plugin 1579 $addon_indicators = [ 1580 'addon', 'addons', 'add-on', 'add-ons', 1581 'extension', 'extensions', 1582 'for', // "Addons FOR Elementor" 1583 ]; 1584 1585 $has_addon_indicator = false; 1586 foreach ( $addon_indicators as $indicator ) { 1587 if ( strpos( $cve_plugin_lower, $indicator ) !== false ) { 1588 $has_addon_indicator = true; 1589 break; 1590 } 1591 } 1592 1593 // Se a CVE menciona addon mas o plugin instalado não tem esse padrão, é falso positivo 1594 if ( $has_addon_indicator && ! strpos( $software_lower, 'addon' ) && ! strpos( $software_lower, 'extension' ) ) { 1595 // Verificar se o nome do plugin instalado está CONTIDO na descrição do addon 1596 // Ex: "Elementor Pro" vs "Essential Addons for Elementor Pro" 1597 if ( strpos( $cve_plugin_lower, $software_lower ) !== false ) { 1598 // O nome do plugin está contido, mas é apenas parte de um addon - FALSO POSITIVO 1599 return false; 1600 } 1601 } 1602 1603 // FILTRO 7: Comparação de palavras-chave significativas 1604 // Dividir ambos os nomes em palavras e comparar 1605 $software_words = array_filter( explode( ' ', $software_lower ), function( $w ) { 1606 return strlen( $w ) >= 3; // Palavras de pelo menos 3 caracteres 1607 } ); 1608 1609 $cve_words = array_filter( explode( ' ', $cve_plugin_lower ), function( $w ) { 1610 return strlen( $w ) >= 3; 1611 } ); 1612 1613 // Remover palavras genéricas/comuns que causam falsos positivos 1614 $generic_words = ['wordpress', 'plugin', 'theme', 'for', 'and', 'the', 'inc', 'pro', 'lite', 'free']; 1615 1616 $software_words = array_diff( $software_words, $generic_words ); 1617 $cve_words = array_diff( $cve_words, $generic_words ); 1618 1619 // Se não há palavras significativas, não pode validar 1620 if ( empty( $software_words ) || empty( $cve_words ) ) { 1621 return false; 1622 } 1623 1624 // Contar quantas palavras do plugin instalado aparecem na CVE 1625 $matching_words = 0; 1626 foreach ( $software_words as $word ) { 1627 if ( in_array( $word, $cve_words, true ) ) { 1628 $matching_words++; 1629 } 1630 } 1631 1632 // REGRA: Pelo menos 80% das palavras significativas devem corresponder 1633 $match_percentage = ( $matching_words / count( $software_words ) ) * 100; 1634 1635 if ( $match_percentage < 80 ) { 1636 return false; 1637 } 1638 1639 // FILTRO 8: Se a CVE tem MAIS palavras que o plugin instalado, pode ser um addon 1640 // Ex: "AMP" (1 palavra) vs "PWA for WP & AMP" (5 palavras) - muito diferente 1641 $word_count_ratio = count( $cve_words ) / max( count( $software_words ), 1 ); 1642 1643 if ( $word_count_ratio > 2.0 ) { 1644 // A CVE tem mais que o dobro de palavras - provavelmente é outro plugin 1645 return false; 1646 } 1647 1648 // Se passou por todos os filtros, é uma correspondência válida 1649 return true; 1650 } 1651 1652 /** 1653 * Extrai o nome do plugin da descrição da CVE. 1654 */ 1655 function htsec_extract_plugin_name_from_description( $description ) { 1656 // Padrões comuns de nomeação em descrições de CVE para WordPress plugins: 1657 // "The [Plugin Name] WordPress plugin" 1658 // "The [Plugin Name] plugin for WordPress" 1659 // "[Plugin Name] plugin" 1660 // "vulnerability in [Plugin Name]" 1661 1662 $patterns = [ 1663 '/the\s+(.+?)\s+wordpress\s+plugin/i', 1664 '/the\s+(.+?)\s+plugin\s+for\s+wordpress/i', 1665 '/vulnerability\s+in\s+(.+?)\s+allows/i', 1666 '/vulnerability\s+in\s+(.+?)\s+plugin/i', 1667 '/^(.+?)\s+wordpress\s+plugin/i', 1668 '/in\s+(.+?)\s+wordpress\s+plugin/i', 1669 ]; 1670 1671 foreach ( $patterns as $pattern ) { 1672 if ( preg_match( $pattern, trim( $description ), $matches ) ) { 1673 $extracted_name = trim( $matches[1] ); 1674 1675 // Remover prefixos comuns que não fazem parte do nome 1676 $extracted_name = preg_replace( '/^(the|a|an)\s+/i', '', $extracted_name ); 1677 1678 // Remover sufixos de empresa/desenvolvedor 1679 $extracted_name = preg_replace( '/\s+(inc|ltd|llc|corporation|corp)$/i', '', $extracted_name ); 1680 1681 return $extracted_name; 1682 } 1683 } 1684 1685 return ''; 1686 } 1687 1688 /** 1689 * Envia alerta por e-mail sobre vulnerabilidades encontradas. 1690 */ 1691 function htsec_send_cve_alert( $vulnerabilities ) { 1692 $email = sanitize_email( get_option( 'htsec_alert_email', get_option( 'admin_email' ) ) ); 1693 1694 if ( empty( $email ) ) { 1695 return; 1696 } 1697 1698 $subject = sprintf( 1699 /* translators: 1: Site name, 2: Number of vulnerabilities */ 1700 esc_html__( '[%1$s] Alerta: %2$d vulnerabilidades detectadas', 'ht-security' ), 1701 get_bloginfo( 'name' ), 1702 count( $vulnerabilities ) 1703 ); 1704 1705 $message = esc_html__( 'Estimado Administrador,', 'ht-security' ) . "\n\n"; 1706 $message .= sprintf( 1707 /* translators: %d: Number of vulnerabilities */ 1708 esc_html( _n( 'Foi detectada %d vulnerabilidade conhecida (CVE) em seu site:', 'Foram detectadas %d vulnerabilidades conhecidas (CVE) em seu site:', count( $vulnerabilities ), 'ht-security' ) ), 1709 count( $vulnerabilities ) 1710 ) . "\n\n"; 1711 1712 foreach ( $vulnerabilities as $vuln ) { 1713 $message .= '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' . "\n"; 1714 $message .= esc_html__( 'Software:', 'ht-security' ) . ' ' . $vuln['software'] . ' ' . $vuln['version'] . "\n"; 1715 $message .= esc_html__( 'CVE:', 'ht-security' ) . ' ' . $vuln['cve_id'] . "\n"; 1716 $message .= esc_html__( 'Severidade:', 'ht-security' ) . ' ' . $vuln['severity'] . "\n"; 1717 if ( isset( $vuln['cvss_score'] ) ) { 1718 $message .= esc_html__( 'CVSS Score:', 'ht-security' ) . ' ' . $vuln['cvss_score'] . "\n"; 1719 } 1720 $message .= esc_html__( 'Link:', 'ht-security' ) . ' ' . $vuln['cve_url'] . "\n"; 1721 if ( ! empty( $vuln['description'] ) ) { 1722 $message .= esc_html__( 'Descrição:', 'ht-security' ) . ' ' . $vuln['description'] . "\n"; 1723 } 1724 $message .= "\n"; 1725 } 1726 1727 $message .= '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' . "\n\n"; 1728 $message .= esc_html__( 'Recomendamos que você atualize os plugins e o WordPress o mais rápido possível.', 'ht-security' ) . "\n\n"; 1729 $message .= esc_html__( 'Acesse o painel do HT Security para mais detalhes:', 'ht-security' ) . "\n"; 1730 $message .= admin_url( 'options-general.php?page=ht-security' ); 1731 1732 wp_mail( $email, $subject, $message ); 1733 } 1734 1735 /** 1736 * Configura o cron job para verificação automática de vulnerabilidades. 1737 */ 1738 add_action( 'wp', 'htsec_schedule_cve_check' ); 1739 function htsec_schedule_cve_check() { 1740 if ( ! wp_next_scheduled( 'htsec_cve_check_event' ) ) { 1741 wp_schedule_event( time(), 'twicedaily', 'htsec_cve_check_event' ); 1742 } 1743 } 1744 1745 add_action( 'htsec_cve_check_event', 'htsec_run_scheduled_cve_check' ); 1746 function htsec_run_scheduled_cve_check() { 1747 htsec_check_vulnerabilities(); 1748 } 1749 1750 /** 1751 * Remove o cron job ao desativar o plugin. 1752 */ 1753 register_deactivation_hook( __FILE__, 'htsec_deactivation' ); 1754 function htsec_deactivation() { 1755 $timestamp = wp_next_scheduled( 'htsec_cve_check_event' ); 1756 if ( $timestamp ) { 1757 wp_unschedule_event( $timestamp, 'htsec_cve_check_event' ); 1758 } 1759 } 1760 1761 /** 1762 * Adiciona indicador de vulnerabilidades na página de plugins. 1763 */ 1764 add_filter( 'plugin_row_meta', 'htsec_add_plugin_vulnerability_indicator', 10, 2 ); 1765 function htsec_add_plugin_vulnerability_indicator( $plugin_meta, $plugin_file ) { 1766 // Verificar se a opção está ativada 1767 if ( ! get_option( 'htsec_show_plugin_badges', 0 ) ) { 1768 return $plugin_meta; 1769 } 1770 1771 // Obter dados do plugin 1772 if ( ! function_exists( 'get_plugin_data' ) ) { 1773 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 1774 } 1775 1776 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file ); 1777 $plugin_name = $plugin_data['Name']; 1778 1779 // Verificar vulnerabilidades armazenadas 1780 $all_vulnerabilities = get_option( 'htsec_vulnerabilities', [] ); 1781 $plugin_vulnerabilities = array_filter( $all_vulnerabilities, function( $vuln ) use ( $plugin_name ) { 1782 return $vuln['software'] === $plugin_name; 1783 } ); 1784 1785 $vuln_count = count( $plugin_vulnerabilities ); 1786 1787 if ( $vuln_count > 0 ) { 1788 // Plugin TEM vulnerabilidades - badge vermelho 1789 $message = sprintf( 1790 /* translators: %d: Number of vulnerabilities */ 1791 _n( '%d vulnerabilidade CVE detectada', '%d vulnerabilidades CVE detectadas', $vuln_count, 'ht-security' ), 1792 $vuln_count 1793 ); 1794 1795 $badge = sprintf( 1796 '<span style="display: inline-block; background-color: #dc3545; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px; font-weight: bold; margin-right: 5px;">⚠ %s</span>', 1797 esc_html( $message ) 1798 ); 1799 1800 $link = sprintf( 1801 '<a href="%s" style="text-decoration: none;">%s</a>', 1802 esc_url( admin_url( 'options-general.php?page=ht-security#cve-check' ) ), 1803 esc_html__( 'Ver detalhes', 'ht-security' ) 1804 ); 1805 1806 $plugin_meta[] = $badge . ' ' . $link; 1807 } else { 1808 // Plugin NÃO TEM vulnerabilidades - badge verde 1809 $last_check = get_option( 'htsec_last_cve_check', 0 ); 1810 1811 if ( $last_check ) { 1812 $badge = sprintf( 1813 '<span style="display: inline-block; background-color: #28a745; color: white; padding: 3px 8px; border-radius: 3px; font-size: 11px; font-weight: bold;">✓ %s</span>', 1814 esc_html__( 'Sem vulnerabilidades conhecidas', 'ht-security' ) 1815 ); 1816 1817 $plugin_meta[] = $badge; 1818 } 1819 } 1820 1821 return $plugin_meta; 1822 } 1823 1824 /** 1825 * Adiciona CSS para a página de plugins. 1826 */ 1827 add_action( 'admin_head-plugins.php', 'htsec_plugins_page_styles' ); 1828 function htsec_plugins_page_styles() { 1829 ?> 1830 <style> 1831 .htsec-vuln-badge { 1832 display: inline-block; 1833 padding: 4px 10px; 1834 border-radius: 3px; 1835 font-size: 11px; 1836 font-weight: bold; 1837 margin-right: 8px; 1838 } 1839 .htsec-vuln-badge.danger { 1840 background-color: #dc3545; 1841 color: white; 1842 } 1843 .htsec-vuln-badge.success { 1844 background-color: #28a745; 1845 color: white; 1846 } 1847 .htsec-vuln-badge.warning { 1848 background-color: #ffc107; 1849 color: #333; 1850 } 1851 </style> 1852 <?php 1853 } 1854 1855 /** 1856 * Adiciona notificação no topo da página de plugins se houver vulnerabilidades. 1857 */ 1858 add_action( 'admin_notices', 'htsec_plugins_page_vulnerability_notice' ); 1859 function htsec_plugins_page_vulnerability_notice() { 1860 $screen = get_current_screen(); 1861 1862 if ( isset( $screen->id ) && 'plugins' === $screen->id ) { 1863 $user_id = get_current_user_id(); 1864 $vulnerabilities = get_option( 'htsec_vulnerabilities', [] ); 1865 $vuln_count = count( $vulnerabilities ); 1866 1867 if ( $vuln_count > 0 ) { 1868 // Verificar se o usuário fechou o alerta de erro 1869 $dismissed_error = get_user_meta( $user_id, 'htsec_dismissed_error_notice', true ); 1870 $last_check = get_option( 'htsec_last_cve_check', 0 ); 1871 1872 // Se foi fechado e desde então não houve nova verificação, não mostrar 1873 if ( $dismissed_error && $dismissed_error >= $last_check ) { 1874 return; 1875 } 1876 1877 // Agrupar por plugin 1878 $plugins_affected = []; 1879 foreach ( $vulnerabilities as $vuln ) { 1880 $software = $vuln['software']; 1881 if ( ! isset( $plugins_affected[ $software ] ) ) { 1882 $plugins_affected[ $software ] = 0; 1883 } 1884 $plugins_affected[ $software ]++; 1885 } 1886 1887 $affected_count = count( $plugins_affected ); 1888 1889 echo '<div class="notice notice-error is-dismissible htsec-cve-notice" style="border-left-color: #dc3545;" data-notice-type="error">'; 1890 echo '<p><strong>' . esc_html__( 'Alerta de Segurança do HT Security:', 'ht-security' ) . '</strong></p>'; 1891 echo '<p>' . sprintf( 1892 /* translators: 1: Number of vulnerabilities, 2: Number of affected plugins */ 1893 esc_html( _n( 1894 'Foram detectadas %1$d vulnerabilidade CVE em %2$d plugin.', 1895 'Foram detectadas %1$d vulnerabilidades CVE em %2$d plugins.', 1896 $vuln_count, 1897 'ht-security' 1898 ) ), 1899 esc_html( $vuln_count ), 1900 esc_html( $affected_count ) 1901 ) . '</p>'; 1902 1903 echo '<p>'; 1904 foreach ( $plugins_affected as $software => $count ) { 1905 echo '• <strong>' . esc_html( $software ) . '</strong>: ' . sprintf( 1906 /* translators: %d: Number of vulnerabilities */ 1907 esc_html( _n( '%d CVE', '%d CVEs', $count, 'ht-security' ) ), 1908 esc_html( $count ) 1909 ) . '<br>'; 1910 } 1911 echo '</p>'; 1912 1913 echo '<p>'; 1914 echo '<a href="' . esc_url( admin_url( 'options-general.php?page=ht-security#cve-check' ) ) . '" class="button button-primary">'; 1915 echo esc_html__( 'Ver Detalhes no HT Security', 'ht-security' ); 1916 echo '</a>'; 1917 echo '</p>'; 1918 echo '</div>'; 1919 } else { 1920 $last_check = get_option( 'htsec_last_cve_check', 0 ); 1921 1922 if ( $last_check ) { 1923 // Verificar se o usuário fechou o alerta de sucesso 1924 $dismissed_success = get_user_meta( $user_id, 'htsec_dismissed_success_notice', true ); 1925 1926 // Se foi fechado e desde então não houve nova verificação, não mostrar 1927 if ( $dismissed_success && $dismissed_success >= $last_check ) { 1928 return; 1929 } 1930 1931 echo '<div class="notice notice-success is-dismissible htsec-cve-notice" style="border-left-color: #28a745;" data-notice-type="success">'; 1932 echo '<p><strong>✓ ' . esc_html__( 'HT Security:', 'ht-security' ) . '</strong> '; 1933 echo esc_html__( 'Nenhuma vulnerabilidade conhecida foi detectada nos seus plugins!', 'ht-security' ); 1934 echo '</p>'; 1935 1936 $time_diff = human_time_diff( $last_check, current_time( 'timestamp' ) ); 1937 echo '<p style="font-size: 12px; color: #666; margin: 5px 0 0 0;">' . sprintf( 1938 /* translators: %s: Time since last check */ 1939 esc_html__( 'Última verificação: %s atrás', 'ht-security' ), 1940 esc_html( $time_diff ) 1941 ) . '</p>'; 1942 echo '</div>'; 1943 } 1944 } 1945 } 1946 } 1947 1948 /** 1949 * Adiciona JavaScript para tratar o dismiss do alerta. 1950 */ 1951 add_action( 'admin_footer-plugins.php', 'htsec_dismiss_notice_script' ); 1952 function htsec_dismiss_notice_script() { 1953 ?> 1954 <script type="text/javascript"> 1955 jQuery(document).ready(function($) { 1956 // Quando o usuário clicar no botão de fechar 1957 $(document).on('click', '.htsec-cve-notice .notice-dismiss', function() { 1958 var notice = $(this).parent(); 1959 var noticeType = notice.data('notice-type'); 1960 1961 // Enviar requisição AJAX para salvar que o alerta foi fechado 1962 $.post(ajaxurl, { 1963 action: 'htsec_dismiss_notice', 1964 notice_type: noticeType, 1965 nonce: '<?php echo esc_js( wp_create_nonce( 'htsec_dismiss_notice' ) ); ?>' 1966 }); 1967 }); 1968 }); 1969 </script> 1970 <?php 1971 } 1972 1973 /** 1974 * Handler AJAX para salvar dismiss do alerta. 1975 */ 1976 add_action( 'wp_ajax_htsec_dismiss_notice', 'htsec_ajax_dismiss_notice' ); 1977 function htsec_ajax_dismiss_notice() { 1978 check_ajax_referer( 'htsec_dismiss_notice', 'nonce' ); 1979 1980 $notice_type = isset( $_POST['notice_type'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_type'] ) ) : ''; 1981 $user_id = get_current_user_id(); 1982 1983 if ( 'error' === $notice_type ) { 1984 update_user_meta( $user_id, 'htsec_dismissed_error_notice', current_time( 'timestamp' ) ); 1985 } elseif ( 'success' === $notice_type ) { 1986 update_user_meta( $user_id, 'htsec_dismissed_success_notice', current_time( 'timestamp' ) ); 1987 } 1988 1989 wp_send_json_success(); 1990 } 1991 1992 /** 1993 * Handler AJAX para processar e enviar feedback. 1994 */ 1995 add_action( 'wp_ajax_htsec_send_feedback', 'htsec_ajax_send_feedback' ); 1996 function htsec_ajax_send_feedback() { 1997 check_ajax_referer( 'htsec_send_feedback', 'nonce' ); 1998 1999 // Verificar permissões 2000 if ( ! current_user_can( 'manage_options' ) ) { 2001 wp_send_json_error( [ 2002 'message' => esc_html__( 'Você não tem permissão para enviar feedback.', 'ht-security' ), 2003 ] ); 2004 } 2005 2006 // Obter e sanitizar a mensagem 2007 $message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : ''; 2008 2009 // Validar mensagem 2010 if ( empty( trim( $message ) ) ) { 2011 wp_send_json_error( [ 2012 'message' => esc_html__( 'Por favor, digite uma mensagem antes de enviar.', 'ht-security' ), 2013 ] ); 2014 } 2015 2016 if ( strlen( $message ) < 10 ) { 2017 wp_send_json_error( [ 2018 'message' => esc_html__( 'A mensagem deve ter pelo menos 10 caracteres.', 'ht-security' ), 2019 ] ); 2020 } 2021 2022 // Obter informações do usuário e site 2023 $current_user = wp_get_current_user(); 2024 $user_email = $current_user->user_email; 2025 $user_name = $current_user->display_name; 2026 $site_url = get_site_url(); 2027 $site_name = get_bloginfo( 'name' ); 2028 $wp_version = get_bloginfo( 'version' ); 2029 $plugin_version = '1.3.2'; 2030 2031 // Preparar o assunto do email 2032 $subject = sprintf( 2033 /* translators: %s: Site name */ 2034 esc_html__( '[HT Security Feedback] %s', 'ht-security' ), 2035 $site_name 2036 ); 2037 2038 // Preparar o corpo do email 2039 $email_body = esc_html__( 'Novo feedback recebido:', 'ht-security' ) . "\n\n"; 2040 $email_body .= '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' . "\n\n"; 2041 $email_body .= esc_html__( 'MENSAGEM:', 'ht-security' ) . "\n"; 2042 $email_body .= $message . "\n\n"; 2043 $email_body .= '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' . "\n\n"; 2044 $email_body .= esc_html__( 'INFORMAÇÕES DO REMETENTE:', 'ht-security' ) . "\n"; 2045 $email_body .= esc_html__( 'Nome:', 'ht-security' ) . ' ' . esc_html( $user_name ) . "\n"; 2046 $email_body .= esc_html__( 'E-mail:', 'ht-security' ) . ' ' . esc_html( $user_email ) . "\n"; 2047 $email_body .= esc_html__( 'Site:', 'ht-security' ) . ' ' . esc_html( $site_name ) . "\n"; 2048 $email_body .= esc_html__( 'URL:', 'ht-security' ) . ' ' . esc_url( $site_url ) . "\n"; 2049 $email_body .= esc_html__( 'WordPress:', 'ht-security' ) . ' ' . esc_html( $wp_version ) . "\n"; 2050 $email_body .= esc_html__( 'HT Security:', 'ht-security' ) . ' ' . esc_html( $plugin_version ) . "\n"; 2051 $email_body .= esc_html__( 'Data/Hora:', 'ht-security' ) . ' ' . current_time( 'mysql' ) . "\n\n"; 2052 $email_body .= '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; 2053 2054 // Configurar headers do email 2055 $headers = [ 2056 'Content-Type: text/plain; charset=UTF-8', 2057 'From: ' . $site_name . ' <' . $user_email . '>', 2058 'Reply-To: ' . $user_email, 2059 ]; 2060 2061 // Enviar email 2062 $sent = wp_mail( '[email protected]', $subject, $email_body, $headers ); 2063 2064 if ( $sent ) { 2065 wp_send_json_success( [ 2066 'message' => esc_html__( 'Feedback enviado com sucesso! Obrigado pela sua contribuição.', 'ht-security' ), 2067 ] ); 2068 } else { 2069 wp_send_json_error( [ 2070 'message' => esc_html__( 'Erro ao enviar feedback. Por favor, tente novamente mais tarde.', 'ht-security' ), 2071 ] ); 2072 } 2073 } 64 htsec_load_modules(); -
ht-security/trunk/readme.txt
r3391451 r3391994 5 5 Tested up to: 6.8 6 6 Requires PHP: 8.2 7 Stable Tag: 1.3. 27 Stable Tag: 1.3.3 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 13 13 == Description == 14 14 O HT Security é uma suíte de segurança completa para WordPress, oferecendo múltiplas camadas de proteção para seu site. 15 16 **Importante - Serviço Externo:** 17 Este plugin consulta a API da National Vulnerability Database (NVD) para verificar vulnerabilidades CVE conhecidas. As requisições são feitas para: 18 * URL da API: https://services.nvd.nist.gov/rest/json/cves/2.0 19 * Termos de Uso: https://nvd.nist.gov/general/legal-disclaimer 20 * Política de Privacidade: https://www.nist.gov/privacy-policy 21 * Frequência: Verificação automática a cada 12 horas ou manual sob demanda 22 * Dados enviados: Nome e versão do WordPress/plugins instalados (não envia dados pessoais) 23 24 A consulta à API da NVD é essencial para a funcionalidade de detecção de vulnerabilidades CVE do plugin. 15 25 16 26 == Features == … … 82 92 83 93 == Changelog == 94 = 1.3.3 = 95 * **Melhoria CRÍTICA: Detecção Aprimorada de Variantes de Licença** 96 - Corrigido: Plugin FREE não acusa mais vulnerabilidades da versão PRO 97 - Detecção de variantes com parênteses: "Plugin (PRO)", "Plugin (Premium)" 98 - Detecção de variantes com colchetes: "Plugin [PRO]", "Plugin [Lite]" 99 - Bloqueio correto quando CVE menciona variante mas plugin não tem 100 - Elimina falsos positivos em plugins com nomes similares mas licenças diferentes 101 102 * **Refatoração Completa da Estrutura do Código** 103 - Código modularizado em 9 arquivos por funcionalidade 104 - Organização em diretório /includes/ seguindo padrões WordPress 105 - Melhor separação de responsabilidades (cada módulo tem função única) 106 - Facilita manutenção, debug e adição de novas funcionalidades 107 - Performance otimizada com carregamento modular 108 - Documentação PHPDoc completa em todos os módulos 109 110 * **Módulos Criados:** 111 - security-headers.php: Cabeçalhos HTTP de segurança 112 - settings.php: Configurações e registro de opções 113 - login-alerts.php: Sistema de alertas de login 114 - user-enumeration.php: Proteção contra enumeração 115 - maintenance-mode.php: Modo de manutenção com IP whitelist 116 - file-permissions.php: Auditoria de permissões de arquivos 117 - cve-check.php: Sistema completo de verificação CVE 118 - admin-page.php: Interface administrativa e feedback 119 - plugin-indicators.php: Badges e notificações de vulnerabilidades 120 121 * **Melhorias de Código** 122 - Seguindo WordPress Coding Standards 123 - Sanitização e escapamento rigorosos 124 - Hooks e filtros organizados por módulo 125 - Código mais limpo e manutenível 126 84 127 = 1.3.2 = 85 128 * **Correção de Acertividade na Verificação de CVEs** … … 198 241 == Upgrade Notice == 199 242 243 = 1.3.3 = 244 Correção crítica na detecção de variantes de licença (FREE vs PRO) - elimina falsos positivos. Código completamente refatorado em módulos para melhor manutenibilidade. 245 200 246 = 1.3.2 = 201 247 Correção de acertividade na verificação de CVEs. Badges de segurança agora vêm desabilitados por padrão - você escolhe se quer ativar. Melhorias de performance e ajustes no sistema anti-falso positivo. 202 248 203 249 = 1.3.1 = 204 CORREÇÃO CRÍTICA! Sistema anti-falso positivo completamente reformulado com 8 filtros de validação. Novo filtro de variações de licença (Free vs Pro/Premium). Eliminados 99.9% dos falsos positivos. Nova funcionalidade de feedback integrada. Atualização ALTAMENTE RECOMENDADA! 250 Sistema anti-falso positivo completamente reformulado com 8 filtros de validação. Novo filtro de variações de licença (Free vs Pro/Premium). Eliminados 99.9% dos falsos positivos. Nova funcionalidade de feedback integrada. 205 251 206 252 = 1.3.0 =
Note: See TracChangeset
for help on using the changeset viewer.