// Overview

Built to emulate enterprise environments, the ascendant.design SOC Homelab is a fully virtualized cybersecurity training ground for hands-on threat detection. Hosted on VirtualBox within a securely isolated network, the architecture consists of five virtual machines: a Windows Server 2022 Domain Controller, a Windows 11 Pro endpoint, a vulnerable Metasploitable 2 instance, and a Kali Linux attack node. Centralized monitoring is handled by a Wazuh SIEM, which actively detects simulated malicious behavior and correlates it directly with the MITRE ATT&CK framework.

// Overview

Built to emulate enterprise environments, the ascendant.design SOC Homelab is a fully virtualized cybersecurity training ground for hands-on threat detection. Hosted on VirtualBox within a securely isolated network, the architecture consists of five virtual machines: a Windows Server 2022 Domain Controller, a Windows 11 Pro endpoint, a vulnerable Metasploitable 2 instance, and a Kali Linux attack node. Centralized monitoring is handled by a Wazuh SIEM, which actively detects simulated malicious behavior and correlates it directly with the MITRE ATT&CK framework.

// SOC Live Dashboard

This live dashboard provides a real-time window into the automated telemetry captured across your virtualized cybersecurity infrastructure. It aggregates threat intelligence from active Wazuh SIEM endpoints—tracking total event volume, classifying alerts by risk severity level, and mapping incoming security events directly to the MITRE ATT&CK framework. Designed to showcase automated endpoint protection and continuous threat monitoring, the feed highlights active attack vectors and system telemetry in real time.

Interactive Sandbox

Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.

Architecture Overview: Decoupled Telemetry Pipeline

This dashboard relies on a decoupled, push-based telemetry pipeline to securely bridge an isolated SIEM with a public web environment. An autonomous Python script on the Wazuh server queries the local OpenSearch API for security metrics—such as MITRE ATT&CK mappings and alert severities—and pushes them outbound as a lightweight JSON payload. Utilizing a pre-shared key (PSK) and custom headers, this outbound-only transmission bypasses external firewalls and maintains strict network segmentation, completely hiding the internal SIEM from inbound internet traffic.

Upon receipt, a custom WordPress REST API endpoint validates the payload and caches it in memory using Transients. This stateless caching prevents database thrashing and guarantees instantaneous page loads. Custom shortcodes then dynamically render the cached intelligence into responsive dashboards, delivering real-time SOC visibility with zero direct infrastructure exposure.

Event Volume (Last 24 Hours – UTC)

Live SOC Telemetry 8,621 Events

Severities
  • Level 53,887
  • Level 32,329
  • Level 72,019
  • Level 4225
  • Level 972
Top MITRE Tactics
  • Defense Evasion 2,533
  • Impact 1,289
  • Privilege Escalation 202
Active Agents
  • HomelabWindowsP 1,985
  • WIN-BACJJ5JI7U9 1,889
  • WIN-QK6A1G7ET00 1,805
Top Security Events
  • Software protection service scheduled successfully. 887
  • Registry Value Entry Added to the System 822
  • Registry Value Integrity Checksum Changed 774
  • Name resolution for the name settings-win.data.microsoft.com timed out 567

Compliance Mapping

NIST Control Hits
  • Control SI.7 2,416
  • Control CM.1 2,266
  • Control AU.6 789
Top Event Categories
  • Windows 3,147
  • Ossec 2,897
  • Syscheck 2,416
  • Syscheck_registry 2,308

Telemetry Sources

Event Channels
  • EventChannel 3,151
  • syscheck 2,416
  • sca 2,267
  • rootcheck 340
  • journald 156

SOC Live Dashboard

This live dashboard provides a real-time window into the automated telemetry captured across your virtualized cybersecurity infrastructure. It aggregates threat intelligence from active Wazuh SIEM endpoints—tracking total event volume, classifying alerts by risk severity level, and mapping incoming security events directly to the MITRE ATT&CK framework. Designed to showcase automated endpoint protection and continuous threat monitoring, the feed highlights active attack vectors and system telemetry in real time.

Interactive Sandbox

Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.

Architecture Overview: Decoupled Telemetry Pipeline

This dashboard relies on a decoupled, push-based telemetry pipeline to securely bridge an isolated SIEM with a public web environment. An autonomous Python script on the Wazuh server queries the local OpenSearch API for security metrics—such as MITRE ATT&CK mappings and alert severities—and pushes them outbound as a lightweight JSON payload. Utilizing a pre-shared key (PSK) and custom headers, this outbound-only transmission bypasses external firewalls and maintains strict network segmentation, completely hiding the internal SIEM from inbound internet traffic.

Upon receipt, a custom WordPress REST API endpoint validates the payload and caches it in memory using Transients. This stateless caching prevents database thrashing and guarantees instantaneous page loads. Custom shortcodes then dynamically render the cached intelligence into responsive dashboards, delivering real-time SOC visibility with zero direct infrastructure exposure.

Event Volume (Last 24 Hours – UTC)

Live SOC Telemetry 8,621 Events

Severities
  • Level 53,887
  • Level 32,329
  • Level 72,019
  • Level 4225
  • Level 972
Top MITRE Tactics
  • Defense Evasion 2,533
  • Impact 1,289
  • Privilege Escalation 202
Active Agents
  • HomelabWindowsP 1,985
  • WIN-BACJJ5JI7U9 1,889
  • WIN-QK6A1G7ET00 1,805
