Automatic WordPress security: local firewall, malware and file-integrity scanning, vulnerability protection, quarantine, scheduled backups, an optional page cache, and automatic global threat intelligence. See README.md for installation, update, and uninstall instructions.
312 lines
9.7 KiB
PHP
312 lines
9.7 KiB
PHP
<?php
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
class Argus_DB {
|
|
|
|
const SCHEMA_VERSION = '8';
|
|
const SCHEMA_OPTION = 'argus_wpd_schema_version';
|
|
|
|
protected static function drop_index_if_exists( $table, $index ) {
|
|
global $wpdb;
|
|
$exists = $wpdb->get_var(
|
|
$wpdb->prepare( 'SHOW INDEX FROM ' . $table . ' WHERE Key_name = %s', $index ) // phpcs:ignore
|
|
);
|
|
if ( $exists ) {
|
|
$wpdb->query( 'ALTER TABLE ' . $table . ' DROP INDEX ' . $index ); // phpcs:ignore
|
|
}
|
|
}
|
|
|
|
public static function table( $name ) {
|
|
global $wpdb;
|
|
return $wpdb->prefix . 'argus_' . $name;
|
|
}
|
|
|
|
public static function install() {
|
|
global $wpdb;
|
|
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
|
|
|
$charset_collate = $wpdb->get_charset_collate();
|
|
|
|
$events = self::table( 'events' );
|
|
$findings = self::table( 'findings' );
|
|
$bans = self::table( 'bans' );
|
|
$policy_decisions = self::table( 'policy_decisions' );
|
|
$integrity_baseline = self::table( 'integrity_baseline' );
|
|
$login_attempts = self::table( 'login_attempts' );
|
|
$vuln_cache = self::table( 'vuln_cache' );
|
|
$quarantine = self::table( 'quarantine' );
|
|
$cache_stats_daily = self::table( 'cache_stats_daily' );
|
|
$cache_discovered_urls = self::table( 'cache_discovered_urls' );
|
|
$cache_warm_queue = self::table( 'cache_warm_queue' );
|
|
$anis_reputation = self::table( 'anis_reputation' );
|
|
$vuln_status = self::table( 'vuln_status' );
|
|
$scan_history = self::table( 'scan_history' );
|
|
$backups = self::table( 'backups' );
|
|
|
|
$sql = "CREATE TABLE {$events} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
event_type VARCHAR(40) NOT NULL,
|
|
severity VARCHAR(20) NOT NULL DEFAULT 'info',
|
|
ip VARCHAR(45) NULL,
|
|
summary VARCHAR(255) NOT NULL,
|
|
payload LONGTEXT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY event_type (event_type),
|
|
KEY created_at (created_at),
|
|
KEY ip (ip)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$findings} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
category VARCHAR(40) NOT NULL,
|
|
severity VARCHAR(20) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
detail LONGTEXT NULL,
|
|
remediation_hint LONGTEXT NULL,
|
|
evidence LONGTEXT NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
|
first_seen_at DATETIME NOT NULL,
|
|
last_seen_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY category (category),
|
|
KEY status (status),
|
|
KEY severity (severity)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$bans} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
ip VARCHAR(45) NOT NULL,
|
|
source VARCHAR(40) NOT NULL,
|
|
reason VARCHAR(255) NOT NULL,
|
|
evidence LONGTEXT NULL,
|
|
ban_level VARCHAR(20) NOT NULL DEFAULT 'temporary',
|
|
created_at DATETIME NOT NULL,
|
|
expires_at DATETIME NULL,
|
|
recovery_eligible_at DATETIME NULL,
|
|
lifted_at DATETIME NULL,
|
|
PRIMARY KEY (id),
|
|
KEY ip (ip),
|
|
KEY expires_at (expires_at)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
self::drop_index_if_exists( $bans, 'ip_active' );
|
|
|
|
$sql = "CREATE TABLE {$policy_decisions} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
ip VARCHAR(45) NOT NULL,
|
|
trigger_type VARCHAR(40) NOT NULL,
|
|
action VARCHAR(20) NOT NULL,
|
|
rule_name VARCHAR(100) NULL,
|
|
reason VARCHAR(255) NOT NULL,
|
|
observation_only TINYINT(1) NOT NULL DEFAULT 0,
|
|
mode VARCHAR(20) NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY ip (ip),
|
|
KEY created_at (created_at)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$integrity_baseline} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
file_path VARCHAR(500) NOT NULL,
|
|
file_hash CHAR(64) NOT NULL,
|
|
file_size BIGINT UNSIGNED NOT NULL,
|
|
file_mtime DATETIME NOT NULL,
|
|
category VARCHAR(20) NOT NULL,
|
|
first_seen_at DATETIME NOT NULL,
|
|
last_checked_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY file_path (file_path(191)),
|
|
KEY category (category)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$login_attempts} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
ip VARCHAR(45) NOT NULL,
|
|
username VARCHAR(60) NOT NULL,
|
|
success TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY ip_created (ip, created_at),
|
|
KEY username_created (username, created_at)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$vuln_cache} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
component_type VARCHAR(20) NOT NULL,
|
|
slug VARCHAR(191) NOT NULL,
|
|
vulnerable_below VARCHAR(40) NULL,
|
|
fixed_in VARCHAR(40) NULL,
|
|
severity VARCHAR(20) NOT NULL,
|
|
cve VARCHAR(20) NULL,
|
|
description LONGTEXT NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY component_slug (component_type, slug(120))
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$quarantine} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
original_path VARCHAR(500) NOT NULL,
|
|
quarantine_filename VARCHAR(60) NOT NULL,
|
|
original_filename VARCHAR(255) NOT NULL,
|
|
file_size BIGINT UNSIGNED NOT NULL,
|
|
file_hash CHAR(64) NOT NULL,
|
|
original_mtime DATETIME NULL,
|
|
original_perms VARCHAR(4) NULL,
|
|
detection_engine VARCHAR(60) NOT NULL,
|
|
detection_rule VARCHAR(100) NULL,
|
|
detection_type VARCHAR(20) NOT NULL,
|
|
severity VARCHAR(20) NOT NULL,
|
|
confidence_score INT NULL,
|
|
matched_rules LONGTEXT NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'quarantined',
|
|
finding_id BIGINT UNSIGNED NULL,
|
|
quarantined_at DATETIME NOT NULL,
|
|
analysed_at DATETIME NULL,
|
|
resolved_at DATETIME NULL,
|
|
restore_attempts INT NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY quarantine_filename (quarantine_filename),
|
|
KEY status (status),
|
|
KEY finding_id (finding_id)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$cache_stats_daily} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
stat_date DATE NOT NULL,
|
|
status VARCHAR(20) NOT NULL,
|
|
request_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
total_response_time_ms BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
total_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY date_status (stat_date, status)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$cache_discovered_urls} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
url VARCHAR(500) NOT NULL,
|
|
source VARCHAR(40) NOT NULL,
|
|
eligibility VARCHAR(20) NOT NULL DEFAULT 'needs_review',
|
|
eligibility_reason VARCHAR(255) NULL,
|
|
priority_hint DECIMAL(3,2) NULL,
|
|
lastmod DATETIME NULL,
|
|
last_checked_at DATETIME NULL,
|
|
last_warmed_at DATETIME NULL,
|
|
discovered_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY url (url(191)),
|
|
KEY eligibility (eligibility)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$cache_warm_queue} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
url VARCHAR(500) NOT NULL,
|
|
priority INT NOT NULL DEFAULT 100,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'queued',
|
|
attempts INT NOT NULL DEFAULT 0,
|
|
last_attempt_at DATETIME NULL,
|
|
last_result VARCHAR(255) NULL,
|
|
queued_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY status_priority (status, priority),
|
|
KEY url (url(191))
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$anis_reputation} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
ip VARCHAR(45) NOT NULL,
|
|
action VARCHAR(20) NOT NULL,
|
|
confidence INT NOT NULL DEFAULT 0,
|
|
reports INT NOT NULL DEFAULT 0,
|
|
source VARCHAR(60) NULL,
|
|
reason VARCHAR(255) NULL,
|
|
decided_at DATETIME NULL,
|
|
expires_at DATETIME NULL,
|
|
cached_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY ip (ip),
|
|
KEY action (action)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$vuln_status} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
component_type VARCHAR(20) NOT NULL,
|
|
slug VARCHAR(191) NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
installed_version VARCHAR(40) NULL,
|
|
checked_version VARCHAR(40) NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
severity VARCHAR(20) NULL,
|
|
cve VARCHAR(20) NULL,
|
|
fixed_in VARCHAR(40) NULL,
|
|
description LONGTEXT NULL,
|
|
last_checked_at DATETIME NULL,
|
|
updated_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY component (component_type, slug),
|
|
KEY status (status)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$scan_history} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
scan_type VARCHAR(30) NOT NULL,
|
|
started_at DATETIME NOT NULL,
|
|
finished_at DATETIME NOT NULL,
|
|
files_scanned INT UNSIGNED NOT NULL DEFAULT 0,
|
|
findings_count INT UNSIGNED NOT NULL DEFAULT 0,
|
|
result VARCHAR(20) NOT NULL,
|
|
PRIMARY KEY (id),
|
|
KEY started_at (started_at)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
$sql = "CREATE TABLE {$backups} (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
filename VARCHAR(191) NOT NULL,
|
|
type VARCHAR(20) NOT NULL,
|
|
size_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'complete',
|
|
error_message TEXT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY filename (filename),
|
|
KEY created_at (created_at)
|
|
) {$charset_collate};";
|
|
dbDelta( $sql );
|
|
|
|
update_option( self::SCHEMA_OPTION, self::SCHEMA_VERSION, false );
|
|
}
|
|
|
|
public static function maybe_upgrade() {
|
|
if ( get_option( self::SCHEMA_OPTION ) !== self::SCHEMA_VERSION ) {
|
|
self::install();
|
|
}
|
|
}
|
|
|
|
public static function uninstall() {
|
|
global $wpdb;
|
|
foreach ( array( 'events', 'findings', 'bans', 'policy_decisions', 'integrity_baseline', 'login_attempts', 'vuln_cache', 'quarantine', 'cache_stats_daily', 'cache_discovered_urls', 'cache_warm_queue', 'anis_reputation', 'vuln_status', 'scan_history', 'backups' ) as $t ) {
|
|
$wpdb->query( 'DROP TABLE IF EXISTS ' . self::table( $t ) ); // phpcs:ignore
|
|
}
|
|
delete_option( self::SCHEMA_OPTION );
|
|
}
|
|
}
|