<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

class Vulnity_Scanner_Detection_Alert extends Vulnity_Alert_Base {
    
    // Scanner tool patterns organized by category
    private $scanner_patterns = array(
        // SQL Injection Tools
        'sqlmap' => array(
            'pattern' => 'sqlmap',
            'category' => 'SQL Injection Scanner',
            'severity' => 'critical'
        ),
        'jsql' => array(
            'pattern' => 'jsql',
            'category' => 'SQL Injection Scanner',
            'severity' => 'critical'
        ),
        'sqlninja' => array(
            'pattern' => 'sqlninja',
            'category' => 'SQL Injection Scanner',
            'severity' => 'critical'
        ),
        'havij' => array(
            'pattern' => 'havij',
            'category' => 'SQL Injection Scanner',
            'severity' => 'critical'
        ),
        
        // WordPress Scanners
        'wpscan' => array(
            'pattern' => 'wpscan',
            'category' => 'WordPress Scanner',
            'severity' => 'high'
        ),
        'cmsmap' => array(
            'pattern' => 'cmsmap',
            'category' => 'CMS Scanner',
            'severity' => 'high'
        ),
        'droopescan' => array(
            'pattern' => 'droopescan',
            'category' => 'CMS Scanner',
            'severity' => 'high'
        ),
        
        // Web Vulnerability Scanners
        'nikto' => array(
            'pattern' => 'nikto',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'netsparker' => array(
            'pattern' => 'netsparker',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'acunetix' => array(
            'pattern' => 'acunetix',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'nessus' => array(
            'pattern' => 'nessus',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'openvas' => array(
            'pattern' => 'openvas',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'qualys' => array(
            'pattern' => 'qualysguard',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        
        // Directory/File Bruteforcers
        'gobuster' => array(
            'pattern' => 'gobuster',
            'category' => 'Directory Bruteforcer',
            'severity' => 'medium'
        ),
        'dirb' => array(
            'pattern' => 'dirb',
            'category' => 'Directory Bruteforcer',
            'severity' => 'medium'
        ),
        'dirbuster' => array(
            'pattern' => 'dirbuster',
            'category' => 'Directory Bruteforcer',
            'severity' => 'medium'
        ),
        'ffuf' => array(
            'pattern' => 'ffuf',
            'category' => 'Web Fuzzer',
            'severity' => 'medium'
        ),
        'wfuzz' => array(
            'pattern' => 'wfuzz',
            'category' => 'Web Fuzzer',
            'severity' => 'medium'
        ),
        'feroxbuster' => array(
            'pattern' => 'feroxbuster',
            'category' => 'Directory Bruteforcer',
            'severity' => 'medium'
        ),
        
        // Security Testing Frameworks
        'burp' => array(
            'pattern' => 'burp',
            'category' => 'Security Testing Suite',
            'severity' => 'high'
        ),
        'zap' => array(
            'pattern' => '(zap\/|owasp.*zap)',
            'category' => 'Security Testing Suite',
            'severity' => 'high'
        ),
        'metasploit' => array(
            'pattern' => 'metasploit',
            'category' => 'Exploitation Framework',
            'severity' => 'critical'
        ),
        
        // WAF Detection Tools
        'wafw00f' => array(
            'pattern' => 'wafw00f',
            'category' => 'WAF Detector',
            'severity' => 'medium'
        ),
        'whatwaf' => array(
            'pattern' => 'whatwaf',
            'category' => 'WAF Detector',
            'severity' => 'medium'
        ),
        
        // Information Gathering
        'whatweb' => array(
            'pattern' => 'whatweb',
            'category' => 'Web Fingerprinter',
            'severity' => 'low'
        ),
        'nmap' => array(
            'pattern' => 'nmap',
            'category' => 'Port Scanner',
            'severity' => 'medium'
        ),
        'masscan' => array(
            'pattern' => 'masscan',
            'category' => 'Port Scanner',
            'severity' => 'medium'
        ),
        
        // Crawlers and Spiders
        'skipfish' => array(
            'pattern' => 'skipfish',
            'category' => 'Security Scanner',
            'severity' => 'high'
        ),
        'arachni' => array(
            'pattern' => 'arachni',
            'category' => 'Security Scanner',
            'severity' => 'high'
        ),
        'w3af' => array(
            'pattern' => 'w3af',
            'category' => 'Security Scanner',
            'severity' => 'high'
        ),
        
        // XSS Scanners
        'xsstrike' => array(
            'pattern' => 'xsstrike',
            'category' => 'XSS Scanner',
            'severity' => 'high'
        ),
        'xsser' => array(
            'pattern' => 'xsser',
            'category' => 'XSS Scanner',
            'severity' => 'high'
        ),
        'xenotix' => array(
            'pattern' => 'xenotix',
            'category' => 'XSS Scanner',
            'severity' => 'high'
        ),
        
        // Command Injection Tools
        'commix' => array(
            'pattern' => 'commix',
            'category' => 'Command Injection Scanner',
            'severity' => 'critical'
        ),
        
        // Recon Tools
        'nuclei' => array(
            'pattern' => 'nuclei',
            'category' => 'Vulnerability Scanner',
            'severity' => 'high'
        ),
        'subfinder' => array(
            'pattern' => 'subfinder',
            'category' => 'Subdomain Finder',
            'severity' => 'low'
        ),
        'amass' => array(
            'pattern' => 'amass',
            'category' => 'Reconnaissance Tool',
            'severity' => 'low'
        ),
        'hakrawler' => array(
            'pattern' => 'hakrawler',
            'category' => 'Web Crawler',
            'severity' => 'low'
        ),
        'gau' => array(
            'pattern' => 'gau',
            'category' => 'URL Collector',
            'severity' => 'low'
        ),
        'arjun' => array(
            'pattern' => 'arjun',
            'category' => 'Parameter Finder',
            'severity' => 'medium'
        ),
        'paramspider' => array(
            'pattern' => 'paramspider',
            'category' => 'Parameter Finder',
            'severity' => 'medium'
        ),
        
        // Other Security Tools
        'aquatone' => array(
            'pattern' => 'aquatone',
            'category' => 'Visual Recon Tool',
            'severity' => 'low'
        ),
        'recon-ng' => array(
            'pattern' => 'recon-ng',
            'category' => 'Reconnaissance Framework',
            'severity' => 'medium'
        ),
        'theharvester' => array(
            'pattern' => 'theharvester',
            'category' => 'Information Gathering',
            'severity' => 'low'
        ),
        'patator' => array(
            'pattern' => 'patator',
            'category' => 'Bruteforce Tool',
            'severity' => 'high'
        ),
        'hydra' => array(
            'pattern' => 'hydra',
            'category' => 'Bruteforce Tool',
            'severity' => 'high'
        ),
        'medusa' => array(
            'pattern' => 'medusa',
            'category' => 'Bruteforce Tool',
            'severity' => 'high'
        ),
        
        // Generic patterns for common tools
        'curl' => array(
            'pattern' => '^curl',
            'category' => 'Command Line Tool',
            'severity' => 'low'
        ),
        'wget' => array(
            'pattern' => '^wget',
            'category' => 'Command Line Tool',
            'severity' => 'low'
        ),
        'python-requests' => array(
            'pattern' => 'python-requests',
            'category' => 'Script/Bot',
            'severity' => 'low'
        ),
        'go-http-client' => array(
            'pattern' => 'go-http-client',
            'category' => 'Script/Bot',
            'severity' => 'low'
        )
    );
    
    // Cooldown to avoid spam (5 minutes per IP/tool combination)
    private $cooldown_duration = 300;
    
    public function __construct() {
        $this->alert_type = 'scanner_detected';
        parent::__construct();
    }
    
    protected function register_hooks() {
        // Check on every request
        add_action('init', array($this, 'check_user_agent'), 0);
        
        // Also check during login attempts
        add_action('wp_login_failed', array($this, 'check_on_login_fail'));
        
        // Check on 404 errors (common with scanners)
        add_action('template_redirect', array($this, 'check_on_404'));
    }
    
    public function check_user_agent() {
        $user_agent = $this->get_server_value('HTTP_USER_AGENT');

        if (empty($user_agent)) {
            return;
        }

        $this->evaluate(array(
            'user_agent' => $user_agent,
            'context' => 'general_request'
        ));
    }

    public function check_on_login_fail() {
        $user_agent = $this->get_server_value('HTTP_USER_AGENT');

        if (empty($user_agent)) {
            return;
        }

        $this->evaluate(array(
            'user_agent' => $user_agent,
            'context' => 'login_attempt'
        ));
    }

    public function check_on_404() {
        if (is_404()) {
            $user_agent = $this->get_server_value('HTTP_USER_AGENT');

            if (empty($user_agent)) {
                return;
            }
            $this->evaluate(array(
                'user_agent' => $user_agent,
                'context' => '404_request'
            ));
        }
    }
    
    protected function evaluate($data) {
        $user_agent = strtolower($data['user_agent']);
        $detected_tools = array();
        $highest_severity = 'low';
        
        // Check against all scanner patterns
        foreach ($this->scanner_patterns as $tool_name => $tool_info) {
            if (@preg_match('/' . $tool_info['pattern'] . '/i', $user_agent)) {
                $detected_tools[] = array(
                    'tool' => $tool_name,
                    'category' => $tool_info['category'],
                    'severity' => $tool_info['severity'],
                    'pattern_matched' => $tool_info['pattern']
                );
                
                // Update highest severity
                if ($this->compare_severity($tool_info['severity'], $highest_severity) > 0) {
                    $highest_severity = $tool_info['severity'];
                }
            }
        }
        
        // If no scanners detected, exit
        if (empty($detected_tools)) {
            return;
        }
        
        // Check for cooldown
        $ip = $this->get_client_ip();
        $cooldown_key = 'vulnity_scanner_cooldown_' . md5($ip . '_' . $detected_tools[0]['tool']);
        
        if (get_transient($cooldown_key)) {
            return; // Still in cooldown
        }
        
        // Analyze the request for suspicious patterns
        $suspicious_indicators = $this->analyze_request_patterns();
        
        // Determine final severity based on context and indicators
        $final_severity = $this->calculate_final_severity($highest_severity, $data['context'], $suspicious_indicators);
        
        // Get additional request information
        $request_info = $this->get_request_info();
        
        // Create the alert
        $this->create_alert(array(
            'severity' => $final_severity,
            'title' => sprintf('Scanner Tool Detected: %s', $detected_tools[0]['category']),
            'message' => sprintf(
                'Security scanner "%s" detected from IP %s accessing %s',
                $detected_tools[0]['tool'],
                $ip,
                !empty($request_info['uri']) ? $request_info['uri'] : 'unknown'
            ),
            'details' => array(
                'detected_tools' => $detected_tools,
                'user_agent' => substr($data['user_agent'], 0, 500),
                'ip_address' => $ip,
                'context' => $data['context'],
                'request_uri' => $request_info['uri'],
                'request_method' => $request_info['method'],
                'referrer' => $request_info['referrer'],
                'query_params' => $request_info['query_params'],
                'suspicious_indicators' => $suspicious_indicators,
                'timestamp' => current_time('mysql'),
                'is_authenticated' => is_user_logged_in(),
                'current_user' => is_user_logged_in() ? wp_get_current_user()->user_login : 'anonymous'
            )
        ));
        
        // Set cooldown to avoid spam
        set_transient($cooldown_key, true, $this->cooldown_duration);
        
        // Log for monitoring
        vulnity_log(sprintf(
            '[Vulnity] Scanner detected: %s from IP %s - UA: %s',
            $detected_tools[0]['tool'],
            $ip,
            substr($data['user_agent'], 0, 100)
        ));
        
        // If critical scanner detected, trigger additional security measures
        if ($final_severity === 'critical') {
            $this->trigger_security_response($detected_tools[0], $ip);
        }
    }
    
    /**
     * Analyze request for suspicious patterns
     */
    private function analyze_request_patterns() {
        $indicators = array();

        // Only require a valid nonce for sensitive admin-side request parameter inspection.
        $nonce_valid = $this->is_request_nonce_valid('vulnity_scanner_request');

        if (is_admin() && !$nonce_valid) {
            $nonce_valid = false;
        }

        if ( ! is_admin() || $nonce_valid ) {
            $suspicious_params = array('cmd', 'exec', 'command', 'execute', 'ping', 'query', 'search', 'id');
            $query_parameters = $this->get_request_query_parameters();

            foreach ($suspicious_params as $param) {
                $param_key = sanitize_key($param);
                $param_value = isset($query_parameters[$param_key]) ? $query_parameters[$param_key] : null;

                if ($this->query_parameter_has_value($param_value)) {
                    $indicators[] = 'suspicious_parameter_' . $param;
                }
            }
        }

        // Check for common scanner paths
        $uri = $this->get_server_value('REQUEST_URI', 'url');
        $admin_ajax_url = admin_url('admin-ajax.php');
        $admin_ajax_path = ! empty($admin_ajax_url) ? wp_parse_url($admin_ajax_url, PHP_URL_PATH) : '';

        $scanner_paths = array(
            '/admin', '/administrator',
            '/xmlrpc.php', '/.env', '/wp-config.php', '/backup',
            '/phpmyadmin', '/pma', '/.git', '/.svn', '/shell.php'
        );

        if (!empty($admin_ajax_path)) {
            $scanner_paths[] = $admin_ajax_path;
        }
        
        foreach ($scanner_paths as $path) {
            if (strpos($uri, $path) !== false) {
                $indicators[] = 'scanner_path_' . str_replace('/', '', $path);
                break;
            }
        }
        
        // Check for missing or suspicious referrer
        $referrer = $this->get_server_value('HTTP_REFERER', 'url');
        if (empty($referrer)) {
            $indicators[] = 'no_referrer';
        } elseif (strpos($referrer, get_site_url()) === false) {
            $indicators[] = 'external_referrer';
        }

        // Flag direct access to sensitive endpoints often used during reconnaissance.
        $request_method = strtoupper($this->get_server_value('REQUEST_METHOD'));
        $sensitive_endpoints = array(
            '/wp-login.php',
            '/wp-admin/',
            '/wp-json/',
            '/xmlrpc.php'
        );

        foreach ($sensitive_endpoints as $endpoint) {
            if (strpos($uri, $endpoint) !== false) {
                $indicators[] = 'sensitive_endpoint_' . trim(str_replace(array('/', '.'), '_', $endpoint), '_');
                break;
            }
        }

        if ('HEAD' === $request_method || 'OPTIONS' === $request_method) {
            $indicators[] = 'probing_method_' . strtolower($request_method);
        }

        if ('' === $request_method) {
            $indicators[] = 'missing_request_method';
        }
        
        // Check for rapid requests (if we can track it)
        $rapid_key = 'vulnity_rapid_' . md5($this->get_client_ip());
        $rapid_count = get_transient($rapid_key);
        if ($rapid_count && $rapid_count > 10) {
            $indicators[] = 'rapid_requests';
        }
        set_transient($rapid_key, ($rapid_count ? $rapid_count + 1 : 1), 60);
        
        return $indicators;
    }

    /**
     * Verifies the request nonce when available to ensure trusted origins.
     */
    private function is_request_nonce_valid($action) {
        $nonce = $this->get_sanitized_request_value('_wpnonce');

        return !empty($nonce) && function_exists('wp_verify_nonce') && wp_verify_nonce($nonce, $action);
    }
    
    /**
     * Calculate final severity based on multiple factors
     */
    private function calculate_final_severity($base_severity, $context, $indicators) {
        $severity_levels = array('low' => 1, 'medium' => 2, 'high' => 3, 'critical' => 4);
        $current_level = $severity_levels[$base_severity];
        
        // Increase severity based on context
        if ($context === 'login_attempt') {
            $current_level = min($current_level + 1, 4);
        } elseif ($context === '404_request' && count($indicators) > 2) {
            $current_level = min($current_level + 1, 4);
        }
        
        // Increase severity if multiple suspicious indicators
        if (count($indicators) >= 3) {
            $current_level = min($current_level + 1, 4);
        }
        
        // Convert back to string
        $severity_map = array_flip($severity_levels);
        return $severity_map[$current_level];
    }
    
    /**
     * Compare severity levels
     */
    private function compare_severity($sev1, $sev2) {
        $levels = array('low' => 1, 'medium' => 2, 'high' => 3, 'critical' => 4);
        return $levels[$sev1] - $levels[$sev2];
    }
    
    /**
     * Get detailed request information
     */
    private function get_request_info() {
        $request_info = array(
            'uri' => $this->get_server_value('REQUEST_URI', 'url'),
            'method' => $this->get_server_value('REQUEST_METHOD'),
            'referrer' => $this->get_server_value('HTTP_REFERER', 'url', 'none'),
            'query_params' => array()
        );

        $nonce_valid = $this->is_request_nonce_valid('vulnity_scanner_request');
        if (is_admin() && !$nonce_valid) {
            return $request_info;
        }

        $query_parameters = $this->get_request_query_parameters();
        if (empty($query_parameters)) {
            return $request_info;
        }

        foreach (array_keys($query_parameters) as $param) {
            if (!is_string($param) || $param === '') {
                continue;
            }

            $request_info['query_params'][] = sanitize_key($param);
        }

        $request_info['query_params'] = array_values(array_unique(array_filter($request_info['query_params'])));

        if (count($request_info['query_params']) > 20) {
            $request_info['query_params'] = array_slice($request_info['query_params'], 0, 20);
            $request_info['query_params'][] = 'truncated';
        }

        return $request_info;
    }

    private function get_sanitized_request_value($key) {
        if (!is_string($key) || $key === '') {
            return '';
        }

        $value = filter_input(INPUT_POST, $key, FILTER_UNSAFE_RAW);
        if (null === $value) {
            $value = filter_input(INPUT_GET, $key, FILTER_UNSAFE_RAW);
        }

        if (null === $value || is_array($value)) {
            return '';
        }

        return sanitize_text_field(function_exists('wp_unslash') ? wp_unslash($value) : $value);
    }

    private function get_request_query_parameters() {
        $query_string = filter_input(INPUT_SERVER, 'QUERY_STRING', FILTER_UNSAFE_RAW);

        if (!is_string($query_string) || $query_string === '') {
            $request_uri = $this->get_server_value('REQUEST_URI', 'url');
            $parsed_query = is_string($request_uri) ? wp_parse_url($request_uri, PHP_URL_QUERY) : '';
            $query_string = is_string($parsed_query) ? $parsed_query : '';
        }

        if ($query_string === '') {
            return array();
        }

        $parameters = array();
        parse_str($query_string, $parameters);

        if (!is_array($parameters) || empty($parameters)) {
            return array();
        }

        $sanitized = array();
        foreach ($parameters as $key => $value) {
            if (!is_string($key) || $key === '') {
                continue;
            }

            $sanitized_key = sanitize_key($key);
            if ($sanitized_key === '') {
                continue;
            }

            $sanitized[$sanitized_key] = $this->sanitize_query_parameter_value($value);
        }

        return $sanitized;
    }

    private function sanitize_query_parameter_value($value) {
        if (is_array($value)) {
            return array_map(array($this, 'sanitize_query_parameter_value'), $value);
        }

        if (!is_scalar($value)) {
            return '';
        }

        return sanitize_text_field((string) $value);
    }

    private function query_parameter_has_value($value) {
        if (is_array($value)) {
            foreach ($value as $segment) {
                if ($this->query_parameter_has_value($segment)) {
                    return true;
                }
            }

            return false;
        }

        return is_string($value) && $value !== '';
    }
    
    /**
     * Trigger additional security response for critical scanners
     */
    private function trigger_security_response($tool_info, $ip) {
        // Log critical scanner detection
        vulnity_log(sprintf(
            '[Vulnity] CRITICAL: %s scanner detected from IP %s - Category: %s',
            $tool_info['tool'],
            $ip,
            $tool_info['category']
        ));

        // Store information about the scanner
        $scanner_log = get_option('vulnity_scanner_log', array());
        $scanner_log[] = array(
            'tool' => $tool_info['tool'],
            'category' => $tool_info['category'],
            'ip' => $ip,
            'timestamp' => current_time('mysql')
        );

        // Keep only last 100 entries
        $scanner_log = array_slice($scanner_log, -100);
        update_option('vulnity_scanner_log', $scanner_log);

        // If it's an exploitation framework, consider it a breach attempt
        if (in_array($tool_info['category'], array('Exploitation Framework', 'SQL Injection Scanner', 'Command Injection Scanner'))) {
            do_action('vulnity_critical_event_detected', 'exploitation_tool_detected', array(
                'tool' => $tool_info['tool'],
                'ip' => $ip
            ));
        }
    }

    private function get_server_value( $key, $type = 'text', $default = '' ) {
        $value = filter_input( INPUT_SERVER, $key, FILTER_SANITIZE_FULL_SPECIAL_CHARS );

        if ( null === $value ) {
            return $default;
        }

        if ( is_array( $value ) ) {
            $value = reset( $value );
        }

        if ( function_exists( 'wp_unslash' ) ) {
            $value = wp_unslash( $value );
        }

        switch ( $type ) {
            case 'url':
                if ( function_exists( 'esc_url_raw' ) ) {
                    $sanitized = esc_url_raw( $value );
                } else {
                    $sanitized = filter_var( $value, FILTER_SANITIZE_URL );
                }
                break;
            default:
                if ( function_exists( 'sanitize_text_field' ) ) {
                    $sanitized = sanitize_text_field( $value );
                } else {
                    if ( ! is_string( $value ) ) {
                        $sanitized = '';
                    } else {
                        $sanitized = preg_replace( '/[\x00-\x1F\x7F]+/u', '', $value );

                        if ( null === $sanitized ) {
                            $sanitized = '';
                        } else {
                            $sanitized = trim( $sanitized );
                        }
                    }
                }
        }

        return $sanitized;
    }
}