Top Security Events
  • Software protection service scheduled successfully. 887
  • Registry Value Entry Added to the System 822
  • Registry Value Integrity Checksum Changed 774
  • Name resolution for the name settings-win.data.microsoft.com timed out 567

Compliance Mapping

NIST Control Hits
  • Control SI.7 2,416
  • Control CM.1 2,266
  • Control AU.6 789
Top Event Categories
  • Windows 3,147
  • Ossec 2,897
  • Syscheck 2,416
  • Syscheck_registry 2,308

Telemetry Sources

Event Channels
  • EventChannel 3,151
  • syscheck 2,416
  • sca 2,267
  • rootcheck 340
  • journald 156

// Tech Stack

Wazuh SIEM Manager
Kali Linux Virtual Machine
Windows 2022 Active Dir
Ubuntu Linux Instance
Windows 11 Pro Instance

// Network Overview

VM ROLE OS IP
DC1 Domain Controller Windows 2022 Server 192.168.128.10
Wazuh SIEM SIEM Ubuntu 22.04 192.168.128.8
Client1 Workstation Windows 11 Pro 192.168.128.7
Client2 Workstation Ubuntu 26.04 192.168.128.11
Kali Linux Attacker Attacker Kali Linux 192.168.128.9

// Wazuh SIEM PHP Construction Script

				
					<?php
// --- 1. DATA CATCHER ---
add_action('rest_api_init', function () {
    register_rest_route('soc/v1', '/telemetry', array(
        'methods' => 'POST',
        'callback' => 'update_wazuh_telemetry',
        'permission_callback' => '__return_true'
    ));
});

function update_wazuh_telemetry($request) {
    $secret_token = 'INSERT_YOUR_SECRET_HERE'; 
    if ($request->get_header('x-api-key') !== $secret_token) {
        return new WP_Error('unauthorized', 'Invalid Token', array('status' => 401));
    }
    set_transient('wazuh_alert_stats_cache', $request->get_json_params(), 600); 
    return rest_ensure_response(array('status' => 'success'));
}

function get_wz_color($index) {
    $colors = array('#ef4444', '#f97316', '#facc15', '#22c55e', '#009fe3');
    return $colors[$index % count($colors)];
}

