feat: add WAF + Attack Intelligence system
- class-itk-waf.php: WordPress WAF scanning GET/POST/COOKIE/UA - class-itk-attacks-api.php: queue/flush/history client for Attack API - config/waf-rules.conf: 9 attack categories, 60+ WP-specific rules - class-itk-database.php: itk_attack_log table, DB version 2 - class-itk-admin.php: WAF tab (toggles, response settings, API card), Attack Logs tab (filterable table), attacks dispatch in AJAX handlers - informatiq-toolkit.php: wire WAF + Attacks API into plugin bootstrap - .gitignore: exclude attack-api/ (separate repo) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ if (!defined('ABSPATH')) exit;
|
||||
*/
|
||||
class ITK_Database {
|
||||
|
||||
const DB_VERSION = 1;
|
||||
const DB_VERSION = 2;
|
||||
const DB_VERSION_OPTION = 'itk_db_version';
|
||||
|
||||
/* ── Table names ──────────────────────────────────────────── */
|
||||
@@ -22,6 +22,11 @@ class ITK_Database {
|
||||
return $wpdb->prefix . 'itk_honeypot_log';
|
||||
}
|
||||
|
||||
public static function attack_table(): string {
|
||||
global $wpdb;
|
||||
return $wpdb->prefix . 'itk_attack_log';
|
||||
}
|
||||
|
||||
/* ── Install / upgrade ────────────────────────────────────── */
|
||||
|
||||
public static function install() {
|
||||
@@ -59,9 +64,28 @@ class ITK_Database {
|
||||
KEY form_type (form_type)
|
||||
) {$charset};";
|
||||
|
||||
$sql_atk = "CREATE TABLE " . self::attack_table() . " (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
logged_at DATETIME NOT NULL,
|
||||
ip_address VARCHAR(45) NOT NULL DEFAULT '',
|
||||
attack_type VARCHAR(50) NOT NULL DEFAULT '',
|
||||
rule_desc VARCHAR(255) NOT NULL DEFAULT '',
|
||||
source VARCHAR(20) NOT NULL DEFAULT '',
|
||||
param VARCHAR(200) NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL,
|
||||
request_uri VARCHAR(1000) NOT NULL DEFAULT '',
|
||||
method VARCHAR(10) NOT NULL DEFAULT '',
|
||||
user_agent TEXT NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY ip_address (ip_address),
|
||||
KEY logged_at (logged_at),
|
||||
KEY attack_type (attack_type)
|
||||
) {$charset};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta($sql_bot);
|
||||
dbDelta($sql_hp);
|
||||
dbDelta($sql_atk);
|
||||
|
||||
update_option(self::DB_VERSION_OPTION, self::DB_VERSION);
|
||||
}
|
||||
@@ -280,4 +304,88 @@ class ITK_Database {
|
||||
$days
|
||||
));
|
||||
}
|
||||
|
||||
/* ── Attack log ───────────────────────────────────────────── */
|
||||
|
||||
public static function log_attack(array $data): void {
|
||||
global $wpdb;
|
||||
$wpdb->insert(
|
||||
self::attack_table(),
|
||||
[
|
||||
'logged_at' => current_time('mysql'),
|
||||
'ip_address' => sanitize_text_field($data['ip'] ?? ''),
|
||||
'attack_type' => sanitize_text_field($data['attack_type'] ?? ''),
|
||||
'rule_desc' => sanitize_text_field($data['rule_desc'] ?? ''),
|
||||
'source' => sanitize_text_field($data['source'] ?? ''),
|
||||
'param' => sanitize_text_field($data['param'] ?? ''),
|
||||
'payload' => sanitize_textarea_field(substr($data['payload'] ?? '', 0, 500)),
|
||||
'request_uri' => sanitize_text_field(substr($data['uri'] ?? '', 0, 1000)),
|
||||
'method' => sanitize_text_field($data['method'] ?? ''),
|
||||
'user_agent' => sanitize_textarea_field($data['ua'] ?? ''),
|
||||
],
|
||||
['%s','%s','%s','%s','%s','%s','%s','%s','%s','%s']
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_attack_rows(array $args = []): array {
|
||||
global $wpdb;
|
||||
$table = self::attack_table();
|
||||
$limit = max(1, (int)($args['per_page'] ?? 25));
|
||||
$offset = max(0, (int)($args['offset'] ?? 0));
|
||||
$where = '1=1';
|
||||
$params = [];
|
||||
|
||||
if (!empty($args['attack_type'])) {
|
||||
$where .= ' AND attack_type = %s';
|
||||
$params[] = $args['attack_type'];
|
||||
}
|
||||
if (!empty($args['ip'])) {
|
||||
$where .= ' AND ip_address = %s';
|
||||
$params[] = $args['ip'];
|
||||
}
|
||||
if (!empty($args['search'])) {
|
||||
$like = '%' . $wpdb->esc_like($args['search']) . '%';
|
||||
$where .= ' AND (ip_address LIKE %s OR param LIKE %s OR payload LIKE %s OR rule_desc LIKE %s)';
|
||||
$params[] = $like; $params[] = $like; $params[] = $like; $params[] = $like;
|
||||
}
|
||||
|
||||
$params[] = $limit;
|
||||
$params[] = $offset;
|
||||
$sql = "SELECT * FROM {$table} WHERE {$where} ORDER BY logged_at DESC LIMIT %d OFFSET %d";
|
||||
return $wpdb->get_results($wpdb->prepare($sql, $params)) ?: [];
|
||||
}
|
||||
|
||||
public static function count_attack_rows(array $args = []): int {
|
||||
global $wpdb;
|
||||
$table = self::attack_table();
|
||||
$where = '1=1';
|
||||
$params = [];
|
||||
|
||||
if (!empty($args['attack_type'])) {
|
||||
$where .= ' AND attack_type = %s';
|
||||
$params[] = $args['attack_type'];
|
||||
}
|
||||
if (!empty($args['ip'])) {
|
||||
$where .= ' AND ip_address = %s';
|
||||
$params[] = $args['ip'];
|
||||
}
|
||||
if (!empty($args['search'])) {
|
||||
$like = '%' . $wpdb->esc_like($args['search']) . '%';
|
||||
$where .= ' AND (ip_address LIKE %s OR param LIKE %s OR payload LIKE %s OR rule_desc LIKE %s)';
|
||||
$params[] = $like; $params[] = $like; $params[] = $like; $params[] = $like;
|
||||
}
|
||||
|
||||
$sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}";
|
||||
return (int)($params ? $wpdb->get_var($wpdb->prepare($sql, $params)) : $wpdb->get_var($sql));
|
||||
}
|
||||
|
||||
public static function get_attack_types(): array {
|
||||
global $wpdb;
|
||||
return $wpdb->get_col("SELECT DISTINCT attack_type FROM " . self::attack_table() . " WHERE attack_type != '' ORDER BY attack_type ASC") ?: [];
|
||||
}
|
||||
|
||||
public static function clear_attack_log(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query("TRUNCATE TABLE " . self::attack_table());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user