Modular Web Dashboard for Snort IDS Alert Analysis
Introduction
Network security monitoring is a critical component of any organization’s cybersecurity infrastructure. Snort IDS (Intrusion Detection System) is one of the most widely used open-source network intrusion detection systems that monitors network traffic in real-time, searching for suspicious patterns and generating alerts when potential threats are detected.
However, analyzing raw Snort alerts directly from the database can be challenging without a proper interface. That’s where Dash.Snort comes in—a modular, lightweight web dashboard designed to provide security analysts with an intuitive way to view and analyze Snort IDS alerts.
Features
Real-time Alert Viewing - Displays the most recent 50 Snort alerts with timestamp, signature, source IP, and destination IP
Signature Distribution Visualization - Bar chart showing the top 10 most frequent alert signatures
Responsive Design - Mobile-friendly CSS with modern styling
Database Abstraction - Uses ADOdb for database portability
Chart Generation - PHPlot integration for visual reporting
Comprehensive Test Suite - PHPUnit tests for database operations and reporting logic
Environment-based Configuration - Database credentials via environment variables
GitHub Actions CI - Automated testing on pull requests
Core Components
Database Layer
The database layer is handled through ADOdb, a powerful database abstraction library for PHP. This provides flexibility to switch between different database backends (MySQL, PostgreSQL, Oracle, etc.) with minimal code changes.
Database Schema
The application expects a snort database with an event table containing the following structure:
CREATE TABLE event ( sid INT UNSIGNED NOT NULL, -- Sensor ID cid INT UNSIGNED NOT NULL, -- Event ID timestamp DATETIME NOT NULL, -- Alert timestamp signature VARCHAR(255), -- Alert signature/classification src_ip VARCHAR(15), -- Source IP address dst_ip VARCHAR(15), -- Destination IP address PRIMARY KEY (sid, cid));
Developer Note: The schema assumes IPv4 addresses (VARCHAR(15)). For IPv6 support, you’d need to adjust to VARCHAR(45) and update the corresponding PHP code.
Web Interface
The web interface consists of two main pages:
index.php - The main dashboard showing a table of recent alerts
report.php - A visualization page with a bar chart of signature distribution
Both pages share a consistent design through style.css, which provides a modern, professional look with responsive behavior.
Reporting & Visualization
The reporting module uses PHPlot, a PHP graph library, to generate bar charts. The chart displays the top 10 alert signatures ranked by frequency, giving security analysts quick insight into which types of threats are most common in their network.
Deep Dive
Database Configuration
File: apache/www/acid/acid_config.php
<?php// Database configurationdefine('DBHOST', getenv('DB_HOST') ?: 'localhost');define('DBUSER', getenv('DB_USER') ?: 'dash');define('DBPASS', getenv('DB_PASS') ?: 'password');define('DBNAME', getenv('DB_NAME') ?: 'dashdb');// Make sure to set the correct paths for ADOdb and PHPlot librariesdefine('ADODB_PATH', '/usr/share/php/adodb/');define('PHPLOT_PATH', '/usr/share/php/phplot/');?>
Key Design Decisions
Environment Variables: The configuration uses getenv() with fallback defaults, allowing containerized deployments and CI/CD pipelines to inject credentials securely
Absolute Paths: ADOdb and PHPlot paths are absolute reflecting, their typical installation in system directories (/usr/share/php/)
Developer Note: In a production environment, never commit actual credentials. Consider using a .env file with a library like phpdotenv for local development, and ensure your production deployment uses secure secret management.
Connection: Creates a new ADOdb connection using the MySQLi driver
Query Execution: Executes a SELECT query ordering by timestamp descending, limiting to 50 most recent alerts
Data Fetching: Iterates through the result set using ADOdb’s recordset methods (EOF, MoveNext)
Return: Returns an associative array of alert records
Developer Note: ADOdb’s Execute() method returns a recordset object, not a simple array. This is why we iterate using EOF (End Of File) and MoveNext()—an older but reliable pattern. Modern PHP code might prefer PDO’s fetchAll() for simplicity.
Dashboard UI
File: apache/www/acid/index.php
<?phprequire_once 'acid_conf.php'; // Note: typo in original - should be acid_config.phprequire_once 'db.php';?><!DOCTYPE html><html><head> <title>Dash for Snort</title> <link rel="stylesheet" type="text/css" href="style.css"></head><body> <h1>Home</h1> <a href="report.php">View Reports</a> <table border="1"> <tr><th>Time</th><th>Signature</th><th>Source</th><th>Destination</th></tr> <?php $alerts = getAlerts(); foreach($alerts as $row) { echo "<tr>"; echo "<td>{$row['timestamp']}</td>"; echo "<td>{$row['signature']}</td>"; echo "<td>{$row['src_ip']}</td>"; echo "<td>{$row['dst_ip']}</td>"; echo "</tr>"; } ?> </table></body></html>
Template Structure
Header: Includes the stylesheet and page title
Navigation: Simple link to the reports page
Data Table: Iterates through alerts and renders table rows
PHP in HTML: Uses inline PHP for simplicity in this small application
Developer Note: There’s a typo in the original code—acid_conf.php should be acid_config.php. This would cause a fatal error. Additionally, in production code, you’d want to escape output using htmlspecialchars() to prevent XSS attacks:
<?phprequire_once 'acid_conf.php';require_once ADODB_PATH . 'adodb.inc.php';require_once PHPLOT_PATH . 'phplot.php';$db = ADONewConnection('mysqli');$db->Connect(DBHOST, DBUSER, DBPASS, DBNAME);// Get top 10 signatures (alert types) and their event count$sql = " SELECT signature, COUNT(*) AS cnt FROM event GROUP BY signature ORDER BY cnt DESC LIMIT 10";$results = $db->Execute($sql);// Build data array for PHPlot$data = [];while (!$results->EOF) { $data[] = [$results->fields['signature'], (int)$results->fields['cnt']]; $results->MoveNext();}if (empty($data)) { $data = [ ['No Data', 1] ];}$plot = new PHPlot(800, 400);$plot->SetDataType('text-data-single');$plot->SetDataValues($data);$plot->SetTitle('Top 10 Alert Signatures');$plot->SetXTitle('Signature');$plot->SetYTitle('Count');$plot->SetPlotType('bars');$plot->SetImageBorderType('plain');$plot->DrawGraph();?>
How the Chart Works
SQL Aggregation: Uses GROUP BY with COUNT(*) to aggregate alerts by signature
Data Formatting: Converts results into PHPlot’s expected format: [[signature, count], ...]
Empty State Handling: Provides fallback data if no alerts exist
Chart Configuration: Sets title, axis labels, plot type (bars), and image dimensions
Rendering: Calls DrawGraph() which outputs the image directly to the browser
Developer Note: PHPlot outputs an image directly with proper Content-Type headers. This means you can’t include PHPlot output in a larger HTML page easily—it’s typically served as a standalone image or embedded via an <img> tag pointing to this script.
Color Scheme: Professional indigo/blue palette (#5161ce primary)
Visual Hierarchy: Clear headers with hover states on rows
Responsive: Media query adjusts layout for mobile devices
Modern Touches: Rounded corners, subtle shadows, smooth transitions
Testing Suite
The project includes a comprehensive PHPUnit test suite with two main test classes:
Database Tests
File: tests/database_test.php
class DatabaseTest extends TestCase{ private $db; protected function setUp(): void { $host = getenv('DB_HOST') ?: '127.0.0.1'; $user = getenv('DB_USER') ?: 'root'; $pass = getenv('DB_PASS') ?: 'root'; $name = getenv('DB_NAME') ?: 'snort'; $this->db = new mysqli($host, $user, $pass, $name); if ($this->db->connect_error) { $this->fail("Database connection failed: " . $this->db->connect_error); } } public function testDatabaseConnection() { $this->assertInstanceOf(mysqli::class, $this->db); $this->assertEmpty($this->db->connect_error); } public function testEventTableExists() { $result = $this->db->query("SHOW TABLES LIKE 'event'"); $this->assertTrue($result !== false && $result->num_rows === 1, "Table 'event' does not exist."); } public function testInsertEvent() { $sid = rand(100,1000); $cid = rand(100,1000); // ... insert test ... } public function testDeleteEvent() { // ... delete test ... }}
Test Coverage
Connection Test: Verifies database connectivity
Schema Test: Confirms the event table exists
CRUD Tests: Tests insert and delete operations
Isolation: Uses random IDs to avoid conflicts between test runs
Report Tests
File: tests/report_test.php
class ReportTest extends TestCase{ // ... setup with table creation ... private function seedEvents(): void { $events = [ [1, 1, 'TCP Flood', '192.168.1.10', '10.0.0.1'], [1, 2, 'TCP Flood', '192.168.1.11', '10.0.0.1'], [1, 3, 'Port Scan', '192.168.1.12', '10.0.0.2'], // ... ]; // ... seeding logic ... } public function testReportAggregationQuery() { $this->seedEvents(); $sql = " SELECT signature, COUNT(*) AS cnt FROM event GROUP BY signature ORDER BY cnt DESC "; // ... assertions ... } public function testReportDataFormatForPlot() { // Verifies data format matches PHPlot expectations } public function testEmptyDatabaseFallback() { // Tests behavior with no data }}
Test Scenarios
Aggregation Query: Validates SQL GROUP BY produces correct counts
Data Format: Ensures data is structured correctly for PHPlot consumption
Edge Cases: Tests empty database handling
Developer Note: The seedEvents() method uses prepared statements with bind_param(), which is excellent for security. The test suite demonstrates good practices: setup/teardown lifecycle methods, test data seeding, and proper assertion messages.
dash.snort provides a solid foundation for visualizing Snort IDS alerts. Its modular architecture, comprehensive test suite, and clean codebase make it an excellent starting point for building a network security monitoring dashboard. While there are areas for improvement (notably security hardening and dependency management), the project demonstrates good practices in PHP application structure and testing.
For organizations looking to modernize their intrusion detection analysis workflow, Dash.Snort offers a lightweight alternative to heavier SIEM solutions, perfect for smaller environments or as a proof-of-concept for larger deployments.