// --- 2. WIDGET 1: MASTER DASHBOARD ---
function display_wazuh_telemetry() {
    $data = get_transient('wazuh_alert_stats_cache');
    if (empty($data) || !isset($data['severities'])) return '<p>Awaiting Data...</p>';
    
    $html = '<div class="wazuh-stats-widget">';
    $html .= '<h4>Live SOC Telemetry <span class="wazuh-total-badge">' . number_format($data['total']) . ' Events</span></h4>';
    $html .= '<div class="wz-master-layout">';
    
    $html .= '<div class="wz-sidebar-severities"><h5 class="wz-section-title">Severities</h5><ul class="wz-severities-stack">';
    foreach ($data['severities'] as $sev) {
        $lvl = intval($sev['level']);
        $class = ($lvl >= 12) ? 'wz-critical' : (($lvl >= 8) ? 'wz-high' : (($lvl >= 4) ? 'wz-medium' : 'wz-low'));
        $html .= '<li class="' . $class . '"><span>Level ' . $lvl . '</span><span class="wz-count">' . number_format($sev['count']) . '</span></li>';
    }
    $html .= '</ul></div>';

    $html .= '<div class="wz-main-grid"><div class="wz-sub-row">';
    
    $html .= '<div class="wz-card"><h5 class="wz-section-title">Top MITRE Tactics</h5><ul class="wz-text-list">';
    $mitre_data = isset($data['mitre']) ? $data['mitre'] : [];
    if (empty($mitre_data)) { $html .= '<li><span>No MITRE data</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($mitre_data as $m) { $c = get_wz_color($i++); $html .= '<li style="border-bottom-color: '.$c.';"><span>' . esc_html(ucfirst($m['tactic'])) . '</span> <span style="color: '.$c.' !important;">' . number_format($m['count']) . '</span></li>'; } }
    $html .= '</ul></div>';

    $html .= '<div class="wz-card"><h5 class="wz-section-title">Active Agents</h5><ul class="wz-text-list">';
    $agent_data = isset($data['agents']) ? $data['agents'] : [];
    if (empty($agent_data)) { $html .= '<li><span>No agent data</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($agent_data as $a) { $c = get_wz_color($i++); $html .= '<li style="border-bottom-color: '.$c.';"><span>' . esc_html($a['name']) . '</span> <span style="color: '.$c.' !important;">' . number_format($a['count']) . '</span></li>'; } }
    $html .= '</ul></div></div>'; 

    $html .= '<div class="wz-card wz-full-card"><h5 class="wz-section-title">Top Security Events</h5><ul class="wz-text-list">';
    $event_data = isset($data['events']) ? $data['events'] : [];
    if (empty($event_data)) { $html .= '<li><span>No events</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($event_data as $e) { $c = get_wz_color($i++); $html .= '<li style="border-bottom-color: '.$c.';"><span>' . esc_html($e['desc']) . '</span> <span style="color: '.$c.' !important;">' . number_format($e['count']) . '</span></li>'; } }
    $html .= '</ul></div>';
    
    $html .= '</div></div></div>'; 
    return $html;
}
add_shortcode('wazuh_live_stats', 'display_wazuh_telemetry');

// --- 3. WIDGET 2: COMPLIANCE & AUTHENTICATION ---
function display_wazuh_compliance() {
    $data = get_transient('wazuh_alert_stats_cache');
    if (empty($data)) return '<p>Awaiting Data...</p>';

    $html = '<div class="wazuh-stats-widget"><h4 style="border-bottom:none; margin-bottom:10px;">Security Framework & Auth Groupings</h4>';
    $html .= '<div class="wz-sub-row">';
    
    $html .= '<div class="wz-card"><h5 class="wz-section-title">NIST Control Hits</h5><ul class="wz-text-list">';
    $nist_data = isset($data['nist']) ? $data['nist'] : [];
    if (empty($nist_data)) { $html .= '<li><span>No NIST data</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($nist_data as $n) { $c = get_wz_color($i++); $html .= '<li style="border-bottom-color: '.$c.';"><span>Control ' . esc_html($n['control']) . '</span> <span style="color: '.$c.' !important;">' . number_format($n['count']) . '</span></li>'; } }
    $html .= '</ul></div>';
    
    $html .= '<div class="wz-card"><h5 class="wz-section-title">Top Event Categories</h5><ul class="wz-text-list">';
    $group_data = isset($data['groups']) ? $data['groups'] : [];
    if (empty($group_data)) { $html .= '<li><span>No category data</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($group_data as $g) { $c = get_wz_color($i++); $html .= '<li style="border-bottom-color: '.$c.';"><span>' . esc_html(ucfirst($g['group'])) . '</span> <span style="color: '.$c.' !important;">' . number_format($g['count']) . '</span></li>'; } }
    $html .= '</ul></div></div></div>';
    return $html;
}
add_shortcode('wazuh_compliance_stats', 'display_wazuh_compliance');

// --- 4. WIDGET 3: TELEMETRY SOURCES ---
function display_wazuh_sources() {
    $data = get_transient('wazuh_alert_stats_cache');
    if (empty($data)) return '<p>Awaiting Data...</p>';

    $html = '<div class="wazuh-stats-widget"><h4>Telemetry Sources</h4>';
    $html .= '<div class="wz-card"><h5 class="wz-section-title">Event Channels</h5><ul class="wz-text-list">';
    
    $loc_data = isset($data['locations']) ? $data['locations'] : [];
    if (empty($loc_data)) { $html .= '<li><span>No source data</span> <span>0</span></li>'; } 
    else { $i = 0; foreach ($loc_data as $l) { $c = get_wz_color($i++); $loc_name = (strlen($l['loc']) > 30) ? substr($l['loc'], 0, 30) . '...' : $l['loc']; $html .= '<li style="border-bottom-color: '.$c.';"><span>' . esc_html($loc_name) . '</span> <span style="color: '.$c.' !important;">' . number_format($l['count']) . '</span></li>'; } }
    $html .= '</ul></div></div>';
    return $html;
}
add_shortcode('wazuh_source_stats', 'display_wazuh_sources');

// --- 5. WIDGET 4: TIMELINE GRAPH ---
function display_wazuh_graph() {
    $data = get_transient('wazuh_alert_stats_cache');
    if (empty($data) || !isset($data['timeline'])) return '<p>Awaiting Graph Data...</p>';

    $labels = [];
    $counts = [];
    
    foreach ((array)$data['timeline'] as $t) {
        $labels[] = gmdate('g A', intval($t['time'] / 1000));
        $counts[] = intval($t['count']);
    }

    $chart_id = 'wazuhChart_' . uniqid();

    $html = '<div class="wazuh-stats-widget">';
    $html .= '<h4 style="border-bottom:none; margin-bottom:10px;">Event Volume (Last 24 Hours - UTC)</h4>';
    $html .= '<div style="position: relative; height: 250px; width: 100%; display: block;">';
    $html .= '<canvas id="' . $chart_id . '" style="max-width: 100%; cursor: pointer;"></canvas>';
    $html .= '</div></div>';
    
    static $chart_js_loaded = false;
    if (!$chart_js_loaded) {
        $html .= '<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>';
        $chart_js_loaded = true;
    }
    
    $html .= '<script>
        (function() {
            function renderWazuhChart() {
                if (typeof Chart === "undefined") {
                    setTimeout(renderWazuhChart, 150);
                    return;
                }
                
                var canvas = document.getElementById("' . $chart_id . '");
                if (!canvas) return;
                
                var ctx = canvas.getContext("2d");
                var gradient = ctx.createLinearGradient(0, 0, 0, 300);
                gradient.addColorStop(0, "rgba(0, 159, 227, 0.4)");
                gradient.addColorStop(1, "rgba(0, 159, 227, 0.0)");
                
                var wazuhChart = new Chart(ctx, {
                    type: "line",
                    data: {
                        labels: ' . json_encode($labels) . ',
                        datasets: [{
                            label: "Security Events",
                            data: ' . json_encode($counts) . ',
                            borderColor: "#009fe3",
                            backgroundColor: gradient,
                            borderWidth: 3,
                            pointBackgroundColor: "#ffffff",
                            pointBorderColor: "#009fe3",
                            pointBorderWidth: 2,
                            pointRadius: 4,
                            pointHoverRadius: 6,
                            fill: true,
                            tension: 0.4
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: { legend: { display: false } },
                        scales: {
                            x: { grid: { display: false }, ticks: { maxRotation: 45, minRotation: 45 } },
                            y: { beginAtZero: true, grid: { color: "#e5e7eb" } }
                        },
                        onClick: (e, elements) => {
                            if (elements.length > 0) {
                                const dataIndex = elements[0].index;
                                const clickedTime = wazuhChart.data.labels[dataIndex];
                                const clickedCount = wazuhChart.data.datasets[0].data[dataIndex];
                                const drillDownUrl = "/soc-investigation/?time=" + encodeURIComponent(clickedTime) + "&events=" + clickedCount;
                                window.location.href = drillDownUrl;
                            }
                        }
                    }
                });
            }
            
            if (document.readyState === "loading") {
                document.addEventListener("DOMContentLoaded", renderWazuhChart);
            } else {
                setTimeout(renderWazuhChart, 200); 
            }
        })();
    </script>';

    return $html;
}
add_shortcode('wazuh_graph_stats', 'display_wazuh_graph');

// --- 6. WIDGET 5: INCIDENT RESPONSE DRILL-DOWN (LIVE DATA) ---
function display_wazuh_investigation() {
    $time_param = isset($_GET['time']) ? sanitize_text_field($_GET['time']) : '';
    $data = get_transient('wazuh_alert_stats_cache');
    
    $raw_logs = isset($data['raw_logs']) ? $data['raw_logs'] : [];
    $filtered_logs = [];

    if ($time_param && !empty($raw_logs)) {
        foreach ($raw_logs as $log) {
            $log_hour = gmdate('g A', strtotime($log['time']));
            if ($log_hour === $time_param) {
                $filtered_logs[] = $log;
            }
        }
    } else {
        $filtered_logs = array_slice($raw_logs, 0, 50);
        $time_param = 'Latest Activity';
    }

    $actual_count = count($filtered_logs);

    $html = '<div class="wazuh-stats-widget" style="overflow: hidden; max-width: 100%;">';
    $html .= '<h4>SOC Investigation Drill-Down <span class="wazuh-total-badge">' . esc_html($actual_count) . ' Events at ' . esc_html($time_param) . '</span></h4>';
    $html .= '<p style="font-size: 0.85rem; color: #6b7280; margin-bottom: 20px; line-height: 1.5;"><strong>Live Environment:</strong> Displaying raw SIEM telemetry from the selected timeframe. <strong>Click any row to view the raw JSON payload.</strong></p>';

    $html .= '<div style="width: 100%; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; display: block; border-radius: 8px; border: 1px solid #e5e7eb;">';
    $html .= '<table style="width: 100%; min-width: 700px; border-collapse: collapse; text-align: left; font-size: 0.8rem; background: #ffffff;">';
    $html .= '<thead><tr style="border-bottom: 2px solid #e5e7eb; background: #f9fafb; color: #4b5563; text-transform: uppercase;">';
    $html .= '<th style="padding: 12px; white-space: nowrap;">Timestamp</th><th style="padding: 12px; white-space: nowrap;">Source / Agent</th><th style="padding: 12px; white-space: nowrap;">Level</th><th style="padding: 12px;">Description</th><th style="padding: 12px; white-space: nowrap;">MITRE Tactic</th>';
    $html .= '</tr></thead><tbody>';

    if (empty($filtered_logs)) {
        $html .= '<tr><td colspan="5" style="padding: 20px; text-align: center; color: #6b7280;">No raw events available for this timeframe. They may have fallen out of the 24-hour buffer.</td></tr>';
    } else {
        $index = 0;
        foreach ($filtered_logs as $log) {
            $lvl = intval($log['level']);
            $bg_color = ($lvl >= 12) ? '#ef4444' : (($lvl >= 8) ? '#f97316' : (($lvl >= 4) ? '#facc15' : '#009fe3'));
            $text_color = ($lvl >= 12 || $lvl < 4 || $lvl >= 8) ? '#ffffff' : '#1a1a1a';
            $display_time = gmdate('Y-m-d H:i:s', strtotime($log['time'])) . ' UTC';
            
            $html .= '<tr style="border-bottom: 1px solid #f3f4f6; cursor: pointer; transition: background 0.2s;" onmouseover="this.style.background=\'#f3f4f6\'" onmouseout="this.style.background=\'transparent\'" onclick="document.getElementById(\'raw_log_' . $index . '\').style.display = document.getElementById(\'raw_log_' . $index . '\').style.display === \'none\' ? \'table-row\' : \'none\';">';
            $html .= '<td style="padding: 12px; color: #4b5563; white-space: nowrap;">' . esc_html($display_time) . '</td>';
            $html .= '<td style="padding: 12px; font-weight: 600; white-space: nowrap;">' . esc_html($log['agent']) . '</td>';
            $html .= '<td style="padding: 12px;"><span style="background: ' . $bg_color . '; color: ' . $text_color . '; padding: 4px 8px; border-radius: 6px; font-weight: 700;">L' . $lvl . '</span></td>';
            $html .= '<td style="padding: 12px;">' . esc_html($log['desc']) . '</td>';
            $html .= '<td style="padding: 12px; color: #009fe3; font-weight: 500; white-space: nowrap;">' . esc_html($log['mitre']) . '</td>';
            $html .= '</tr>';
            
            $html .= '<tr id="raw_log_' . $index . '" style="display: none; background: #1e1e1e; color: #a3be8c; font-family: monospace; font-size: 0.75rem;">';
            $html .= '<td colspan="5" style="padding: 16px; border-bottom: 2px solid #000000; white-space: pre-wrap;">';
            $html .= "{\n  \"@timestamp\": \"{$log['time']}\",\n  \"agent\": {\n    \"name\": \"{$log['agent']}\"\n  },\n  \"rule\": {\n    \"level\": {$lvl},\n    \"description\": \"" . htmlspecialchars($log['desc'], ENT_QUOTES) . "\",\n    \"mitre\": {\n      \"tactic\": \"{$log['mitre']}\"\n    }\n  }\n}";
            $html .= '</td></tr>';
            
            $index++;
        }
    }

    $html .= '</tbody></table></div>';
    $html .= '<div style="margin-top: 20px; text-align: right;">';
    $html .= '<a href="javascript:history.back()" style="display: inline-block; background: #1a1a1a; color: #ffffff; padding: 10px 18px; border-radius: 8px; text-decoration: none; font-size: 0.85rem; font-weight: 700; transition: opacity 0.2s;">&larr; Back to Dashboard</a>';
    $html .= '</div></div>';

    return $html;
}
add_shortcode('wazuh_investigation_demo', 'display_wazuh_investigation');

// --- 7. WIDGET 6: INTERACTIVE SANDBOX BUTTON ---
add_action('rest_api_init', function () {
    register_rest_route('soc/v1', '/simulate', array(
        'methods' => 'POST',
        'callback' => 'inject_simulated_alert',
        'permission_callback' => '__return_true'
    ));
});

function inject_simulated_alert($request) {
    $data = get_transient('wazuh_alert_stats_cache');
    if (!$data) return new WP_Error('no_data', 'Awaiting live data sync.', array('status' => 404));

    $fake_log = [
        'time' => gmdate('Y-m-d\TH:i:s\Z'),
        'agent' => 'WIN-QK6A1G7ET00',
        'level' => 12,
        'desc' => '[SIMULATED] High amount of POST requests in a small period of time',
        'mitre' => 'Command and Control'
    ];

    if (isset($data['raw_logs'])) {
        array_unshift($data['raw_logs'], $fake_log);
        $data['raw_logs'] = array_slice($data['raw_logs'], 0, 1000); 
    }

    $data['total'] = isset($data['total']) ? $data['total'] + 1 : 1;

    $updated_sev = false;
    foreach ((array)$data['severities'] as &$sev) {
        if (intval($sev['level']) === 12) {
            $sev['count'] += 1;
            $updated_sev = true;
            break;
        }
    }
    if (!$updated_sev) $data['severities'][] = ['level' => 12, 'count' => 1];

    $current_hour = floor(time() / 3600) * 3600 * 1000; 
    $updated_time = false;
    foreach ((array)$data['timeline'] as &$t) {
        if ($t['time'] == $current_hour) {
            $t['count'] += 1;
            $updated_time = true;
            break;
        }
    }
    if (!$updated_time) $data['timeline'][] = ['time' => $current_hour, 'count' => 1];

    set_transient('wazuh_alert_stats_cache', $data, 600);
    return rest_ensure_response(array('status' => 'success'));
}

function display_sandbox_button() {
    $btn_id = 'trigger_btn_' . uniqid();
    $status_id = 'status_' . uniqid();

    $html = '<div style="background: #ffffff; border-radius: 16px; padding: 24px; text-align: center; border: 1px solid #e5e7eb; box-shadow: 0 4px 24px rgba(0,0,0,0.04); margin-bottom: 20px;">';
    $html .= '<h4 style="margin: 0 0 10px 0; font-size: 1.1rem; font-weight: 800; text-transform: uppercase; color: #1a1a1a;">Interactive Sandbox</h4>';
    $html .= '<p style="font-size: 0.85rem; color: #6b7280; margin-bottom: 20px;">Simulate a Level 12 Command & Control alert to see how the front-end SOC dashboard updates telemetry in real-time.</p>';
    $html .= '<button id="' . $btn_id . '" style="background: #ef4444; color: #ffffff; border: none; padding: 12px 24px; border-radius: 8px; font-weight: 700; cursor: pointer; transition: background 0.2s;">Simulate Attack</button>';
    $html .= '<p id="' . $status_id . '" style="font-size: 0.8rem; font-weight: 700; color: #ef4444; margin-top: 15px; display: none;">Payload Injected! Refreshing telemetry...</p>';
    $html .= '</div>';
    
    $html .= '<script>
        document.addEventListener("DOMContentLoaded", function() {
            var btn = document.getElementById("' . $btn_id . '");
            if (!btn) return;
            
            btn.addEventListener("click", function() {
                btn.disabled = true;
                btn.style.background = "#9ca3af";
                btn.innerText = "Injecting...";
                
                fetch("/wp-json/soc/v1/simulate", { method: "POST" })
                .then(response => response.json())
                .then(data => {
                    document.getElementById("' . $status_id . '").style.display = "block";
                    setTimeout(() => location.reload(), 1200);
                })
                .catch(error => {
                    btn.innerText = "Error (Check Console)";
                    btn.style.background = "#ef4444";
                });
            });
        });
    </script>';
    return $html;
}
add_shortcode('wazuh_sandbox_trigger', 'display_sandbox_button');

// --- 8. UNIVERSAL CSS ---
add_action('wp_footer', function() {
    echo '<style>
    .wazuh-stats-widget, .wazuh-stats-widget * { box-sizing: border-box !important; }
    .wazuh-stats-widget { background: #ffffff; border-radius: 16px; padding: 24px; width: 100%; max-width: 100%; box-shadow: 0 4px 24px rgba(0,0,0,0.04); font-family: "Inter", sans-serif; color: #1a1a1a; margin-bottom: 20px;}
    .wazuh-stats-widget h4 { margin: 0 0 20px 0; font-size: 1.1rem; font-weight: 800; letter-spacing: 0.02em; text-transform: uppercase; color: #1a1a1a !important; display: flex; align-items: center; justify-content: space-between; border-bottom: 2px solid #f3f4f6; padding-bottom: 12px; }
    .wazuh-stats-widget h4::before { content: ""; display: inline-block; width: 10px; height: 10px; background-color: #009fe3; border-radius: 50%; margin-right: 10px; box-shadow: 0 0 8px rgba(0, 159, 227, 0.6); animation: wazuh-pulse 2s infinite; }
    .wazuh-total-badge { font-size: 0.75rem; background: #e5e7eb; padding: 4px 10px; border-radius: 20px; color: #4b5563; font-weight: 700; margin-left: auto; }
    @keyframes wazuh-pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
    .wz-section-title { margin: 0 0 10px 0; font-size: 0.75rem; color: #6b7280; text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em; }
    .wz-master-layout { display: flex; gap: 20px; align-items: flex-start; max-width: 100%; }
    .wz-sidebar-severities { width: 240px; flex-shrink: 0; background: #f4f5f7; padding: 14px; border-radius: 12px; }
    .wazuh-stats-widget ul.wz-severities-stack { display: flex !important; flex-direction: column !important; gap: 8px !important; list-style: none !important; padding: 0 !important; margin: 0 !important; }
    .wz-severities-stack li { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 10px 12px !important; background: #ffffff !important; border-radius: 8px !important; font-size: 0.8rem !important; font-weight: 600 !important; border-left: 4px solid #e5e7eb !important; color: #1a1a1a !important; }
    .wz-severities-stack li.wz-critical { border-left-color: #ef4444 !important; } .wz-severities-stack li.wz-high { border-left-color: #f97316 !important; } .wz-severities-stack li.wz-medium { border-left-color: #facc15 !important; } .wz-severities-stack li.wz-low { border-left-color: #009fe3 !important; }
    .wz-severities-stack .wz-count { background-color: #1a1a1a; color: #ffffff; padding: 2px 8px; border-radius: 6px; font-weight: 700; font-size: 0.85rem; width: 45px; text-align: center; }
    .wz-main-grid { flex: 1; display: flex; flex-direction: column; gap: 16px; min-width: 0; max-width: 100%; }
    .wz-sub-row { display: flex; gap: 16px; width: 100%; max-width: 100%; }
    .wz-card { flex: 1; background: #f4f5f7; padding: 14px; border-radius: 12px; min-width: 0; max-width: 100%; }
    .wz-full-card { width: 100%; max-width: 100%; }
    .wazuh-stats-widget ul.wz-text-list { display: flex !important; flex-direction: column !important; gap: 0 !important; list-style: none !important; padding: 0 !important; margin: 0 !important; width: 100%; }
    .wz-text-list li { display: flex !important; flex-direction: row !important; justify-content: space-between !important; align-items: center !important; font-size: 0.8rem !important; font-weight: 500 !important; padding: 10px 0 !important; border-bottom: 2px solid #e5e7eb !important; background: transparent !important; color: #1a1a1a !important; width: 100%; }
    .wz-text-list li:last-child { border-bottom: none !important; padding-bottom: 0 !important; }
    .wz-text-list li > span:first-child { flex: 1; padding-right: 12px; text-align: left; word-break: break-word; overflow-wrap: break-word; white-space: normal; }
    .wz-text-list li > span:last-child { font-weight: 700 !important; background: #ffffff !important; padding: 4px 0 !important; border-radius: 10px !important; font-size: 0.75rem !important; flex-shrink: 0 !important; width: 60px !important; text-align: center !important; display: inline-block !important; margin-left: auto !important; }
    @media (max-width: 768px) {
        .wazuh-stats-widget { padding: 16px; }
        .wz-master-layout { flex-direction: column; }
        .wz-sidebar-severities { width: 100%; margin-bottom: 10px; }
        .wz-main-grid { width: 100%; }
        .wz-sub-row { flex-direction: column; gap: 16px; width: 100%; }
        .wazuh-stats-widget h4 { flex-direction: column; align-items: flex-start; gap: 12px; }
        .wazuh-total-badge { margin-left: 0; }
    }
    </style>';
});
?>
				
			

// Wazuh SIEM Python Push Script

				
					import urllib.request
import json
import ssl
import base64

# --- 1. CONFIGURATION ---
WAZUH_URL = "https://INSERT_YOUR_WAZUH_IP:9200/wazuh-alerts*/_search"
WAZUH_USER = "INSERT_WAZUH_USERNAME"
WAZUH_PASS = "INSERT_WAZUH_PASSWORD"
WP_URL = "https://INSERT_YOUR_DOMAIN.com/wp-json/soc/v1/telemetry"
WP_SECRET = "INSERT_YOUR_SECRET_HERE"

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

# --- 2. EXPANDED TIME-SERIES QUERY ---
query_body = json.dumps({
    "size": 0,
    "aggs": {
        "top_severities": { "terms": { "field": "rule.level", "size": 5 } },
        "top_agents": { "terms": { "field": "agent.name", "size": 3 } },
        "top_mitre": { "terms": { "field": "rule.mitre.tactic", "size": 3 } },
        "top_events": { "terms": { "field": "rule.description", "size": 4 } },
        "top_nist": { "terms": { "field": "rule.nist_800_53", "size": 3 } },
        "top_groups": { "terms": { "field": "rule.groups", "size": 4 } },
        "top_locations": { "terms": { "field": "location", "size": 5 } },
        "timeline": {
            "filter": { "range": { "@timestamp": { "gte": "now-24h" } } },
            "aggs": {
                "hourly": {
                    "date_histogram": { "field": "@timestamp", "fixed_interval": "1h", "min_doc_count": 0 }
                }
            }
        }
    }
}).encode('utf-8')

# --- 3. RAW LOGS QUERY ---
raw_query_body = json.dumps({
    "size": 1000,
    "sort": [{"@timestamp": {"order": "desc"}}],
    "_source": ["@timestamp", "agent.name", "rule.level", "rule.description", "rule.mitre.tactic"],
    "query": { "range": { "@timestamp": { "gte": "now-24h" } } }
}).encode('utf-8')

auth_string = f"{WAZUH_USER}:{WAZUH_PASS}"
base64_auth = base64.b64encode(auth_string.encode('utf-8')).decode('utf-8')

req_local = urllib.request.Request(WAZUH_URL, data=query_body, method="POST")
req_local.add_header("Authorization", f"Basic {base64_auth}")
req_local.add_header("Content-Type", "application/json")

try:
    with urllib.request.urlopen(req_local, context=ctx) as response:
        data = json.loads(response.read().decode())
except Exception as e:
    print(f"Failed to query Wazuh: {e}")
    exit(1)

req_raw = urllib.request.Request(WAZUH_URL, data=raw_query_body, method="POST")
req_raw.add_header("Authorization", f"Basic {base64_auth}")
req_raw.add_header("Content-Type", "application/json")

try:
    with urllib.request.urlopen(req_raw, context=ctx) as response:
        raw_data = json.loads(response.read().decode())
except Exception as e:
    print(f"Failed to query raw logs: {e}")
    raw_data = {}

# --- 4. FORMAT THE MULTI-PART PAYLOAD ---
aggs = data.get('aggregations', {})
timeline_buckets = aggs.get('timeline', {}).get('hourly', {}).get('buckets', [])

raw_hits = raw_data.get('hits', {}).get('hits', [])
parsed_raw_logs = []
for hit in raw_hits:
    src = hit.get('_source', {})
    rule = src.get('rule', {})
    mitre = rule.get('mitre', {})
    tactic = mitre.get('tactic', ['None'])
    if isinstance(tactic, list):
        tactic = tactic[0] if len(tactic) > 0 else 'None'
        
    parsed_raw_logs.append({
        "time": src.get('@timestamp', ''),
        "agent": src.get('agent', {}).get('name', 'Unknown'),
        "level": rule.get('level', 0),
        "desc": rule.get('description', 'No description'),
        "mitre": tactic
    })

payload = {
    "total": data.get('hits', {}).get('total', {}).get('value', 0),
    "severities": [{"level": b['key'], "count": b['doc_count']} for b in aggs.get('top_severities', {}).get('buckets', [])],
    "agents": [{"name": b['key'], "count": b['doc_count']} for b in aggs.get('top_agents', {}).get('buckets', [])],
    "mitre": [{"tactic": b['key'], "count": b['doc_count']} for b in aggs.get('top_mitre', {}).get('buckets', [])],
    "events": [{"desc": b['key'], "count": b['doc_count']} for b in aggs.get('top_events', {}).get('buckets', [])],
    "nist": [{"control": b['key'], "count": b['doc_count']} for b in aggs.get('top_nist', {}).get('buckets', [])],
    "groups": [{"group": b['key'], "count": b['doc_count']} for b in aggs.get('top_groups', {}).get('buckets', [])],
    "locations": [{"loc": b['key'], "count": b['doc_count']} for b in aggs.get('top_locations', {}).get('buckets', [])],
    "timeline": [{"time": b['key'], "count": b['doc_count']} for b in timeline_buckets[-24:]],
    "raw_logs": parsed_raw_logs
}

# --- 5. PUSH TO WORDPRESS ---
req_remote = urllib.request.Request(WP_URL, data=json.dumps(payload).encode('utf-8'), method="POST")
req_remote.add_header("x-api-key", WP_SECRET)
req_remote.add_header("Content-Type", "application/json")
req_remote.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

try:
    with urllib.request.urlopen(req_remote) as response:
        print(f"Successfully pushed expanded data. Status: {response.status}")
except Exception as e:
    print(f"Failed to push to WordPress: {e}")
				
			

// Wazuh Code Formatting

				
					/* Force strict boundary boxing on all widget elements */
.wazuh-stats-widget, .wazuh-stats-widget * { 
    box-sizing: border-box !important; 
}

/* Master Widget Container */
.wazuh-stats-widget { 
    background: #ffffff; 
    border-radius: 16px; 
    padding: 24px; 
    width: 100%; 
    max-width: 100%; 
    box-shadow: 0 4px 24px rgba(0,0,0,0.04); 
    font-family: "Inter", sans-serif; 
    color: #1a1a1a; 
    margin-bottom: 20px;
}

/* Header Styling & Pulsing Blue Dot */
.wazuh-stats-widget h4 { 
    margin: 0 0 20px 0; 
    font-size: 1.1rem; 
    font-weight: 800; 
    letter-spacing: 0.02em; 
    text-transform: uppercase; 
    color: #1a1a1a !important; 
    display: flex; 
    align-items: center; 
    justify-content: space-between; 
    border-bottom: 2px solid #f3f4f6; 
    padding-bottom: 12px; 
}
.wazuh-stats-widget h4::before { 
    content: ""; 
    display: inline-block; 
    width: 10px; 
    height: 10px; 
    background-color: #009fe3; 
    border-radius: 50%; 
    margin-right: 10px; 
    box-shadow: 0 0 8px rgba(0, 159, 227, 0.6); 
    animation: wazuh-pulse 2s infinite; 
}
.wazuh-total-badge { 
    font-size: 0.75rem; 
    background: #e5e7eb; 
    padding: 4px 10px; 
    border-radius: 20px; 
    color: #4b5563; 
    font-weight: 700; 
    margin-left: auto; 
}
@keyframes wazuh-pulse { 
    0% { opacity: 1; } 
    50% { opacity: 0.5; } 
    100% { opacity: 1; } 
}

.wz-section-title { 
    margin: 0 0 10px 0; 
    font-size: 0.75rem; 
    color: #6b7280; 
    text-transform: uppercase; 
    font-weight: 700; 
    letter-spacing: 0.05em; 
}

/* Master Flex Layouts */
.wz-master-layout { 
    display: flex; 
    gap: 20px; 
    align-items: flex-start; 
    max-width: 100%; 
}
.wz-sidebar-severities { 
    width: 240px; 
    flex-shrink: 0; 
    background: #f4f5f7; 
    padding: 14px; 
    border-radius: 12px; 
}
.wz-main-grid { 
    flex: 1; 
    display: flex; 
    flex-direction: column; 
    gap: 16px; 
    min-width: 0; 
    max-width: 100%; 
}
.wz-sub-row { 
    display: flex; 
    gap: 16px; 
    width: 100%; 
    max-width: 100%; 
}
.wz-card { 
    flex: 1; 
    background: #f4f5f7; 
    padding: 14px; 
    border-radius: 12px; 
    min-width: 0; 
    max-width: 100%; 
}
.wz-full-card { 
    width: 100%; 
    max-width: 100%; 
}

/* Severity Stack Styling */
.wazuh-stats-widget ul.wz-severities-stack { 
    display: flex !important; 
    flex-direction: column !important; 
    gap: 8px !important; 
    list-style: none !important; 
    padding: 0 !important; 
    margin: 0 !important; 
}
.wz-severities-stack li { 
    display: flex !important; 
    justify-content: space-between !important; 
    align-items: center !important; 
    padding: 10px 12px !important; 
    background: #ffffff !important; 
    border-radius: 8px !important; 
    font-size: 0.8rem !important; 
    font-weight: 600 !important; 
    border-left: 4px solid #e5e7eb !important; 
    color: #1a1a1a !important; 
}
.wz-severities-stack li.wz-critical { border-left-color: #ef4444 !important; } 
.wz-severities-stack li.wz-high { border-left-color: #f97316 !important; } 
.wz-severities-stack li.wz-medium { border-left-color: #facc15 !important; } 
.wz-severities-stack li.wz-low { border-left-color: #009fe3 !important; }
.wz-severities-stack .wz-count { 
    background-color: #1a1a1a; 
    color: #ffffff; 
    padding: 2px 8px; 
    border-radius: 6px; 
    font-weight: 700; 
    font-size: 0.85rem; 
    width: 45px; 
    text-align: center; 
}

/* Text List Styling & Wrapping */
.wazuh-stats-widget ul.wz-text-list { 
    display: flex !important; 
    flex-direction: column !important; 
    gap: 0 !important; 
    list-style: none !important; 
    padding: 0 !important; 
    margin: 0 !important; 
    width: 100%; 
}
.wz-text-list li { 
    display: flex !important; 
    flex-direction: row !important; 
    justify-content: space-between !important; 
    align-items: center !important; 
    font-size: 0.8rem !important; 
    font-weight: 500 !important; 
    padding: 10px 0 !important; 
    border-bottom: 2px solid #e5e7eb !important; 
    background: transparent !important; 
    color: #1a1a1a !important; 
    width: 100%; 
}
.wz-text-list li:last-child { 
    border-bottom: none !important; 
    padding-bottom: 0 !important; 
}
.wz-text-list li > span:first-child { 
    flex: 1; 
    padding-right: 12px; 
    text-align: left; 
    word-break: break-word; 
    overflow-wrap: break-word; 
    white-space: normal; 
}
.wz-text-list li > span:last-child { 
    font-weight: 700 !important; 
    background: #ffffff !important; 
    padding: 4px 0 !important; 
    border-radius: 10px !important; 
    font-size: 0.75rem !important; 
    flex-shrink: 0 !important; 
    width: 60px !important; 
    text-align: center !important; 
    display: inline-block !important; 
    margin-left: auto !important; 
}

/* Mobile Responsiveness */
@media (max-width: 768px) {
    .wazuh-stats-widget { padding: 16px; }
    .wz-master-layout { flex-direction: column; }
    .wz-sidebar-severities { width: 100%; margin-bottom: 10px; }
    .wz-main-grid { width: 100%; }
    .wz-sub-row { flex-direction: column; gap: 16px; width: 100%; }
    .wazuh-stats-widget h4 { flex-direction: column; align-items: flex-start; gap: 12px; }
    .wazuh-total-badge { margin-left: 0; }
}