370 lines
10 KiB
PHP
370 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Ticket Tailor Security Logger
|
|
*
|
|
* Logs security events and sends alerts
|
|
* SECURITY ENHANCEMENT: Complete audit trail for compliance
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class Ticket_Tailor_Security_Logger {
|
|
|
|
/**
|
|
* Table name
|
|
*/
|
|
private $table;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
global $wpdb;
|
|
$this->table = $wpdb->prefix . 'ticket_tailor_security_log';
|
|
|
|
// Create table if needed
|
|
$this->maybe_create_table();
|
|
}
|
|
|
|
/**
|
|
* Create security log table
|
|
*/
|
|
private function maybe_create_table() {
|
|
global $wpdb;
|
|
|
|
// Check if table already exists
|
|
$table_exists = $wpdb->get_var($wpdb->prepare(
|
|
"SHOW TABLES LIKE %s",
|
|
$this->table
|
|
));
|
|
|
|
if ($table_exists) {
|
|
return;
|
|
}
|
|
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
|
|
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
|
|
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
event_type varchar(50) NOT NULL,
|
|
user_id bigint(20) DEFAULT 0,
|
|
ip_address varchar(45) NOT NULL,
|
|
details longtext,
|
|
timestamp datetime NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY event_type (event_type),
|
|
KEY timestamp (timestamp),
|
|
KEY ip_address (ip_address)
|
|
) {$charset_collate};";
|
|
|
|
// Only require upgrade.php if we actually need to create the table
|
|
if (!function_exists('dbDelta')) {
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
|
}
|
|
dbDelta($sql);
|
|
}
|
|
|
|
/**
|
|
* Log security event
|
|
*/
|
|
public function log_event($event_type, $details = array()) {
|
|
global $wpdb;
|
|
|
|
$wpdb->insert(
|
|
$this->table,
|
|
array(
|
|
'event_type' => sanitize_key($event_type),
|
|
'user_id' => get_current_user_id(),
|
|
'ip_address' => $this->get_ip(),
|
|
'details' => wp_json_encode($details),
|
|
'timestamp' => current_time('mysql', 1),
|
|
),
|
|
array('%s', '%d', '%s', '%s', '%s')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Log failed webhook
|
|
*/
|
|
public function log_failed_webhook($ip, $reason) {
|
|
$this->log_event('webhook_failed', array(
|
|
'ip' => $ip,
|
|
'reason' => $reason,
|
|
));
|
|
|
|
// Check for multiple failures
|
|
$recent_failures = $this->count_recent_events('webhook_failed', 300);
|
|
if ($recent_failures >= 5) {
|
|
$this->send_security_alert('Multiple Failed Webhook Attempts', array(
|
|
'count' => $recent_failures,
|
|
'ip' => $ip,
|
|
'timeframe' => '5 minutes',
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log successful webhook
|
|
*/
|
|
public function log_successful_webhook($ip, $event_type) {
|
|
$this->log_event('webhook_success', array(
|
|
'ip' => $ip,
|
|
'webhook_type' => $event_type,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Log API key change
|
|
*/
|
|
public function log_api_key_change($old_key_hash, $new_key_hash) {
|
|
$this->log_event('api_key_changed', array(
|
|
'old_hash' => substr($old_key_hash, 0, 10),
|
|
'new_hash' => substr($new_key_hash, 0, 10),
|
|
'user' => wp_get_current_user()->user_login,
|
|
));
|
|
|
|
$this->send_security_alert('API Key Changed', array(
|
|
'changed_by' => wp_get_current_user()->user_login,
|
|
'ip' => $this->get_ip(),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Log settings change
|
|
*/
|
|
public function log_settings_change($setting_name, $old_value = null, $new_value = null) {
|
|
$this->log_event('settings_changed', array(
|
|
'setting' => $setting_name,
|
|
'changed_by' => wp_get_current_user()->user_login,
|
|
'has_old' => !is_null($old_value),
|
|
'has_new' => !is_null($new_value),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Log failed login attempt
|
|
*/
|
|
public function log_failed_login($username) {
|
|
$this->log_event('login_failed', array(
|
|
'username' => $username,
|
|
));
|
|
|
|
// Check for brute force
|
|
$recent_failures = $this->count_recent_events('login_failed', 900, $this->get_ip());
|
|
if ($recent_failures >= 5) {
|
|
$this->send_security_alert('Multiple Failed Login Attempts', array(
|
|
'count' => $recent_failures,
|
|
'ip' => $this->get_ip(),
|
|
'username' => $username,
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log rate limit exceeded
|
|
*/
|
|
public function log_rate_limit_exceeded($ip, $context = 'webhook') {
|
|
$this->log_event('rate_limit_exceeded', array(
|
|
'ip' => $ip,
|
|
'context' => $context,
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Log unauthorized access attempt
|
|
*/
|
|
public function log_unauthorized_access($resource) {
|
|
$this->log_event('unauthorized_access', array(
|
|
'resource' => $resource,
|
|
'user_id' => get_current_user_id(),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Count recent events
|
|
*/
|
|
private function count_recent_events($event_type, $seconds, $ip = null) {
|
|
global $wpdb;
|
|
|
|
$time_threshold = gmdate('Y-m-d H:i:s', time() - $seconds);
|
|
|
|
if ($ip) {
|
|
return $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$this->table}
|
|
WHERE event_type = %s AND timestamp > %s AND ip_address = %s",
|
|
$event_type,
|
|
$time_threshold,
|
|
$ip
|
|
));
|
|
} else {
|
|
return $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$this->table}
|
|
WHERE event_type = %s AND timestamp > %s",
|
|
$event_type,
|
|
$time_threshold
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send security alert email
|
|
*/
|
|
public function send_security_alert($event_type, $details) {
|
|
// Check if alerts are enabled
|
|
if (!get_option('ticket_tailor_security_alerts', true)) {
|
|
return;
|
|
}
|
|
|
|
$admin_email = get_option('admin_email');
|
|
$site_name = get_bloginfo('name');
|
|
$site_url = get_bloginfo('url');
|
|
|
|
$subject = sprintf(
|
|
'[%s] Security Alert: %s',
|
|
$site_name,
|
|
$event_type
|
|
);
|
|
|
|
$message = sprintf(
|
|
"Security Event Detected on %s\n\n" .
|
|
"Event Type: %s\n" .
|
|
"Time: %s UTC\n" .
|
|
"IP Address: %s\n" .
|
|
"User: %s\n\n" .
|
|
"Details:\n%s\n\n" .
|
|
"---\n" .
|
|
"This is an automated security alert from Ticket Tailor plugin.\n" .
|
|
"To disable these alerts, go to: %s/wp-admin/admin.php?page=ticket-tailor-settings",
|
|
$site_name,
|
|
$event_type,
|
|
current_time('Y-m-d H:i:s'),
|
|
$this->get_ip(),
|
|
wp_get_current_user()->user_login ?: 'Guest',
|
|
$this->format_details($details),
|
|
$site_url
|
|
);
|
|
|
|
wp_mail($admin_email, $subject, $message);
|
|
}
|
|
|
|
/**
|
|
* Format details for email
|
|
*/
|
|
private function format_details($details) {
|
|
$formatted = '';
|
|
foreach ($details as $key => $value) {
|
|
$formatted .= ' ' . ucfirst(str_replace('_', ' ', $key)) . ': ' . $value . "\n";
|
|
}
|
|
return $formatted;
|
|
}
|
|
|
|
/**
|
|
* Get client IP
|
|
*/
|
|
private function get_ip() {
|
|
$ip_keys = array(
|
|
'HTTP_CF_CONNECTING_IP',
|
|
'HTTP_X_FORWARDED_FOR',
|
|
'HTTP_X_REAL_IP',
|
|
'REMOTE_ADDR',
|
|
);
|
|
|
|
foreach ($ip_keys as $key) {
|
|
if (!empty($_SERVER[$key])) {
|
|
$ip = $_SERVER[$key];
|
|
|
|
if (strpos($ip, ',') !== false) {
|
|
$ip = trim(explode(',', $ip)[0]);
|
|
}
|
|
|
|
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
return $ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
return '0.0.0.0';
|
|
}
|
|
|
|
/**
|
|
* Get recent security events
|
|
*/
|
|
public function get_recent_events($limit = 50, $event_type = null) {
|
|
global $wpdb;
|
|
|
|
if ($event_type) {
|
|
return $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$this->table}
|
|
WHERE event_type = %s
|
|
ORDER BY timestamp DESC
|
|
LIMIT %d",
|
|
$event_type,
|
|
$limit
|
|
));
|
|
} else {
|
|
return $wpdb->get_results($wpdb->prepare(
|
|
"SELECT * FROM {$this->table}
|
|
ORDER BY timestamp DESC
|
|
LIMIT %d",
|
|
$limit
|
|
));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean old logs (older than 90 days)
|
|
*/
|
|
public function cleanup_old_logs() {
|
|
global $wpdb;
|
|
|
|
$threshold = gmdate('Y-m-d H:i:s', time() - (90 * DAY_IN_SECONDS));
|
|
|
|
$wpdb->query($wpdb->prepare(
|
|
"DELETE FROM {$this->table} WHERE timestamp < %s",
|
|
$threshold
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Get security statistics
|
|
*/
|
|
public function get_statistics($days = 30) {
|
|
global $wpdb;
|
|
|
|
$threshold = gmdate('Y-m-d H:i:s', time() - ($days * DAY_IN_SECONDS));
|
|
|
|
$stats = array();
|
|
|
|
// Total events
|
|
$stats['total_events'] = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$this->table} WHERE timestamp > %s",
|
|
$threshold
|
|
));
|
|
|
|
// Events by type
|
|
$stats['by_type'] = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT event_type, COUNT(*) as count
|
|
FROM {$this->table}
|
|
WHERE timestamp > %s
|
|
GROUP BY event_type
|
|
ORDER BY count DESC",
|
|
$threshold
|
|
), ARRAY_A);
|
|
|
|
// Top IPs
|
|
$stats['top_ips'] = $wpdb->get_results($wpdb->prepare(
|
|
"SELECT ip_address, COUNT(*) as count
|
|
FROM {$this->table}
|
|
WHERE timestamp > %s
|
|
GROUP BY ip_address
|
|
ORDER BY count DESC
|
|
LIMIT 10",
|
|
$threshold
|
|
), ARRAY_A);
|
|
|
|
return $stats;
|
|
}
|
|
}
|