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

  1. Real-time Alert Viewing - Displays the most recent 50 Snort alerts with timestamp, signature, source IP, and destination IP
  2. Signature Distribution Visualization - Bar chart showing the top 10 most frequent alert signatures
  3. Responsive Design - Mobile-friendly CSS with modern styling
  4. Database Abstraction - Uses ADOdb for database portability
  5. Chart Generation - PHPlot integration for visual reporting
  6. Comprehensive Test Suite - PHPUnit tests for database operations and reporting logic
  7. Environment-based Configuration - Database credentials via environment variables
  8. 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:

  1. index.php - The main dashboard showing a table of recent alerts
  2. 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 configuration
define('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 libraries
define('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.

Alert Retrieval

File: apache/www/acid/db.php

<?php
require_once 'acid_config.php';
require_once ADODB_PATH . 'adodb.inc.php';
 
function getAlerts() {
    $db = ADONewConnection('mysqli');
    $db->Connect(DBHOST, DBUSER, DBPASS, DBNAME);
    $results = $db->Execute('SELECT timestamp, signature, src_ip, dst_ip FROM event ORDER BY timestamp DESC LIMIT 50');
    $alerts = [];
    while(!$results->EOF) {
        $alerts[] = $results->fields;
        $results->MoveNext();
    }
    return $alerts;
}
?>

How It Works

  1. Connection: Creates a new ADOdb connection using the MySQLi driver
  2. Query Execution: Executes a SELECT query ordering by timestamp descending, limiting to 50 most recent alerts
  3. Data Fetching: Iterates through the result set using ADOdb’s recordset methods (EOF, MoveNext)
  4. 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

<?php
require_once 'acid_conf.php';  // Note: typo in original - should be acid_config.php
require_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:

echo "<td>" . htmlspecialchars($row['signature']) . "</td>";

Signature Distribution Graph

File: apache/www/acid/report.php

<?php
require_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

  1. SQL Aggregation: Uses GROUP BY with COUNT(*) to aggregate alerts by signature
  2. Data Formatting: Converts results into PHPlot’s expected format: [[signature, count], ...]
  3. Empty State Handling: Provides fallback data if no alerts exist
  4. Chart Configuration: Sets title, axis labels, plot type (bars), and image dimensions
  5. 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.

Styling

File: apache/www/acid/style.css

body {
    font-family: 'Segoe UI', Arial, sans-serif;
    background: #f4f6fb;
    margin: 0;
    padding: 0;
}
 
h1 {
    color: #263859;
    text-align: center;
    margin: 24px 0 16px 0;
    font-size: 2.2em;
    font-weight: 700;
    letter-spacing: 1px;
}
 
table {
    background: #fff;
    margin: 0 auto 32px auto;
    width: 80%;
    border-collapse: collapse;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 2px 12px rgba(44,62,80,0.07);
}
 
th {
    background: #5161ce;
    color: #fff;
    font-weight: 600;
    border-bottom: 3px solid #3949ab;
}
 
tr:nth-child(even) {
    background: #f4f6fb;
}
 
tr:hover {
    background: #e9ecef;
    transition: background 0.18s;
}
 
@media (max-width: 900px) {
    table, th, td {
        font-size: 0.97em;
    }
    table {
        width: 97%;
    }
}

Design Features

  • 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.

PHPUnit Configuration

File: phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         colors="true"
         verbose="true"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="ATS">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>
    <php>
        <env name="DB_HOST" value="127.0.0.1"/>
        <env name="DB_USER" value="root"/>
        <env name="DB_PASS" value="root"/>
        <env name="DB_NAME" value="snort"/>
    </php>
</phpunit>

The configuration:

  • Uses Composer’s autoloader for bootstrap
  • Defines environment variables for test database credentials
  • Names the test suite “ATS” (possibly “Application Test Suite”)

Developer Notes

Potential Issues & Improvements

  1. Config File Typo: index.php and report.php reference acid_conf.php instead of acid_config.php
  2. XSS Vulnerability: User-controlled data (IP addresses, signatures) is output without escaping
  3. Error Handling: No try-catch blocks for database operations
  4. Missing Libraries: ADOdb and PHPlot are external dependencies not included in composer.json
  5. SQL Injection Risk: While ADOdb handles this, using prepared statements consistently is recommended

Recommendations for Production

  • Input Sanitization: Escape all output with htmlspecialchars()
  • Error Handling: Add try-catch blocks with user-friendly error messages
  • Logging: Implement proper logging for debugging
  • Authentication: Add login/authentication before accessing sensitive alert data
  • HTTPS: Ensure the dashboard is served over HTTPS
  • Rate Limiting: Consider rate limiting on the alert query endpoint
  • Caching: Implement caching for expensive aggregation queries

Configuration via Environment Variables

VariableDefaultDescription
DB_HOSTlocalhostDatabase host
DB_USERdashDatabase username
DB_PASSpasswordDatabase password
DB_NAMEdashdbDatabase name

Composer Configuration

File: composer.json

{
    "name": "x0prc/dash.snort",
    "description": "Snort IDS Web Analytics Console",
    "type": "project",
    "require": {
        "php": ">=7.4"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.5"
    },
    "autoload": {
        "psr-4": {
            "App\\": "apache/www/acid/"
        }
    }
}

Conclusion

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.