clean build for version 1.4.5 with fixes of security funtionalities, logic branches, etc. Already tested and working fine
This commit is contained in:
343
includes/logs/class-Security-Logger.php
Normal file
343
includes/logs/class-Security-Logger.php
Normal file
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
/**
|
||||
* Security Logger for Sheet Data Checker Pro
|
||||
*
|
||||
* Handles logging of security events including rate limit blocks,
|
||||
* CAPTCHA verification failures, and other security-related events
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
|
||||
class CHECKER_SECURITY_LOGGER {
|
||||
|
||||
/**
|
||||
* Log a security event
|
||||
*
|
||||
* @param string $event_type Type of event (rate_limit, recaptcha, turnstile, nonce)
|
||||
* @param int $checker_id Checker post ID
|
||||
* @param array $event_data Event-specific data
|
||||
* @param string $level Log level (info, warning, error)
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function log_event($event_type, $checker_id, $event_data = [], $level = 'info') {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'checker_security_logs';
|
||||
|
||||
// Ensure table exists
|
||||
self::maybe_create_table();
|
||||
|
||||
// Prepare data
|
||||
$log_entry = [
|
||||
'event_type' => $event_type,
|
||||
'checker_id' => $checker_id,
|
||||
'ip_address' => self::mask_ip(self::get_client_ip()),
|
||||
'user_agent' => substr(sanitize_text_field($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 255),
|
||||
'event_data' => json_encode($event_data),
|
||||
'level' => $level,
|
||||
'created_at' => current_time('mysql')
|
||||
];
|
||||
|
||||
// Insert log entry
|
||||
$result = $wpdb->insert($table_name, $log_entry);
|
||||
|
||||
// Also log to WordPress debug log if enabled
|
||||
if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
|
||||
$message = sprintf(
|
||||
'[Sheet Data Checker] Security Event: %s - Checker ID: %d - IP: %s - Data: %s',
|
||||
$event_type,
|
||||
$checker_id,
|
||||
self::mask_ip(self::get_client_ip()),
|
||||
json_encode($event_data)
|
||||
);
|
||||
|
||||
error_log($message);
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log rate limit block
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @param string $ip IP address
|
||||
* @param array $limit_config Rate limit configuration
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function log_rate_limit_block($checker_id, $ip, $limit_config) {
|
||||
return self::log_event(
|
||||
'rate_limit',
|
||||
$checker_id,
|
||||
[
|
||||
'ip' => $ip,
|
||||
'max_attempts' => $limit_config['max_attempts'] ?? 5,
|
||||
'time_window' => $limit_config['time_window'] ?? 15,
|
||||
'block_duration' => $limit_config['block_duration'] ?? 60
|
||||
],
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log CAPTCHA verification failure
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @param string $captcha_type Type of CAPTCHA (recaptcha, turnstile)
|
||||
* @param array $verification_data Verification result data
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function log_captcha_failure($checker_id, $captcha_type, $verification_data) {
|
||||
return self::log_event(
|
||||
$captcha_type,
|
||||
$checker_id,
|
||||
[
|
||||
'success' => false,
|
||||
'score' => $verification_data['score'] ?? null,
|
||||
'error_codes' => $verification_data['error_codes'] ?? []
|
||||
],
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log nonce verification failure
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @param string $nonce_value Nonce value that failed verification
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function log_nonce_failure($checker_id, $nonce_value) {
|
||||
return self::log_event(
|
||||
'nonce',
|
||||
$checker_id,
|
||||
[
|
||||
'nonce' => substr($nonce_value, 0, 10) . '...' // Only log first 10 chars for security
|
||||
],
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent security logs
|
||||
*
|
||||
* @param array $args Query arguments
|
||||
* @return array Log entries
|
||||
*/
|
||||
public static function get_logs($args = []) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'checker_security_logs';
|
||||
|
||||
// Default arguments
|
||||
$defaults = [
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
'event_type' => null,
|
||||
'level' => null,
|
||||
'checker_id' => null,
|
||||
'date_from' => null,
|
||||
'date_to' => null
|
||||
];
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
// Build WHERE clause
|
||||
$where = ['1=1'];
|
||||
$placeholders = [];
|
||||
|
||||
if ($args['event_type']) {
|
||||
$where[] = 'event_type = %s';
|
||||
$placeholders[] = $args['event_type'];
|
||||
}
|
||||
|
||||
if ($args['level']) {
|
||||
$where[] = 'level = %s';
|
||||
$placeholders[] = $args['level'];
|
||||
}
|
||||
|
||||
if ($args['checker_id']) {
|
||||
$where[] = 'checker_id = %d';
|
||||
$placeholders[] = $args['checker_id'];
|
||||
}
|
||||
|
||||
if ($args['date_from']) {
|
||||
$where[] = 'created_at >= %s';
|
||||
$placeholders[] = $args['date_from'];
|
||||
}
|
||||
|
||||
if ($args['date_to']) {
|
||||
$where[] = 'created_at <= %s';
|
||||
$placeholders[] = $args['date_to'];
|
||||
}
|
||||
|
||||
$where_clause = implode(' AND ', $where);
|
||||
|
||||
// Prepare query
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT * FROM $table_name
|
||||
WHERE $where_clause
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %d OFFSET %d",
|
||||
array_merge($placeholders, [$args['limit'], $args['offset']])
|
||||
);
|
||||
|
||||
return $wpdb->get_results($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get security statistics
|
||||
*
|
||||
* @param array $args Filter arguments
|
||||
* @return array Statistics
|
||||
*/
|
||||
public static function get_statistics($args = []) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'checker_security_logs';
|
||||
|
||||
// Default date range (last 30 days)
|
||||
$defaults = [
|
||||
'date_from' => date('Y-m-d', strtotime('-30 days')),
|
||||
'date_to' => date('Y-m-d')
|
||||
];
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
// Prepare query
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT
|
||||
event_type,
|
||||
COUNT(*) as count,
|
||||
level
|
||||
FROM $table_name
|
||||
WHERE created_at BETWEEN %s AND %s
|
||||
GROUP BY event_type, level
|
||||
ORDER BY count DESC",
|
||||
[$args['date_from'], $args['date_to']]
|
||||
);
|
||||
|
||||
$results = $wpdb->get_results($query);
|
||||
|
||||
// Structure the results
|
||||
$stats = [
|
||||
'rate_limit' => ['total' => 0, 'warning' => 0, 'error' => 0],
|
||||
'recaptcha' => ['total' => 0, 'warning' => 0, 'error' => 0],
|
||||
'turnstile' => ['total' => 0, 'warning' => 0, 'error' => 0],
|
||||
'nonce' => ['total' => 0, 'warning' => 0, 'error' => 0],
|
||||
'total' => 0
|
||||
];
|
||||
|
||||
foreach ($results as $row) {
|
||||
if (isset($stats[$row->event_type])) {
|
||||
$stats[$row->event_type]['total'] += $row->count;
|
||||
$stats[$row->event_type][$row->level] = $row->count;
|
||||
$stats['total'] += $row->count;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old log entries
|
||||
*
|
||||
* @param int $days Keep logs for this many days (default: 90)
|
||||
* @return int Number of rows deleted
|
||||
*/
|
||||
public static function cleanup_old_logs($days = 90) {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'checker_security_logs';
|
||||
$cutoff_date = date('Y-m-d H:i:s', strtotime("-$days days"));
|
||||
|
||||
return $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM $table_name WHERE created_at < %s",
|
||||
$cutoff_date
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the security logs table if it doesn't exist
|
||||
*/
|
||||
private static function maybe_create_table() {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = $wpdb->prefix . 'checker_security_logs';
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
event_type varchar(50) NOT NULL,
|
||||
checker_id bigint(20) unsigned NOT NULL,
|
||||
ip_address varchar(45) NOT NULL,
|
||||
user_agent varchar(255) DEFAULT NULL,
|
||||
event_data longtext DEFAULT NULL,
|
||||
level varchar(10) NOT NULL DEFAULT 'info',
|
||||
created_at datetime NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY event_type (event_type),
|
||||
KEY checker_id (checker_id),
|
||||
KEY created_at (created_at),
|
||||
KEY level (level)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||
dbDelta($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client IP address (reuses method from Security class)
|
||||
*
|
||||
* @return string IP address
|
||||
*/
|
||||
private static function get_client_ip() {
|
||||
// Check for Cloudflare first
|
||||
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
||||
return sanitize_text_field($_SERVER['HTTP_CF_CONNECTING_IP']);
|
||||
}
|
||||
|
||||
// Check various proxy headers
|
||||
$ip_headers = [
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
'REMOTE_ADDR'
|
||||
];
|
||||
|
||||
foreach ($ip_headers as $header) {
|
||||
if (!empty($_SERVER[$header])) {
|
||||
$ips = explode(',', $_SERVER[$header]);
|
||||
$ip = trim($ips[0]);
|
||||
|
||||
// Validate IP
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : '0.0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask IP address for privacy
|
||||
*
|
||||
* @param string $ip IP address
|
||||
* @return string Masked IP address
|
||||
*/
|
||||
private static function mask_ip($ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$parts = explode('.', $ip);
|
||||
return $parts[0] . '.' . $parts[1] . '.***.***';
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$parts = explode(':', $ip);
|
||||
return $parts[0] . ':' . $parts[1] . '::***';
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user