SEAS-8414 Chapter 3 Doctoral Teaching Notebook¶
Detective Analytics for Vulnerability Assessment, Protocol Security, and AI-Assisted Triage¶
This notebook is designed as a Chapter 3 teaching artifact for experienced cybersecurity practitioners. Each section follows the same pattern:
- Explain the concept and how it works internally.
- Use a real public dataset or a real local tool.
- Demonstrate the mechanism with executable Python.
- Interpret what the result teaches before moving to the next concept.
The notebook intentionally separates three epistemic categories:
- Measured: produced by code in this notebook from a live public source, local file, or local tool.
- Simulated: a controlled local lab example used because executing against third-party systems would be unsafe or unauthorized.
- Design pattern: how commercial platforms generally implement the capability, based on public documentation and product patterns.
Safety rule: scanner and credential-checking execution is restricted to local loopback targets created inside this notebook. Public Internet resources are used only as datasets, documentation, templates, or firmware files.
Audio explanation for original cell 01
Transcript: This opening cell frames the notebook as the master Chapter 3 teaching artifact. It teaches that vulnerability work is an evidence pipeline, not a tool demo, and it sets the rule that measured, simulated, and design-pattern claims must stay separate.
AI mentor analysis for original cell 01: Introduction to SEAS-8414 Chapter 3
This introductory markdown cell outlines the structure and purpose of the SEAS-8414 Chapter 3 notebook. It emphasizes the focus on detective analytics for vulnerability assessment, protocol security, and AI-assisted triage. The notebook is designed for experienced cybersecurity practitioners and follows a consistent pattern: explaining concepts, using real datasets or tools, demonstrating mechanisms with Python, and interpreting results. It categorizes evidence into measured, simulated, and design patterns, ensuring safe execution by restricting scanner and credential-checking to local targets.
Discussion question: How does categorizing evidence into measured, simulated, and design patterns enhance the learning experience in cybersecurity education?
Source Contract and Teaching Map¶
| Topic | Concept taught | Real dataset or tool used | Execution status |
|---|---|---|---|
| Detective analytics | Evidence fusion from CVE, KEV, exploit likelihood, and asset context | CISA KEV JSON, NVD API, FIRST EPSS API | Live public feeds |
| CPE-to-CVE pipeline | Banner normalization, CPE selection, NVD CVE correlation, CVSS interpretation | NVD CPE and CVE API | Live public API |
| AutoML for triage | Supervised severity classification and model selection | NVD CVE descriptions labeled by CVSS severity | Live public API |
| OpenVAS/GVM | Scanner architecture, GMP XML workflows, NASL vulnerability tests | Greenbone/OpenVAS public source tree, local XML examples | Public repo plus simulated GMP contract |
| Nuclei | YAML template internals, matchers, metadata, safe execution | ProjectDiscovery nuclei-templates repository, local nuclei binary | Public templates plus local target |
| Default credentials | Credential evidence loops, false-positive controls, lockout safety | SecLists default credential dataset, local HTTP Basic server | Public dataset plus local target |
| Firmware integrity | Hash validation, vendor manifest matching, entropy anomaly analysis | OpenWrt firmware and sha256sums | Live public files |
| Protocol grammar inference | PCAP fields, frame geometry, byte-position profiles | Wireshark DHCP sample PCAP, local tshark | Public PCAP plus local tool |
| LLM-guided mutation fuzzing | Parser-state feedback, semantic mutation, RL operator selection | Local toy parser and deterministic mutator | Simulated safe target |
| Transformers, GenAI, graphs, AR-ready data | Self-attention over protocol tokens and evidence graph export | NumPy, NetworkX, local JSON export | Local computation |
Reference URLs used by the notebook and the commercial-platform discussion:
- NVD API: https://nvd.nist.gov/developers/vulnerabilities
- NVD CPE API: https://nvd.nist.gov/developers/products
- CISA KEV catalog: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- FIRST EPSS API: https://www.first.org/epss/api
- ProjectDiscovery nuclei templates: https://github.com/projectdiscovery/nuclei-templates
- ProjectDiscovery template syntax: https://docs.projectdiscovery.io/templates/introduction
- Greenbone/OpenVAS scanner source: https://github.com/greenbone/openvas-scanner
- Greenbone community documentation: https://greenbone.github.io/docs/latest/
- SecLists default credentials: https://github.com/danielmiessler/SecLists
- Wireshark sample captures: https://gitlab.com/wireshark/wireshark/-/wikis/SampleCaptures
- OpenWrt downloads: https://downloads.openwrt.org/
- Microsoft Defender for IoT: https://learn.microsoft.com/en-us/azure/defender-for-iot/
- Tenable OT Security docs: https://docs.tenable.com/OT-security/
- Armis platform pages: https://www.armis.com/platform/
- Claroty xDome pages: https://www.claroty.com/products/xdome
Audio explanation for original cell 02
Transcript: This source contract cell tells students where every dataset and tool comes from. It teaches source discipline: NVD, CISA KEV, EPSS, Nuclei templates, SecLists, OpenWrt firmware, Wireshark PCAPs, and commercial product references are explicitly named before analysis begins.
AI mentor analysis for original cell 02: Source Contract and Teaching Map Overview
This markdown cell provides a structured overview of the topics covered in the notebook, detailing the concept taught, the dataset or tool used, and the execution status. It highlights the use of live public feeds and APIs for detective analytics, CPE-to-CVE pipelines, and AutoML for triage. It also mentions the use of public repositories and local targets for tools like OpenVAS/GVM and Nuclei. This map serves as a guide for practitioners to understand the scope and resources involved in each section, ensuring clarity and focus on practical applications.
Discussion question: Why is it important to use both live public datasets and local simulations in cybersecurity training?
Mermaid concept diagram before original cell 03: Lab Environment Architecture
This diagram explains the runtime boundaries before students run setup cells.
0. Lab Setup¶
This cell installs small Python dependencies only when they are missing. The heavy external tools, tshark and nuclei, are expected to be installed locally. On this machine they are installed through Homebrew and discovered by shutil.which.
The notebook uses a cache directory under ~/Downloads/seas8414_ch03_master_teaching so repeated teaching runs do not hammer public APIs.
Audio explanation for original cell 03
Transcript: This setup explanation prepares the lab environment. It teaches that repeatable security analytics starts with controlled dependencies, local tool discovery, and a cache directory so that teaching runs do not depend on one fragile live request.
AI mentor analysis for original cell 03: Lab Setup Explanation
This markdown cell explains the lab setup process, focusing on the installation of Python dependencies and the expectation that external tools like `tshark` and `nuclei` are pre-installed locally. It mentions the use of a cache directory to prevent excessive API calls during repeated teaching runs. This setup ensures that the notebook can be executed smoothly without unnecessary delays, while also respecting the rate limits of public APIs. The setup process is crucial for maintaining an efficient and effective learning environment.
Discussion question: What are the benefits of using a cache directory in a teaching notebook that interacts with public APIs?
import sys
import subprocess
import pkgutil
required_modules = {
"pandas": "pandas",
"numpy": "numpy",
"requests": "requests",
"sklearn": "scikit-learn",
"yaml": "pyyaml",
"networkx": "networkx",
"matplotlib": "matplotlib",
"plotly": "plotly",
"bs4": "beautifulsoup4",
"lxml": "lxml",
}
missing = [pkg for module, pkg in required_modules.items() if pkgutil.find_loader(module) is None]
if missing:
print(f"Installing missing packages: {missing}")
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *missing])
else:
print("All Python dependencies already available.")
All Python dependencies already available.
Audio explanation for original cell 04
Transcript: This code cell installs only the small Python packages that are missing. It teaches a pragmatic lab pattern: keep setup minimal, avoid changing the repository, and make the notebook runnable on a clean teaching machine.
AI mentor analysis for original cell 04: Python Dependency Check and Installation
This code cell checks for the presence of essential Python modules required for the notebook's execution. It uses `pkgutil.find_loader` to identify missing packages and installs them using `pip` if necessary. This ensures that all dependencies are available, preventing runtime errors and facilitating a seamless learning experience. By automating the dependency management process, the notebook reduces setup time and allows practitioners to focus on the core cybersecurity concepts being taught.
Discussion question: How does automating dependency management improve the usability of educational notebooks in cybersecurity?
import base64
import csv
import hashlib
import http.server
import io
import json
import math
import os
import random
import re
import shutil
import socket
import socketserver
import struct
import subprocess
import tempfile
import threading
import time
import urllib.parse
import xml.etree.ElementTree as ET
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import pandas as pd
import requests
import yaml
from bs4 import BeautifulSoup
from IPython.display import Markdown, display
from sklearn.cluster import KMeans
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, silhouette_score
from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.svm import LinearSVC
pd.set_option("display.max_columns", 30)
pd.set_option("display.max_colwidth", 140)
random.seed(7)
np.random.seed(7)
BASE = Path.home() / "Downloads" / "seas8414_ch03_master_teaching"
DATA = BASE / "data"
CACHE = BASE / "cache"
OUT = BASE / "outputs"
for directory in (DATA, CACHE, OUT):
directory.mkdir(parents=True, exist_ok=True)
ENABLE_LIVE_OPENAI_CALLS = os.environ.get("ENABLE_LIVE_OPENAI_CALLS", "false").lower() == "true"
LAB_ALLOW_EXTERNAL_TARGETS = False
NVD_API_KEY = os.environ.get("NVD_API_KEY", "")
print(f"Working directory: {BASE}")
print(f"tshark: {shutil.which('tshark')}")
print(f"nuclei: {shutil.which('nuclei')}")
print(f"Live OpenAI calls enabled: {ENABLE_LIVE_OPENAI_CALLS}")
print(f"External scanner targets enabled: {LAB_ALLOW_EXTERNAL_TARGETS}")
Working directory: notebook-runtime/seas8414_ch03_master_teaching tshark: /opt/homebrew/bin/tshark nuclei: /opt/homebrew/bin/nuclei Live OpenAI calls enabled: False External scanner targets enabled: False
Audio explanation for original cell 05
Transcript: This code cell imports the analytics, network, plotting, parsing, and machine-learning libraries, then defines output directories and safety flags. It teaches that scanner demonstrations should default to local-only execution unless external scope is explicitly authorized.
Plotly visual analytics
This Plotly cell adds an interactive visual analytics view for the preceding evidence section.
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = "notebook_connected"
print("Plotly visual analytics enabled.")
Plotly visual analytics enabled.
AI mentor analysis for original cell 05: Importing Libraries and Setting Up Environment
This code cell imports a comprehensive set of Python libraries and sets up the environment for the notebook. It includes libraries for data manipulation, machine learning, visualization, and network analysis. The cell also configures directories for data, cache, and outputs, ensuring organized storage of resources. Environment variables control the use of live OpenAI calls and external targets, maintaining a controlled and secure execution environment. This setup is essential for executing the various analytical tasks covered in the notebook.
Discussion question: Why is it important to configure environment variables and directories in a cybersecurity notebook?
Mermaid concept diagram before original cell 06: Source and Cache Contract
Use this diagram to explain why reliable vulnerability analytics needs source control, caching, and evidence preservation.
0.1 Shared Dataset Utilities¶
The utility layer below is part of the lesson. Production scanners have to solve the same basic problems:
- Respect rate limits and authentication requirements.
- Cache source data for repeatability.
- Preserve raw evidence so a finding can be audited later.
- Parse nested security data without flattening away the details that matter.
NVD enforces rate limits. This notebook adds a conservative delay when no NVD_API_KEY is provided.
Audio explanation for original cell 06
Transcript: This markdown cell introduces the shared data utility layer. It teaches that production scanners must cache evidence, respect source rate limits, preserve raw data, and parse nested vulnerability records without losing audit context.
AI mentor analysis for original cell 06: Shared Dataset Utilities Explanation
This markdown cell describes the utility layer used for handling datasets in the notebook. It highlights key challenges faced by production scanners, such as respecting rate limits, caching data, preserving raw evidence, and parsing complex security data. The notebook addresses these challenges by implementing caching and rate-limiting strategies, particularly for the NVD API. This utility layer ensures that data retrieval is efficient and reliable, allowing practitioners to focus on analyzing and interpreting security evidence.
Discussion question: What are the advantages of implementing caching and rate-limiting strategies in cybersecurity data retrieval?
SOURCE_EVENTS: list[dict[str, Any]] = []
_LAST_NVD_REQUEST = 0.0
def _cache_key(url: str, params: dict[str, Any] | None = None) -> str:
payload = json.dumps({"url": url, "params": params or {}}, sort_keys=True)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def get_json(url: str, params: dict[str, Any] | None = None, *, nvd: bool = False, cache_seconds: int = 24 * 3600) -> dict[str, Any]:
"""Fetch JSON with disk caching and conservative NVD rate limiting."""
global _LAST_NVD_REQUEST
key = _cache_key(url, params)
cache_path = CACHE / f"{key}.json"
if cache_path.exists() and time.time() - cache_path.stat().st_mtime < cache_seconds:
data = json.loads(cache_path.read_text())
SOURCE_EVENTS.append({"url": url, "params": params or {}, "source": "cache"})
return data
if nvd:
delay = 1.2 if NVD_API_KEY else 6.2
elapsed = time.time() - _LAST_NVD_REQUEST
if elapsed < delay:
time.sleep(delay - elapsed)
headers = {"User-Agent": "SEAS-8414-teaching-notebook/1.0"}
if nvd and NVD_API_KEY:
headers["apiKey"] = NVD_API_KEY
response = requests.get(url, params=params, headers=headers, timeout=60)
if nvd:
_LAST_NVD_REQUEST = time.time()
response.raise_for_status()
data = response.json()
cache_path.write_text(json.dumps(data, indent=2))
SOURCE_EVENTS.append({"url": response.url, "params": params or {}, "source": "network"})
return data
def get_bytes(url: str, name: str, *, cache_seconds: int = 7 * 24 * 3600) -> bytes:
cache_path = DATA / name
if cache_path.exists() and time.time() - cache_path.stat().st_mtime < cache_seconds:
SOURCE_EVENTS.append({"url": url, "file": str(cache_path), "source": "cache"})
return cache_path.read_bytes()
response = requests.get(url, headers={"User-Agent": "SEAS-8414-teaching-notebook/1.0"}, timeout=120)
response.raise_for_status()
cache_path.write_bytes(response.content)
SOURCE_EVENTS.append({"url": response.url, "file": str(cache_path), "source": "network"})
return response.content
def extract_cpes(configurations: list[dict[str, Any]]) -> list[str]:
cpes: list[str] = []
def walk_node(node: dict[str, Any]) -> None:
for match in node.get("cpeMatch", []) or []:
criteria = match.get("criteria")
if criteria:
cpes.append(criteria)
for child in node.get("children", []) or []:
walk_node(child)
for config in configurations or []:
for node in config.get("nodes", []) or []:
walk_node(node)
return sorted(set(cpes))
def flatten_nvd_item(item: dict[str, Any]) -> dict[str, Any]:
cve = item.get("cve", {})
descriptions = cve.get("descriptions", [])
description = next((d.get("value", "") for d in descriptions if d.get("lang") == "en"), "")
metrics = cve.get("metrics", {}) or {}
metric_record = None
metric_key = None
for candidate in ("cvssMetricV31", "cvssMetricV30", "cvssMetricV2"):
if metrics.get(candidate):
metric_record = metrics[candidate][0]
metric_key = candidate
break
cvss = (metric_record or {}).get("cvssData", {})
cpes = extract_cpes(cve.get("configurations", []))
return {
"cve_id": cve.get("id"),
"published": cve.get("published"),
"last_modified": cve.get("lastModified"),
"description": description,
"metric_key": metric_key,
"base_score": cvss.get("baseScore"),
"base_severity": (metric_record or {}).get("baseSeverity") or cvss.get("baseSeverity"),
"cvss_vector": cvss.get("vectorString"),
"affected_cpe_count": len(cpes),
"affected_cpes_sample": cpes[:5],
"weaknesses": [d.get("value") for w in cve.get("weaknesses", []) for d in w.get("description", []) if d.get("lang") == "en"],
}
def show_source_events(limit: int = 10) -> pd.DataFrame:
return pd.DataFrame(SOURCE_EVENTS[-limit:])
Audio explanation for original cell 07
Transcript: This utility code implements cached JSON and byte downloads, conservative NVD pacing, CPE extraction, and NVD CVE flattening. It teaches the plumbing behind reliable vulnerability evidence collection and repeatable classroom execution.
AI mentor analysis for original cell 07: Utility Functions for Data Retrieval and Caching
This code cell defines utility functions for fetching JSON data and bytes with disk caching and rate limiting. The `get_json` function handles API requests, caching responses to minimize redundant network calls and applying delays to respect NVD rate limits. The `get_bytes` function retrieves binary data, also utilizing caching. These functions ensure efficient data access and compliance with API usage policies, which is crucial for maintaining the integrity and reliability of the notebook's analytical processes.
Discussion question: How does caching contribute to the efficiency and reliability of data retrieval in cybersecurity analysis?
Mermaid concept diagram before original cell 08: Detective Analytics Pipeline
This diagram frames vulnerability scanning as evidence-backed reasoning instead of magic tool output.
1. Detective Analytics for Vulnerability Assessment¶
Concept¶
Detective analytics turns raw observations into evidence-backed security judgments. In vulnerability management, an observation might be a banner string, a package version, a protocol field, a firmware hash, a passive network fingerprint, an authenticated package inventory, or a crash signal from a fuzzing loop.
A vulnerability scanner does not magically "know" that a system is vulnerable. It usually executes one or more of these internal methods:
- Version and package correlation: map installed software, CPEs, package manager metadata, or firmware versions to CVEs.
- Configuration checks: compare observed settings to hardening rules or known insecure defaults.
- Proof checks: send a safe request that reveals a vulnerable behavior without exploiting impact.
- Patch presence checks: inspect local package state, registry state, kernel module versions, or vendor advisory identifiers.
- Behavioral or anomaly checks: identify suspicious protocol behavior, entropy changes, traffic clusters, exploit preconditions, or unusual response timing.
The stack depth matters. A kernel vulnerability may require kernel build, module, syscall, driver, or interrupt-handler evidence. An embedded firmware issue may require bootloader, kernel image, filesystem, package manifest, and hardware target context. An application CVE may be detectable from an HTTP header, but that is the weakest evidence class.
Zero-days and APT tradecraft break pure signature logic. Modern detection combines signatures, exploit intelligence, anomaly models, reachability analysis, SBOM/package inventories, and environmental context.
Audio explanation for original cell 08
Transcript: This detective analytics concept cell explains how scanners turn observations into findings. It teaches the difference between version correlation, configuration checks, proof checks, patch evidence, behavioral analytics, and deeper firmware or kernel evidence.
AI mentor analysis for original cell 08: Detective Analytics for Vulnerability Assessment
This markdown cell introduces the concept of detective analytics in vulnerability assessment. It explains how raw observations are transformed into evidence-backed security judgments through various methods, such as version correlation, configuration checks, and behavioral analysis. The cell emphasizes the importance of understanding the stack depth and context when assessing vulnerabilities. This foundational knowledge is critical for practitioners to accurately identify and prioritize vulnerabilities in complex systems.
Discussion question: Why is it important to consider stack depth and context in vulnerability assessment?
CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
kev_json = get_json(CISA_KEV_URL, cache_seconds=12 * 3600)
kev_df = pd.DataFrame(kev_json.get("vulnerabilities", []))
kev_df["dateAdded"] = pd.to_datetime(kev_df["dateAdded"], errors="coerce")
kev_df["dueDate"] = pd.to_datetime(kev_df["dueDate"], errors="coerce")
print(f"CISA KEV records loaded: {len(kev_df):,}")
display(kev_df[["cveID", "vendorProject", "product", "vulnerabilityName", "dateAdded", "knownRansomwareCampaignUse"]].tail(8))
CISA KEV records loaded: 1,606
| cveID | vendorProject | product | vulnerabilityName | dateAdded | knownRansomwareCampaignUse | |
|---|---|---|---|---|---|---|
| 1598 | CVE-2020-25213 | WordPress | File Manager Plugin | WordPress File Manager Plugin Remote Code Execution Vulnerability | 2021-11-03 | Unknown |
| 1599 | CVE-2020-11738 | WordPress | Snap Creek Duplicator Plugin | WordPress Snap Creek Duplicator Plugin File Download Vulnerability | 2021-11-03 | Unknown |
| 1600 | CVE-2019-9978 | WordPress | Social Warfare Plugin | WordPress Social Warfare Plugin Cross-Site Scripting (XSS) Vulnerability | 2021-11-03 | Unknown |
| 1601 | CVE-2021-27561 | Yealink | Device Management | Yealink Device Management Server-Side Request Forgery (SSRF) Vulnerability | 2021-11-03 | Unknown |
| 1602 | CVE-2021-40539 | Zoho | ManageEngine | Zoho ManageEngine ADSelfService Plus Authentication Bypass Vulnerability | 2021-11-03 | Known |
| 1603 | CVE-2020-10189 | Zoho | ManageEngine | Zoho ManageEngine Desktop Central File Upload Vulnerability | 2021-11-03 | Unknown |
| 1604 | CVE-2019-8394 | Zoho | ManageEngine | Zoho ManageEngine ServiceDesk Plus (SDP) File Upload Vulnerability | 2021-11-03 | Unknown |
| 1605 | CVE-2020-29583 | Zyxel | Multiple Products | Zyxel Multiple Products Use of Hard-Coded Credentials Vulnerability | 2021-11-03 | Unknown |
Audio explanation for original cell 09
Transcript: This code cell loads the live CISA Known Exploited Vulnerabilities catalog and displays recent exploited CVEs. It teaches that KEV is exploitation intelligence, not scanner output, and that it should enrich prioritization.
AI mentor analysis for original cell 09: Loading CISA Known Exploited Vulnerabilities
This code cell retrieves and processes the CISA Known Exploited Vulnerabilities (KEV) dataset. It uses the `get_json` utility function to fetch the data and loads it into a Pandas DataFrame for analysis. The cell converts date fields to datetime objects and displays a sample of the dataset, providing insights into recent vulnerabilities and their exploitation status. This exercise demonstrates how to handle and analyze real-world vulnerability data, a crucial skill for cybersecurity practitioners.
Discussion question: What insights can be gained from analyzing the CISA Known Exploited Vulnerabilities dataset?
kev_vendor_summary = (
kev_df.groupby("vendorProject", dropna=False)
.agg(cve_count=("cveID", "count"), latest_added=("dateAdded", "max"))
.sort_values(["cve_count", "latest_added"], ascending=[False, False])
.head(12)
)
print("Top vendors/projects by KEV catalog count:")
display(kev_vendor_summary)
kev_ransomware_counts = kev_df["knownRansomwareCampaignUse"].fillna("Unknown").value_counts().rename_axis("known_ransomware_use").to_frame("count")
display(kev_ransomware_counts)
Top vendors/projects by KEV catalog count:
| cve_count | latest_added | |
|---|---|---|
| vendorProject | ||
| Microsoft | 377 | 2026-05-20 |
| Apple | 93 | 2026-03-20 |
| Cisco | 90 | 2026-05-14 |
| Adobe | 79 | 2026-05-20 |
| 71 | 2026-04-01 | |
| Oracle | 42 | 2025-11-21 |
| Apache | 39 | 2026-04-16 |
| Ivanti | 34 | 2026-05-07 |
| D-Link | 26 | 2026-04-24 |
| Fortinet | 26 | 2026-04-13 |
| VMware | 26 | 2025-03-04 |
| Linux | 25 | 2026-05-01 |
| count | |
|---|---|
| known_ransomware_use | |
| Unknown | 1281 |
| Known | 325 |
Audio explanation for original cell 10
Transcript: This code summarizes KEV by vendor and ransomware usage. It teaches how raw threat-intelligence records become operational views that help prioritize remediation and explain exposure to decision makers.
AI mentor analysis for original cell 10: Analyzing Vendor and Ransomware Data from KEV
This code cell performs aggregation and analysis on the KEV dataset to summarize vulnerabilities by vendor and assess known ransomware campaign usage. It groups data by vendor, counts CVEs, and identifies the latest additions, highlighting vendors with the most entries. It also categorizes vulnerabilities by their association with ransomware, providing a count of known and unknown cases. This analysis helps practitioners understand the distribution of vulnerabilities across vendors and the potential ransomware risks, aiding in prioritization and response planning.
Discussion question: How can analyzing vendor-specific vulnerability data inform security strategy and risk management?
Plotly visual analytics
This Plotly cell visualizes KEV concentration and ransomware-use mix so students can see exploitation intelligence as a distribution, not only a table.
# Plotly visual analytics: KEV vendor concentration and ransomware-use mix.
top_kev_vendors = kev_vendor_summary.reset_index().head(12)
fig = px.bar(
top_kev_vendors,
x="cve_count",
y="vendorProject",
orientation="h",
color="cve_count",
title="CISA KEV Concentration by Vendor/Project",
labels={"cve_count": "KEV CVE count", "vendorProject": "Vendor/project"},
)
fig.update_layout(yaxis={"categoryorder": "total ascending"}, height=520)
fig.show()
ransomware_mix = kev_ransomware_counts.reset_index()
fig = px.pie(
ransomware_mix,
names="known_ransomware_use",
values="count",
title="KEV Records by Known Ransomware Campaign Use",
hole=0.45,
)
fig.show()
What the KEV cell teaches¶
CISA KEV is not a vulnerability scanner. It is an exploited-in-the-wild signal. A mature detector uses KEV as a prioritization feature, not as the sole detection method. The internal workflow is:
- Discover asset and software evidence.
- Map evidence to a vulnerability identifier.
- Enrich the identifier with exploitation signals such as KEV and EPSS.
- Add reachability, exposure, business criticality, and compensating controls.
- Produce an action decision.
This is the detective-analytics pattern repeated throughout the rest of the notebook.
Audio explanation for original cell 11
Transcript: This interpretation cell explains why KEV must be fused with asset and CVE evidence. It teaches the detective analytics sequence: discover evidence, map to identifiers, enrich with intelligence, add asset context, and decide action.
AI mentor analysis for original cell 11: Understanding the KEV Cell
This cell explains the role of CISA's Known Exploited Vulnerabilities (KEV) list in vulnerability management. KEV is not a scanner but a signal indicating active exploitation. It is used to prioritize vulnerabilities by integrating it with other factors like asset discovery, vulnerability mapping, and exploitation signals. The workflow described is a detective-analytics pattern, emphasizing the importance of combining multiple data sources and signals to make informed action decisions. This approach highlights the need for a comprehensive view of vulnerabilities beyond just detection.
Discussion question: How can KEV signals be effectively integrated into existing vulnerability management workflows?
Mermaid concept diagram before original cell 12: CPE to CVE Correlation
Use this diagram before the NVD CPE cells to show the internal scanner workflow.
2. CPE-to-CVE Pipeline¶
Concept¶
CPE-to-CVE correlation is the backbone of traditional vulnerability scanning. The internal pipeline usually looks like this:
- Collect evidence: banner, package inventory, authenticated command output, SBOM, firmware manifest, or passive fingerprint.
- Normalize evidence: vendor names, product names, major/minor versions, editions, update tags, and target hardware.
- Resolve CPE candidates: use exact rules where possible and fuzzy matching only when evidence is weak.
- Query CVE records: identify CVEs whose affected configurations match the selected CPE.
- Interpret applicability: evaluate version ranges, platform constraints, vendor advisories, patch status, and known exceptions.
- Score and prioritize: combine CVSS, EPSS, KEV, exposure, exploitability, and asset value.
The failure mode is false precision. A banner such as OpenSSH_8.4 is not enough to prove vulnerability if a distribution backported a patch without changing the upstream version string. Authenticated evidence is stronger than unauthenticated banner evidence.
Audio explanation for original cell 12
Transcript: This CPE-to-CVE concept cell explains the backbone of traditional vulnerability scanning. It teaches the internal pipeline from evidence collection and normalization to CPE candidate resolution, CVE lookup, applicability checks, and prioritization.
AI mentor analysis for original cell 12: CPE-to-CVE Pipeline Mechanics
This cell outlines the CPE-to-CVE correlation process, a fundamental aspect of vulnerability scanning. It describes how evidence is collected, normalized, and resolved into CPEs, which are then used to query CVE records. The process involves evaluating applicability and prioritizing based on various factors like CVSS scores and asset value. The cell warns against false precision, emphasizing the importance of authenticated evidence over unauthenticated banner evidence. This highlights the challenges in accurately identifying vulnerabilities and the need for robust evidence collection methods.
Discussion question: What are the limitations of using unauthenticated evidence in vulnerability scanning?
Mermaid concept diagram before original cell 13: CPE Candidate Resolution
This diagram explains the specific API lookup performed by the next code cell.
NVD_CPE_URL = "https://services.nvd.nist.gov/rest/json/cpes/2.0"
NVD_CVE_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def search_cpes(keyword: str, limit: int = 8) -> pd.DataFrame:
data = get_json(NVD_CPE_URL, params={"keywordSearch": keyword, "resultsPerPage": limit}, nvd=True)
rows = []
for product in data.get("products", []):
cpe = product.get("cpe", {})
rows.append({
"cpe_name": cpe.get("cpeName"),
"title": next((t.get("title") for t in cpe.get("titles", []) if t.get("lang") == "en"), None),
"deprecated": cpe.get("deprecated"),
"last_modified": cpe.get("lastModified"),
})
return pd.DataFrame(rows)
openssh_cpes = search_cpes("OpenSSH 8.4", limit=10)
openwrt_cpes = search_cpes("OpenWrt 23.05", limit=10)
print("OpenSSH CPE candidates:")
display(openssh_cpes)
print("OpenWrt CPE candidates:")
display(openwrt_cpes)
OpenSSH CPE candidates:
| cpe_name | title | deprecated | last_modified | |
|---|---|---|---|---|
| 0 | cpe:2.3:a:openbsd:openssh:8.4:-:*:*:*:*:*:* | OpenBSD OpenSSH 8.4 | False | 2021-02-11T16:18:59.657 |
| 1 | cpe:2.3:a:openbsd:openssh:8.4:p1:*:*:*:*:*:* | OpenBSD OpenSSH 8.4 p1 | False | 2022-08-04T15:08:27.340 |
OpenWrt CPE candidates:
| cpe_name | title | deprecated | last_modified | |
|---|---|---|---|---|
| 0 | cpe:2.3:o:openwrt:openwrt:23.05.0:-:*:*:*:*:*:* | OpenWrt 23.05.0 | False | 2025-10-28T20:10:46.350 |
| 1 | cpe:2.3:o:openwrt:openwrt:23.05.0:rc1:*:*:*:*:*:* | OpenWrt 23.05.0 Release Candidate 1 | False | 2025-10-28T20:10:46.350 |
| 2 | cpe:2.3:o:openwrt:openwrt:23.05.0:rc2:*:*:*:*:*:* | OpenWrt 23.05.0 Release Candidate 2 | False | 2025-10-28T20:10:46.350 |
| 3 | cpe:2.3:o:openwrt:openwrt:23.05.0:rc3:*:*:*:*:*:* | OpenWrt 23.05.0 Release Candidate 3 | False | 2025-10-28T20:10:46.350 |
| 4 | cpe:2.3:o:openwrt:openwrt:23.05.0:rc4:*:*:*:*:*:* | OpenWrt 23.05.0 Release Candidate 4 | False | 2025-10-28T20:10:46.350 |
| 5 | cpe:2.3:o:openwrt:openwrt:23.05.1:*:*:*:*:*:*:* | OpenWrt 23.05.1 | False | 2025-10-28T20:10:46.350 |
| 6 | cpe:2.3:o:openwrt:openwrt:23.05.2:*:*:*:*:*:*:* | OpenWrt 23.05.2 | False | 2025-10-28T20:10:46.350 |
| 7 | cpe:2.3:o:openwrt:openwrt:23.05.3:*:*:*:*:*:*:* | OpenWrt 23.05.3 | False | 2025-10-28T20:10:46.350 |
| 8 | cpe:2.3:o:openwrt:openwrt:23.05.4:*:*:*:*:*:*:* | OpenWrt 23.05.4 | False | 2025-10-28T20:10:46.350 |
| 9 | cpe:2.3:o:openwrt:openwrt:23.05.5:*:*:*:*:*:*:* | OpenWrt 23.05.5 | False | 2025-10-28T20:10:46.350 |
Audio explanation for original cell 13
Transcript: This code cell searches the live NVD CPE API for OpenSSH and OpenWrt candidates. It teaches why scanners need product normalization, because a banner string has to become a precise platform identifier before CVE correlation is credible.
AI mentor analysis for original cell 13: NVD CPE Search Function
This code cell demonstrates how to query the National Vulnerability Database (NVD) for CPEs using a keyword search. It retrieves and displays CPE candidates for specific software versions, such as OpenSSH 8.4 and OpenWrt 23.05. The cell illustrates the mechanics of interacting with NVD's API to gather CPE data, which is crucial for mapping software to known vulnerabilities. This process is foundational for vulnerability management, enabling practitioners to identify potential vulnerabilities based on software inventory.
Discussion question: How does the accuracy of CPE matching impact vulnerability management?
selected_cpe = None
if not openssh_cpes.empty:
# Prefer the p1 CPE when it is available because many real banners expose OpenSSH_8.4p1.
p1 = openssh_cpes[openssh_cpes["cpe_name"].str.contains("8.4:p1", regex=False, na=False)]
selected_cpe = (p1.iloc[0] if not p1.empty else openssh_cpes.iloc[0])["cpe_name"]
if not selected_cpe:
raise RuntimeError("No OpenSSH CPE candidate returned by NVD.")
cve_data = get_json(NVD_CVE_URL, params={"cpeName": selected_cpe, "resultsPerPage": 40}, nvd=True)
cpe_cves = pd.DataFrame([flatten_nvd_item(v) for v in cve_data.get("vulnerabilities", [])])
print(f"Selected CPE: {selected_cpe}")
print(f"NVD CVEs returned for selected CPE: {len(cpe_cves)}")
display(cpe_cves[["cve_id", "published", "base_score", "base_severity", "affected_cpe_count", "description"]].head(12))
Selected CPE: cpe:2.3:a:openbsd:openssh:8.4:p1:*:*:*:*:*:* NVD CVEs returned for selected CPE: 17
| cve_id | published | base_score | base_severity | affected_cpe_count | description | |
|---|---|---|---|---|---|---|
| 0 | CVE-2007-2768 | 2007-05-21T20:30:00.000 | 4.3 | MEDIUM | 5 | OpenSSH, when using OPIE (One-Time Passwords in Everything) for PAM, allows remote attackers to determine the existence of certain user ... |
| 1 | CVE-2008-3844 | 2008-08-27T20:41:00.000 | 9.3 | HIGH | 6 | Certain Red Hat Enterprise Linux (RHEL) 4 and 5 packages for OpenSSH, as signed in August 2008 using a legitimate Red Hat GPG key, conta... |
| 2 | CVE-2021-28041 | 2021-03-05T21:15:13.200 | 7.1 | HIGH | 12 | ssh-agent in OpenSSH before 8.5 has a double free that may be relevant in a few less-common scenarios, such as unconstrained agent-socke... |
| 3 | CVE-2016-20012 | 2021-09-15T20:15:07.310 | 5.3 | MEDIUM | 5 | OpenSSH through 8.7 allows remote attackers, who have a suspicion that a certain combination of username and public key is known to an S... |
| 4 | CVE-2021-41617 | 2021-09-26T19:15:07.263 | 7.0 | HIGH | 18 | sshd in OpenSSH 6.2 through 8.x before 8.8, when certain non-default configurations are used, allows privilege escalation because supple... |
| 5 | CVE-2021-36368 | 2022-03-13T00:15:07.937 | 3.7 | LOW | 4 | An issue was discovered in OpenSSH before 8.9. If a client is using public-key authentication with agent forwarding but without -oLogLev... |
| 6 | CVE-2023-38408 | 2023-07-20T03:15:10.170 | 9.8 | CRITICAL | 5 | The PKCS#11 feature in ssh-agent in OpenSSH before 9.3p2 has an insufficiently trustworthy search path, leading to remote code execution... |
| 7 | CVE-2023-48795 | 2023-12-18T16:15:10.897 | 5.9 | MEDIUM | 76 | The SSH transport protocol with certain OpenSSH extensions, found in OpenSSH before 9.6 and other products, allows remote attackers to b... |
| 8 | CVE-2023-51385 | 2023-12-18T19:15:08.773 | 6.5 | MEDIUM | 4 | In ssh in OpenSSH before 9.6, OS command injection might occur if a user name or host name has shell metacharacters, and this name is re... |
| 9 | CVE-2023-51767 | 2023-12-24T07:15:07.410 | 7.0 | HIGH | 4 | OpenSSH through 10.0, when common types of DRAM are used, might allow row hammer attacks (for authentication bypass) because the integer... |
| 10 | CVE-2025-26465 | 2025-02-18T19:15:29.230 | 6.8 | MEDIUM | 10 | A vulnerability was found in OpenSSH when the VerifyHostKeyDNS option is enabled. A machine-in-the-middle attack can be performed by a m... |
| 11 | CVE-2025-32728 | 2025-04-10T02:15:30.873 | 4.3 | MEDIUM | 2 | In sshd in OpenSSH before 10.0, the DisableForwarding directive does not adhere to the documentation stating that it disables X11 and ag... |
Audio explanation for original cell 14
Transcript: This code selects an OpenSSH CPE and queries the NVD CVE API for matching vulnerabilities. It teaches how a resolved CPE becomes a concrete CVE set, while also warning that banner-based evidence can still overstate applicability.
AI mentor analysis for original cell 14: Selecting and Querying CVEs for a CPE
This code cell selects a specific CPE, preferring the 'p1' version of OpenSSH 8.4, and queries the NVD for associated CVEs. It demonstrates the importance of selecting the most accurate CPE to ensure relevant CVEs are identified. The cell highlights the process of retrieving CVE data, including details like CVE ID, publication date, and severity. This step is critical for understanding the vulnerabilities affecting a specific software version and prioritizing remediation efforts.
Discussion question: Why is it important to select the most accurate CPE when querying for CVEs?
def parse_cvss_vector(vector: str | None) -> dict[str, str]:
if not vector:
return {}
parts = vector.split("/")
parsed = {"version": parts[0]}
for part in parts[1:]:
if ":" in part:
key, value = part.split(":", 1)
parsed[key] = value
return parsed
cvss_rows = []
for _, row in cpe_cves.dropna(subset=["cvss_vector"]).head(12).iterrows():
parsed = parse_cvss_vector(row["cvss_vector"])
parsed["cve_id"] = row["cve_id"]
parsed["base_score"] = row["base_score"]
parsed["base_severity"] = row["base_severity"]
cvss_rows.append(parsed)
cvss_df = pd.DataFrame(cvss_rows)
print("CVSS is a structured vector, not just a number. AV/AC/PR/UI/S/C/I/A encode exploit path and impact.")
display(cvss_df)
CVSS is a structured vector, not just a number. AV/AC/PR/UI/S/C/I/A encode exploit path and impact.
| version | AC | Au | C | I | A | cve_id | base_score | base_severity | AV | PR | UI | S | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | AV:N | M | N | P | N | N | CVE-2007-2768 | 4.3 | MEDIUM | NaN | NaN | NaN | NaN |
| 1 | AV:N | M | N | C | C | C | CVE-2008-3844 | 9.3 | HIGH | NaN | NaN | NaN | NaN |
| 2 | CVSS:3.1 | H | NaN | H | H | H | CVE-2021-28041 | 7.1 | HIGH | N | L | R | U |
| 3 | CVSS:3.1 | L | NaN | L | N | N | CVE-2016-20012 | 5.3 | MEDIUM | N | N | N | U |
| 4 | CVSS:3.1 | H | NaN | H | H | H | CVE-2021-41617 | 7.0 | HIGH | L | L | N | U |
| 5 | CVSS:3.1 | H | NaN | L | N | N | CVE-2021-36368 | 3.7 | LOW | N | N | N | U |
| 6 | CVSS:3.1 | L | NaN | H | H | H | CVE-2023-38408 | 9.8 | CRITICAL | N | N | N | U |
| 7 | CVSS:3.1 | H | NaN | N | H | N | CVE-2023-48795 | 5.9 | MEDIUM | N | N | N | U |
| 8 | CVSS:3.1 | L | NaN | L | L | N | CVE-2023-51385 | 6.5 | MEDIUM | N | N | N | U |
| 9 | CVSS:3.1 | H | NaN | H | H | H | CVE-2023-51767 | 7.0 | HIGH | L | L | N | U |
| 10 | CVSS:3.1 | H | NaN | H | H | N | CVE-2025-26465 | 6.8 | MEDIUM | N | N | R | U |
| 11 | CVSS:3.1 | L | NaN | N | L | N | CVE-2025-32728 | 4.3 | MEDIUM | L | N | N | C |
Audio explanation for original cell 15
Transcript: This code parses CVSS vectors into their individual fields. It teaches that CVSS is not only a score; attack vector, complexity, privileges, user interaction, scope, and impact fields encode the exploit model.
AI mentor analysis for original cell 15: Parsing CVSS Vectors
This code cell parses CVSS vectors from CVE data to extract detailed information about the exploitability and impact of vulnerabilities. It highlights that CVSS is more than just a score; it includes structured information about attack vectors, complexity, privileges required, and impact on confidentiality, integrity, and availability. Understanding these components is crucial for accurately assessing the risk posed by a vulnerability and making informed decisions about mitigation strategies.
Discussion question: How does understanding the components of a CVSS vector improve vulnerability assessment?
Plotly visual analytics
This Plotly cell visualizes CVSS severity and vector dimensions for the selected CPE, making exploit-path assumptions easier to compare.
# Plotly visual analytics: CVE severity and CVSS dimension profile for the selected CPE.
if not cpe_cves.empty:
fig = px.histogram(
cpe_cves.dropna(subset=["base_severity"]),
x="base_severity",
color="base_severity",
title=f"NVD Severity Distribution for {selected_cpe}",
labels={"base_severity": "CVSS severity"},
)
fig.update_layout(showlegend=False)
fig.show()
if not cvss_df.empty:
impact_cols = [col for col in ["AV", "AC", "PR", "UI", "S", "C", "I", "A"] if col in cvss_df.columns]
melted_cvss = cvss_df.melt(id_vars=["cve_id"], value_vars=impact_cols, var_name="cvss_dimension", value_name="value")
fig = px.histogram(
melted_cvss,
x="cvss_dimension",
color="value",
barmode="group",
title="CVSS Vector Dimension Mix Across Selected CPE CVEs",
)
fig.show()
Interpretation: how scanners use CPE internally¶
A scanner can run this CPE pipeline in several ways:
- Unauthenticated: infer product and version from protocol banners or HTTP metadata. Fast, broad, but weak evidence.
- Authenticated: query package databases such as RPM, dpkg, Windows registry, kernel build metadata, container image manifests, or SBOMs. Slower, stronger evidence.
- Passive: infer CPEs from network traffic and TLS/HTTP/industrial protocol fingerprints. Lower operational risk, but often lower version precision.
- Hybrid: combine all three and attach an evidence confidence score to each finding.
A mature product must also handle vendor backports. For example, an enterprise Linux package may carry a vulnerable-looking upstream version string but include a downstream patch. That is why package advisory metadata often outranks naive banner matching.
Audio explanation for original cell 16
Transcript: This interpretation cell compares unauthenticated, authenticated, passive, and hybrid CPE evidence. It teaches why package advisory metadata and patch state often outrank naive service-banner matching.
AI mentor analysis for original cell 16: Internal Use of CPE by Scanners
This markdown cell explains how scanners utilize CPEs internally, detailing different methods of evidence collection: unauthenticated, authenticated, passive, and hybrid. Each method has its strengths and weaknesses, affecting the precision and reliability of vulnerability assessments. The cell emphasizes the importance of handling vendor backports, where version strings may not reflect actual vulnerability status due to downstream patches. This underscores the need for comprehensive evidence evaluation to avoid false positives and negatives.
Discussion question: What are the benefits and drawbacks of using a hybrid approach to CPE evidence collection?
Mermaid concept diagram before original cell 17: Learning Paradigms to AutoML
Use this diagram to distinguish supervised, unsupervised, reinforcement learning, and AutoML before the model-selection cells.
3. AutoML for Vulnerability Triage¶
Concept¶
Before jumping to AutoML, separate the learning paradigms:
- Supervised learning: train on labeled examples. Here, CVE descriptions are text and CVSS severity is the label.
- Unsupervised learning: discover structure without labels. Later we cluster Nuclei templates by metadata.
- Reinforcement learning: choose actions using rewards from environment feedback. Later we select fuzzing mutation operators based on parser-state novelty.
- AutoML: automate part of model selection, preprocessing, or hyperparameter search. In production this might include feature pipelines, model families, calibration, drift checks, and champion/challenger promotion.
This section uses a small AutoML-style model selection loop over real NVD CVE descriptions. It is not a production severity oracle. It teaches why text features can approximate triage labels and why model selection should be measured instead of guessed.
Audio explanation for original cell 17
Transcript: This AutoML concept cell separates supervised learning, unsupervised learning, reinforcement learning, and AutoML. It teaches where machine learning fits in vulnerability triage before using it on real NVD data.
AI mentor analysis for original cell 17: Introduction to AutoML for Vulnerability Triage
This section introduces the concept of using AutoML for vulnerability triage, distinguishing between supervised, unsupervised, and reinforcement learning. It explains how AutoML can automate parts of model selection and preprocessing, which is useful for handling large datasets like NVD CVE descriptions. The goal is to approximate triage labels using text features, demonstrating the potential of machine learning in prioritizing vulnerabilities. This approach highlights the importance of measuring model performance rather than relying on intuition.
Discussion question: How can AutoML improve the efficiency of vulnerability triage in cybersecurity?
Mermaid concept diagram before original cell 18: Supervised Severity Training Set
This diagram explains how real NVD descriptions become a small teaching dataset.
severity_frames = []
for severity in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
data = get_json(
NVD_CVE_URL,
params={"cvssV3Severity": severity, "resultsPerPage": 70},
nvd=True,
cache_seconds=7 * 24 * 3600,
)
frame = pd.DataFrame([flatten_nvd_item(v) for v in data.get("vulnerabilities", [])])
frame = frame.dropna(subset=["description", "base_severity"])
severity_frames.append(frame)
severity_corpus = pd.concat(severity_frames, ignore_index=True)
severity_corpus = severity_corpus.drop_duplicates("cve_id")
severity_corpus = severity_corpus[severity_corpus["base_severity"].isin(["CRITICAL", "HIGH", "MEDIUM", "LOW"])]
print(f"Training examples from NVD: {len(severity_corpus)}")
display(severity_corpus["base_severity"].value_counts().rename_axis("severity").to_frame("count"))
Training examples from NVD: 280
| count | |
|---|---|
| severity | |
| CRITICAL | 70 |
| HIGH | 70 |
| MEDIUM | 70 |
| LOW | 70 |
Audio explanation for original cell 18
Transcript: This code downloads severity-labeled CVE descriptions from NVD. It teaches supervised learning with real labels, where vulnerability descriptions are the features and CVSS severity is the training target.
AI mentor analysis for original cell 18: Building a Severity Corpus from NVD
This code cell constructs a dataset of CVE descriptions and their associated severities from the NVD, categorized into CRITICAL, HIGH, MEDIUM, and LOW. It retrieves data for each severity level and ensures the corpus is free of duplicates. This dataset serves as the foundation for training machine learning models to predict vulnerability severity. By using real-world data, the cell emphasizes the importance of having a balanced and representative dataset for training effective models.
Discussion question: Why is it important to have a balanced dataset when training models for severity prediction?
model_candidates = {
"word_tfidf_logreg": Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=2, max_features=6000)),
("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
]),
"word_tfidf_linear_svc": Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=2, max_features=6000)),
("clf", LinearSVC(class_weight="balanced")),
]),
"word_tfidf_nb": Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=2, max_features=6000)),
("clf", MultinomialNB()),
]),
"char_tfidf_logreg": Pipeline([
("tfidf", TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5), min_df=2, max_features=8000)),
("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
]),
}
X = severity_corpus["description"].fillna("").astype(str)
y = severity_corpus["base_severity"].astype(str)
cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=7)
scores = []
for name, pipe in model_candidates.items():
macro_f1 = cross_val_score(pipe, X, y, cv=cv, scoring="f1_macro")
scores.append({"model": name, "macro_f1_mean": macro_f1.mean(), "macro_f1_std": macro_f1.std()})
scores_df = pd.DataFrame(scores).sort_values("macro_f1_mean", ascending=False)
best_model_name = scores_df.iloc[0]["model"]
best_model = model_candidates[best_model_name]
print("AutoML-style candidate ranking:")
display(scores_df)
print(f"Selected model: {best_model_name}")
AutoML-style candidate ranking:
| model | macro_f1_mean | macro_f1_std | |
|---|---|---|---|
| 1 | word_tfidf_linear_svc | 0.719955 | 0.039877 |
| 0 | word_tfidf_logreg | 0.689548 | 0.048987 |
| 3 | char_tfidf_logreg | 0.666445 | 0.005907 |
| 2 | word_tfidf_nb | 0.657928 | 0.045354 |
Selected model: word_tfidf_linear_svc
Audio explanation for original cell 19
Transcript: This code compares several text classification pipelines with cross-validated macro F1. It teaches AutoML in miniature: do not guess the model family, measure candidate pipelines and select the best performer.
AI mentor analysis for original cell 19: AutoML-style Model Selection for Severity Prediction
This code cell implements an AutoML-style approach to model selection, evaluating different text classification pipelines using cross-validation. It compares models based on their macro F1 scores, selecting the best-performing one for predicting CVE severity. The cell demonstrates the process of evaluating multiple models to identify the most suitable one for a given task, highlighting the importance of empirical evaluation in machine learning. This approach helps in understanding the strengths and weaknesses of different models for text-based vulnerability triage.
Discussion question: What factors should be considered when selecting a machine learning model for text classification tasks?
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, stratify=y, random_state=7)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
print(classification_report(y_test, y_pred, zero_division=0))
example_descriptions = [
"Unauthenticated remote attacker can execute arbitrary commands in embedded device management CGI.",
"Local user can read a temporary file created with insecure permissions.",
"Malformed protocol packet causes a denial of service in a daemon running as root.",
]
example_predictions = pd.DataFrame({
"example_description": example_descriptions,
"predicted_severity": best_model.predict(example_descriptions),
})
display(example_predictions)
precision recall f1-score support
CRITICAL 0.74 0.78 0.76 18
HIGH 0.71 0.59 0.65 17
LOW 1.00 0.94 0.97 17
MEDIUM 0.76 0.89 0.82 18
accuracy 0.80 70
macro avg 0.80 0.80 0.80 70
weighted avg 0.80 0.80 0.80 70
| example_description | predicted_severity | |
|---|---|---|
| 0 | Unauthenticated remote attacker can execute arbitrary commands in embedded device management CGI. | CRITICAL |
| 1 | Local user can read a temporary file created with insecure permissions. | MEDIUM |
| 2 | Malformed protocol packet causes a denial of service in a daemon running as root. | MEDIUM |
Audio explanation for original cell 20
Transcript: This code trains the selected model, evaluates it on a held-out split, and predicts severity for example descriptions. It teaches both the value and the limits of text-based triage models.
AI mentor analysis for original cell 20: Evaluating the Best Model for Severity Prediction
This code cell evaluates the selected model's performance on a test dataset, providing precision, recall, and F1-score metrics. It also predicts the severity of example vulnerability descriptions, demonstrating the model's practical application. The cell highlights the importance of evaluating model performance on unseen data to ensure generalizability. By providing example predictions, it illustrates how the model can be used to assist in vulnerability triage, emphasizing the role of machine learning in enhancing cybersecurity operations.
Discussion question: How can model evaluation metrics guide improvements in machine learning models for cybersecurity?
Plotly visual analytics
This Plotly cell visualizes model-selection evidence and classifier mistakes, which is the key teaching point for AutoML governance.
# Plotly visual analytics: AutoML ranking and confusion matrix.
fig = px.bar(
scores_df,
x="macro_f1_mean",
y="model",
orientation="h",
error_x="macro_f1_std",
title="AutoML-Style Model Ranking by Cross-Validated Macro-F1",
labels={"macro_f1_mean": "Macro-F1 mean", "model": "Candidate pipeline"},
)
fig.update_layout(yaxis={"categoryorder": "total ascending"}, height=420)
fig.show()
labels_sorted = sorted(y.unique())
confusion = pd.crosstab(pd.Series(y_test, name="Actual"), pd.Series(y_pred, name="Predicted"), dropna=False).reindex(index=labels_sorted, columns=labels_sorted, fill_value=0)
fig = px.imshow(
confusion,
text_auto=True,
title="Held-Out Severity Classifier Confusion Matrix",
color_continuous_scale="Blues",
)
fig.show()
Interpretation: what AutoML is and is not doing¶
The model selection cell is intentionally small but conceptually important. It automates the selection of text representation and classifier family based on cross-validated macro-F1. That is AutoML in miniature.
In production, a vulnerability platform would add calibration, explainability, class imbalance controls, tenant drift detection, model lineage, and rollback. It would not use a text model alone to override a grounded CVSS, vendor advisory, or exploitability signal. It would use ML as one feature in a detective analytics pipeline.
Audio explanation for original cell 21
Transcript: This interpretation cell reinforces that AutoML is not an oracle. It teaches that machine-learning output should support, not replace, grounded CVSS, vendor advisory, exploitability, and asset-context evidence.
AI mentor analysis for original cell 21: Understanding AutoML in Vulnerability Analysis
This cell provides a conceptual overview of AutoML's role in vulnerability analysis. It highlights that AutoML can automate model selection based on performance metrics like macro-F1, which is a simplified version of its potential in production environments. In real-world applications, AutoML would be part of a broader system that includes model calibration, explainability, and controls for class imbalance. It emphasizes that machine learning should complement, not replace, established security metrics like CVSS scores or vendor advisories, serving as one component in a comprehensive analytics pipeline.
Discussion question: How can AutoML be effectively integrated into existing vulnerability management workflows?
Mermaid concept diagram before original cell 22: Threat Intelligence Fusion
Use this diagram before the fusion cell so students understand what each risk signal contributes.
4. Threat Intelligence Fusion: NVD + KEV + EPSS¶
Concept¶
CVSS measures technical severity. KEV measures known exploitation. EPSS estimates exploitation probability. They answer different questions.
A detective analytics pipeline becomes more useful when it fuses these signals:
- CVSS: how bad could exploitation be under the modeled conditions?
- EPSS: how likely is exploitation activity in the near term?
- KEV: has exploitation already been observed and cataloged?
- Asset context: is the affected system exposed, critical, reachable, or compensating-controlled?
The code below enriches the OpenSSH CPE CVE set with EPSS and KEV flags.
Audio explanation for original cell 22
Transcript: This threat-intelligence fusion concept cell explains the different jobs of CVSS, EPSS, KEV, and asset context. It teaches that prioritization improves when severity, exploitation probability, known exploitation, and exposure are combined.
AI mentor analysis for original cell 22: Threat Intelligence Fusion with NVD, KEV, and EPSS
This cell discusses the integration of different threat intelligence signalsāCVSS, KEV, and EPSSāinto a unified analytics pipeline. Each signal provides unique insights: CVSS assesses potential severity, EPSS predicts exploitation likelihood, and KEV confirms observed exploitation. By fusing these signals, along with asset context, the pipeline can offer a more nuanced understanding of vulnerabilities. This approach enhances decision-making by considering both technical severity and real-world exploitation data, leading to more informed prioritization and mitigation strategies.
Discussion question: What are the challenges in integrating multiple threat intelligence signals into a single analytics pipeline?
EPSS_URL = "https://api.first.org/data/v1/epss"
triage_base = cpe_cves[["cve_id", "base_score", "base_severity", "description"]].dropna(subset=["cve_id"]).copy()
triage_ids = triage_base["cve_id"].head(30).tolist()
epss_json = get_json(EPSS_URL, params={"cve": ",".join(triage_ids)}, cache_seconds=12 * 3600)
epss_df = pd.DataFrame(epss_json.get("data", []))
if not epss_df.empty:
epss_df["epss"] = pd.to_numeric(epss_df["epss"], errors="coerce").fillna(0.0)
epss_df["percentile"] = pd.to_numeric(epss_df["percentile"], errors="coerce").fillna(0.0)
else:
epss_df = pd.DataFrame(columns=["cve", "epss", "percentile"])
kev_flags = kev_df[["cveID", "knownRansomwareCampaignUse", "dateAdded"]].rename(columns={"cveID": "cve_id"})
kev_flags["in_kev"] = True
kev_triage = (
triage_base
.merge(epss_df.rename(columns={"cve": "cve_id"})[["cve_id", "epss", "percentile"]], on="cve_id", how="left")
.merge(kev_flags, on="cve_id", how="left")
)
kev_triage["epss"] = kev_triage["epss"].fillna(0.0)
kev_triage["percentile"] = kev_triage["percentile"].fillna(0.0)
kev_triage["in_kev"] = kev_triage["in_kev"].fillna(False)
kev_triage["knownRansomwareCampaignUse"] = kev_triage["knownRansomwareCampaignUse"].fillna("Unknown")
kev_triage["base_score"] = pd.to_numeric(kev_triage["base_score"], errors="coerce").fillna(0.0)
kev_triage["triage_score"] = (
kev_triage["base_score"]
+ kev_triage["epss"] * 10.0
+ kev_triage["in_kev"].astype(int) * 5.0
+ (kev_triage["knownRansomwareCampaignUse"].str.lower() == "known").astype(int) * 2.0
)
print("Fused prioritization table for the selected CPE CVEs:")
display(kev_triage.sort_values("triage_score", ascending=False).head(15))
Fused prioritization table for the selected CPE CVEs:
| cve_id | base_score | base_severity | description | epss | percentile | knownRansomwareCampaignUse | dateAdded | in_kev | triage_score | |
|---|---|---|---|---|---|---|---|---|---|---|
| 6 | CVE-2023-38408 | 9.8 | CRITICAL | The PKCS#11 feature in ssh-agent in OpenSSH before 9.3p2 has an insufficiently trustworthy search path, leading to remote code execution... | 0.64352 | 0.98466 | Unknown | NaT | False | 16.2352 |
| 10 | CVE-2025-26465 | 6.8 | MEDIUM | A vulnerability was found in OpenSSH when the VerifyHostKeyDNS option is enabled. A machine-in-the-middle attack can be performed by a m... | 0.62453 | 0.98389 | Unknown | NaT | False | 13.0453 |
| 7 | CVE-2023-48795 | 5.9 | MEDIUM | The SSH transport protocol with certain OpenSSH extensions, found in OpenSSH before 9.6 and other products, allows remote attackers to b... | 0.54214 | 0.98062 | Unknown | NaT | False | 11.3214 |
| 1 | CVE-2008-3844 | 9.3 | HIGH | Certain Red Hat Enterprise Linux (RHEL) 4 and 5 packages for OpenSSH, as signed in August 2008 using a legitimate Red Hat GPG key, conta... | 0.02746 | 0.86234 | Unknown | NaT | False | 9.5746 |
| 8 | CVE-2023-51385 | 6.5 | MEDIUM | In ssh in OpenSSH before 9.6, OS command injection might occur if a user name or host name has shell metacharacters, and this name is re... | 0.17234 | 0.95136 | Unknown | NaT | False | 8.2234 |
| 12 | CVE-2026-35385 | 7.5 | HIGH | In OpenSSH before 10.3, a file downloaded by scp may be installed setuid or setgid, an outcome contrary to some users' expectations, if ... | 0.00058 | 0.18419 | Unknown | NaT | False | 7.5058 |
| 2 | CVE-2021-28041 | 7.1 | HIGH | ssh-agent in OpenSSH before 8.5 has a double free that may be relevant in a few less-common scenarios, such as unconstrained agent-socke... | 0.00256 | 0.49133 | Unknown | NaT | False | 7.1256 |
| 4 | CVE-2021-41617 | 7.0 | HIGH | sshd in OpenSSH 6.2 through 8.x before 8.8, when certain non-default configurations are used, allows privilege escalation because supple... | 0.00274 | 0.50983 | Unknown | NaT | False | 7.0274 |
| 9 | CVE-2023-51767 | 7.0 | HIGH | OpenSSH through 10.0, when common types of DRAM are used, might allow row hammer attacks (for authentication bypass) because the integer... | 0.00007 | 0.00489 | Unknown | NaT | False | 7.0007 |
| 3 | CVE-2016-20012 | 5.3 | MEDIUM | OpenSSH through 8.7 allows remote attackers, who have a suspicion that a certain combination of username and public key is known to an S... | 0.14603 | 0.94580 | Unknown | NaT | False | 6.7603 |
| 11 | CVE-2025-32728 | 4.3 | MEDIUM | In sshd in OpenSSH before 10.0, the DisableForwarding directive does not adhere to the documentation stating that it disables X11 and ag... | 0.00226 | 0.45389 | Unknown | NaT | False | 4.3226 |
| 0 | CVE-2007-2768 | 4.3 | MEDIUM | OpenSSH, when using OPIE (One-Time Passwords in Everything) for PAM, allows remote attackers to determine the existence of certain user ... | 0.00189 | 0.40552 | Unknown | NaT | False | 4.3189 |
| 16 | CVE-2026-35414 | 4.2 | MEDIUM | OpenSSH before 10.3 mishandles the authorized_keys principals option in uncommon scenarios involving a principals list in conjunction wi... | 0.00031 | 0.09377 | Unknown | NaT | False | 4.2031 |
| 5 | CVE-2021-36368 | 3.7 | LOW | An issue was discovered in OpenSSH before 8.9. If a client is using public-key authentication with agent forwarding but without -oLogLev... | 0.00432 | 0.62972 | Unknown | NaT | False | 3.7432 |
| 13 | CVE-2026-35386 | 3.6 | LOW | In OpenSSH before 10.3, command execution can occur via shell metacharacters in a username within a command line. This requires a scenar... | 0.00034 | 0.10598 | Unknown | NaT | False | 3.6034 |
Audio explanation for original cell 23
Transcript: This code joins the selected CPE CVEs with FIRST EPSS and CISA KEV signals, then calculates a teaching triage score. It teaches evidence fusion and makes the scoring logic inspectable instead of mysterious.
AI mentor analysis for original cell 23: Fusing EPSS and KEV Data for CVE Prioritization
This code cell demonstrates the process of enriching a CVE dataset with EPSS and KEV data to prioritize vulnerabilities. It retrieves EPSS scores and KEV flags for a set of CVEs, then calculates a 'triage score' by combining base CVSS scores with EPSS likelihood and KEV exploitation status. This approach allows for a more comprehensive prioritization by considering both the technical severity and the likelihood of exploitation. The output is a sorted table of CVEs, highlighting those that require immediate attention based on the fused data.
Discussion question: How does combining EPSS and KEV data improve the prioritization of vulnerabilities?
Plotly visual analytics
This Plotly cell visualizes fused prioritization across technical severity, exploitation probability, and known exploitation.
# Plotly visual analytics: fused risk triage across CVSS, EPSS, and KEV.
triage_plot = kev_triage.copy()
triage_plot["in_kev_label"] = triage_plot["in_kev"].map({True: "In KEV", False: "Not in KEV"})
fig = px.scatter(
triage_plot,
x="epss",
y="base_score",
size="triage_score",
color="in_kev_label",
hover_name="cve_id",
hover_data=["base_severity", "percentile", "knownRansomwareCampaignUse"],
title="Fused Vulnerability Triage: CVSS vs EPSS with KEV Signal",
labels={"epss": "EPSS probability", "base_score": "CVSS base score"},
)
fig.show()
fig = px.bar(
triage_plot.sort_values("triage_score", ascending=False).head(15),
x="cve_id",
y="triage_score",
color="base_severity",
title="Top Fused Triage Scores for Selected CPE CVEs",
)
fig.update_layout(xaxis_tickangle=-45)
fig.show()
Mermaid concept diagram before original cell 24: OpenVAS and GVM Architecture
This diagram explains the OpenVAS/GVM layers before looking at XML and report parsing.
5. OpenVAS/GVM Integration¶
Concept¶
OpenVAS/GVM is useful pedagogically because it exposes the layers found in many commercial scanners:
- Scanner engine: performs network checks and vulnerability tests.
- Vulnerability tests: often written as plugins or scripts. In OpenVAS these are commonly NASL tests.
- Manager/orchestrator: stores targets, tasks, reports, credentials, schedules, and scan state.
- Protocol/API: clients automate scans through a management protocol such as GMP.
- Feed synchronization: scanners continuously ingest vulnerability tests, advisories, product mappings, and detection logic.
Internally, a scanner may combine TCP connect checks, service probes, TLS inspection, protocol-specific safe requests, authenticated package inspection, and proof-of-vulnerability checks. Commercial products use similar ideas with proprietary feeds, passive sensors, agent telemetry, cloud enrichment, and risk scoring.
This section does not scan an external host. It shows the integration contract and parses a local sample report while using the real Greenbone/OpenVAS public repository as source evidence for NASL-style vulnerability tests.
Audio explanation for original cell 24
Transcript: This OpenVAS and GVM concept cell explains the scanner engine, vulnerability tests, manager, API, and feed layers. It teaches that commercial scanners are orchestration systems around evidence collection and plugin logic.
AI mentor analysis for original cell 24: Understanding OpenVAS/GVM Architecture
This cell provides an overview of the OpenVAS/GVM architecture, which is representative of many commercial vulnerability scanners. It explains the components such as the scanner engine, vulnerability tests, and management protocols. The cell emphasizes the importance of feed synchronization and the combination of various scanning techniques like TCP checks and TLS inspection. By understanding these components, practitioners can better appreciate the complexity and capabilities of vulnerability scanners, as well as the challenges in maintaining accuracy and efficiency.
Discussion question: What are the key challenges in maintaining the accuracy and efficiency of a vulnerability scanner like OpenVAS?
GREENBONE_TREE_URL = "https://api.github.com/repos/greenbone/openvas-scanner/git/trees/main"
greenbone_tree = get_json(GREENBONE_TREE_URL, params={"recursive": "1"}, cache_seconds=7 * 24 * 3600)
paths = [item.get("path", "") for item in greenbone_tree.get("tree", [])]
nasl_paths = [p for p in paths if p.endswith(".nasl") or "/nasl/" in p.lower()]
example_paths = pd.DataFrame({"greenbone_repo_path": nasl_paths[:20]})
print(f"Repository paths inspected: {len(paths):,}")
print(f"NASL-related paths found: {len(nasl_paths):,}")
display(example_paths)
Repository paths inspected: 1,660 NASL-related paths found: 820
| greenbone_repo_path | |
|---|---|
| 0 | doc/manual/nasl/built-in-functions |
| 1 | doc/manual/nasl/built-in-functions/built-in-plugins |
| 2 | doc/manual/nasl/built-in-functions/built-in-plugins/index.md |
| 3 | doc/manual/nasl/built-in-functions/built-in-plugins/plugin_run_find_service.md |
| 4 | doc/manual/nasl/built-in-functions/built-in-plugins/plugin_run_openvas_tcp_scanner.md |
| 5 | doc/manual/nasl/built-in-functions/built-in-plugins/plugin_run_synscan.md |
| 6 | doc/manual/nasl/built-in-functions/cert-functions |
| 7 | doc/manual/nasl/built-in-functions/cert-functions/cert_close.md |
| 8 | doc/manual/nasl/built-in-functions/cert-functions/cert_open.md |
| 9 | doc/manual/nasl/built-in-functions/cert-functions/cert_query.md |
| 10 | doc/manual/nasl/built-in-functions/cert-functions/index.md |
| 11 | doc/manual/nasl/built-in-functions/cryptographic |
| 12 | doc/manual/nasl/built-in-functions/cryptographic/DES.md |
| 13 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_MD2.md |
| 14 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_MD5.md |
| 15 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_RIPEMD160.md |
| 16 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_SHA1.md |
| 17 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_SHA256.md |
| 18 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_SHA384.md |
| 19 | doc/manual/nasl/built-in-functions/cryptographic/HMAC_SHA512.md |
Audio explanation for original cell 25
Transcript: This code inspects the public Greenbone OpenVAS scanner repository and counts NASL-related paths. It teaches that scanner detection logic is often maintained as a large feed of specialized tests.
AI mentor analysis for original cell 25: Exploring Greenbone/OpenVAS NASL Scripts
This code cell retrieves and lists NASL script paths from the Greenbone/OpenVAS public repository, providing insight into the structure and volume of vulnerability tests available. By examining these scripts, practitioners can understand how OpenVAS leverages NASL for vulnerability detection. The cell highlights the importance of script-based tests in identifying vulnerabilities and the role of public repositories in providing up-to-date detection logic. This exploration aids in understanding the mechanics behind vulnerability scanning and the reliance on community-contributed scripts.
Discussion question: How do public repositories like Greenbone's contribute to the effectiveness of vulnerability scanners?
def gmp_create_target_xml(name: str, hosts: list[str], port_list_id: str) -> str:
root = ET.Element("create_target")
ET.SubElement(root, "name").text = name
ET.SubElement(root, "hosts").text = ",".join(hosts)
ET.SubElement(root, "port_list", id=port_list_id)
return ET.tostring(root, encoding="unicode")
def gmp_create_task_xml(name: str, target_id: str, scanner_id: str, config_id: str) -> str:
root = ET.Element("create_task")
ET.SubElement(root, "name").text = name
ET.SubElement(root, "target", id=target_id)
ET.SubElement(root, "scanner", id=scanner_id)
ET.SubElement(root, "config", id=config_id)
return ET.tostring(root, encoding="unicode")
print("Example GMP create_target XML:")
print(gmp_create_target_xml("chapter-3-local-lab", ["127.0.0.1"], "port-list-uuid"))
print("\nExample GMP create_task XML:")
print(gmp_create_task_xml("chapter-3-safe-task", "target-uuid", "scanner-uuid", "scan-config-uuid"))
Example GMP create_target XML: <create_target><name>chapter-3-local-lab</name><hosts>127.0.0.1</hosts><port_list id="port-list-uuid" /></create_target> Example GMP create_task XML: <create_task><name>chapter-3-safe-task</name><target id="target-uuid" /><scanner id="scanner-uuid" /><config id="scan-config-uuid" /></create_task>
Audio explanation for original cell 26
Transcript: This code builds example GMP XML requests for targets and tasks. It teaches the integration contract that automation clients use when they create scan targets, select scanners, and launch tasks through a manager API.
AI mentor analysis for original cell 26: Creating GMP XML for OpenVAS Tasks
This code cell demonstrates the creation of XML configurations for OpenVAS tasks using the Greenbone Management Protocol (GMP). It shows how to define targets and tasks by constructing XML strings that specify target hosts, port lists, and scanner configurations. This process is crucial for automating vulnerability scans and integrating them into larger security workflows. By understanding GMP XML, practitioners can customize and automate their scanning processes, enhancing the efficiency and consistency of vulnerability management efforts.
Discussion question: What are the advantages of using GMP XML for automating vulnerability scans in OpenVAS?
sample_gvm_report = """<get_reports_response status="200" status_text="OK">
<report id="sample-report">
<results>
<result id="r1">
<host>192.0.2.10</host>
<port>tcp/22</port>
<severity>8.1</severity>
<threat>High</threat>
<nvt oid="1.3.6.1.4.1.25623.1.0.900001">
<name>OpenSSH example vulnerability detection</name>
<cves>CVE-2024-6387</cves>
</nvt>
<description>Example result used to teach GMP parsing; not a live scan finding.</description>
</result>
<result id="r2">
<host>192.0.2.20</host>
<port>tcp/80</port>
<severity>5.3</severity>
<threat>Medium</threat>
<nvt oid="1.3.6.1.4.1.25623.1.0.900002">
<name>Default web console credential check</name>
<cves></cves>
</nvt>
<description>Example result used to teach report normalization.</description>
</result>
</results>
</report>
</get_reports_response>"""
root = ET.fromstring(sample_gvm_report)
gvm_rows = []
for result in root.findall(".//result"):
nvt = result.find("nvt")
gvm_rows.append({
"result_id": result.get("id"),
"host": result.findtext("host"),
"port": result.findtext("port"),
"severity": float(result.findtext("severity", "0")),
"threat": result.findtext("threat"),
"nvt_oid": nvt.get("oid") if nvt is not None else None,
"nvt_name": nvt.findtext("name") if nvt is not None else None,
"cves": nvt.findtext("cves") if nvt is not None else None,
"description": result.findtext("description"),
})
gvm_report_df = pd.DataFrame(gvm_rows).sort_values("severity", ascending=False)
display(gvm_report_df)
| result_id | host | port | severity | threat | nvt_oid | nvt_name | cves | description | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | r1 | 192.0.2.10 | tcp/22 | 8.1 | High | 1.3.6.1.4.1.25623.1.0.900001 | OpenSSH example vulnerability detection | CVE-2024-6387 | Example result used to teach GMP parsing; not a live scan finding. |
| 1 | r2 | 192.0.2.20 | tcp/80 | 5.3 | Medium | 1.3.6.1.4.1.25623.1.0.900002 | Default web console credential check | Example result used to teach report normalization. |
Audio explanation for original cell 27
Transcript: This code parses a simulated GVM XML report. It teaches report normalization: host, port, severity, threat, NVT identity, CVEs, and descriptions become structured records for dashboards and tickets.
AI mentor analysis for original cell 27: Parsing a Sample GVM Report
This code cell parses a simulated GVM report to extract and display vulnerability findings. It demonstrates how to navigate XML structures to retrieve details such as host, port, severity, and CVE identifiers. This parsing process is essential for transforming raw scan data into actionable insights. By understanding how to extract and interpret this information, practitioners can better integrate scan results into their security operations, enabling more effective vulnerability management and remediation planning.
Discussion question: How can effective parsing of vulnerability reports enhance security operations?
Plotly visual analytics
This Plotly cell visualizes scanner plugin/source organization and normalized sample report severity.
# Plotly visual analytics: OpenVAS/GVM-style evidence normalization.
if nasl_paths:
nasl_dirs = pd.Series(["/".join(p.split("/")[:2]) if "/" in p else p for p in nasl_paths], name="repo_area").value_counts().head(12).reset_index()
nasl_dirs.columns = ["repo_area", "count"]
fig = px.treemap(nasl_dirs, path=["repo_area"], values="count", title="Greenbone/OpenVAS NASL-Related Repository Areas")
fig.show()
fig = px.bar(
gvm_report_df,
x="nvt_name",
y="severity",
color="threat",
hover_data=["host", "port", "cves"],
title="Normalized Sample GVM Report Findings by Severity",
)
fig.update_layout(xaxis_tickangle=-25)
fig.show()
Interpretation: commercial scanner pattern¶
Commercial scanners differ in UI, feeds, cloud enrichment, and enterprise workflow, but the underlying cycle is similar:
- Define target and credentials.
- Select policies or plugins.
- Probe services and collect evidence.
- Match evidence against plugin logic and vulnerability intelligence.
- Normalize findings into reports, tickets, risk scores, and remediation plans.
The hard engineering problems are scale, false-positive control, credential handling, feed freshness, safe probing, tenant isolation, auditability, and prioritization.
Audio explanation for original cell 28
Transcript: This interpretation cell generalizes the commercial scanner workflow. It teaches the recurring pattern of target definition, credential handling, policy selection, evidence collection, finding normalization, and remediation workflow.
AI mentor analysis for original cell 28: Commercial Vulnerability Scanner Workflow
This cell outlines the typical workflow of commercial vulnerability scanners, emphasizing the similarities despite differences in UI and features. It describes the cycle from target definition to report generation, highlighting challenges like scale, false positives, and feed freshness. Understanding this workflow helps practitioners appreciate the complexities involved in vulnerability scanning and the engineering efforts required to maintain accuracy and efficiency. It also underscores the importance of integrating various components to achieve comprehensive vulnerability management.
Discussion question: What are the most significant engineering challenges in developing a commercial vulnerability scanner?
Mermaid concept diagram before original cell 29: Nuclei Template Execution
Use this diagram to explain template-driven scanning before parsing templates and running the local target.
6. Nuclei Template Scanning¶
Concept¶
Nuclei is a template-driven scanner. Its internal model is easy to teach:
- A YAML template defines metadata, requests, matchers, extractors, and severity.
- The engine expands variables, sends requests, evaluates matchers, and emits structured results.
- A template can detect a CVE, a misconfiguration, a leaked file, a technology fingerprint, or a workflow condition.
Templates are signatures with executable context. The power is speed and community scale. The risk is false positives, unsafe templates, stale checks, and target authorization errors.
This section first analyzes real public Nuclei templates, then runs a local-only template against a local web server created by the notebook.
Audio explanation for original cell 29
Transcript: This Nuclei concept cell explains template-driven scanning. It teaches how YAML metadata, requests, matchers, and extractors become fast executable detection logic, while also naming the risks of unsafe or stale templates.
AI mentor analysis for original cell 29: Nuclei Template Scanning Overview
This cell introduces Nuclei, a template-driven scanner, explaining its structure and functionality. It highlights how YAML templates define scanning parameters and how the engine processes these templates to detect vulnerabilities. The cell emphasizes the speed and scalability of Nuclei due to its community-driven template repository. However, it also notes the risks of false positives and outdated checks. Understanding Nuclei's model helps practitioners leverage its capabilities while being mindful of its limitations, particularly in terms of template management and target authorization.
Discussion question: What are the benefits and risks of using a template-driven scanner like Nuclei?
NUCLEI_2024_API = "https://api.github.com/repos/projectdiscovery/nuclei-templates/contents/http/cves/2024"
nuclei_listing = get_json(NUCLEI_2024_API, cache_seconds=7 * 24 * 3600)
template_items = [item for item in nuclei_listing if item.get("name", "").endswith((".yaml", ".yml"))]
print(f"Template files listed from ProjectDiscovery http/cves/2024: {len(template_items)}")
parsed_templates = []
for item in template_items[:35]:
try:
raw_text = get_bytes(item["download_url"], f"nuclei_{item['name']}", cache_seconds=7 * 24 * 3600).decode("utf-8", errors="replace")
doc = yaml.safe_load(raw_text) or {}
info = doc.get("info", {}) or {}
classification = info.get("classification", {}) or {}
parsed_templates.append({
"id": doc.get("id"),
"name": info.get("name"),
"severity": info.get("severity"),
"tags": ",".join(info.get("tags", []) if isinstance(info.get("tags"), list) else [str(info.get("tags", ""))]),
"cve_id": classification.get("cve-id") or classification.get("cve_id"),
"cvss_score": classification.get("cvss-score") or classification.get("cvss_score"),
"path": item.get("path"),
})
except Exception as exc:
parsed_templates.append({"id": item.get("name"), "parse_error": str(exc), "path": item.get("path")})
nuclei_templates_df = pd.DataFrame(parsed_templates)
display(nuclei_templates_df.head(12))
Template files listed from ProjectDiscovery http/cves/2024: 602
| id | name | severity | tags | cve_id | cvss_score | path | |
|---|---|---|---|---|---|---|---|
| 0 | CVE-2024-0012 | PAN-OS Management Web Interface - Authentication Bypass | critical | cve,cve2024,paloalto,globalprotect,kev,vkev,vuln | CVE-2024-0012 | 9.8 | http/cves/2024/CVE-2024-0012.yaml |
| 1 | CVE-2024-0195 | SpiderFlow Crawler Platform - Remote Code Execution | critical | cve,cve2024,spiderflow,crawler,unauth,rce,ssssssss,vuln | CVE-2024-0195 | 9.8 | http/cves/2024/CVE-2024-0195.yaml |
| 2 | CVE-2024-0200 | Github Enterprise Authenticated Remote Code Execution | critical | cve,cve2024,rce,github,enterprise,vuln | CVE-2024-0200 | 9.8 | http/cves/2024/CVE-2024-0200.yaml |
| 3 | CVE-2024-0204 | Fortra GoAnywhere MFT - Authentication Bypass | critical | packetstorm,cve,cve2024,auth-bypass,goanywhere,fortra,vkev,vuln | CVE-2024-0204 | 9.8 | http/cves/2024/CVE-2024-0204.yaml |
| 4 | CVE-2024-0235 | EventON (Free < 2.2.8, Premium < 4.5.5) - Information Disclosure | medium | cve,cve2024,wp,wordpress,wp-plugin,exposure,eventon,wpscan,myeventon,vkev,vuln | CVE-2024-0235 | 5.3 | http/cves/2024/CVE-2024-0235.yaml |
| 5 | CVE-2024-0250 | Analytics Insights for Google Analytics 4 < 6.3 - Open Redirect | medium | cve,cve2024,wpscan,redirect,wp,wp-plugin,wordpress,analytics-insights,vuln | NaN | NaN | http/cves/2024/CVE-2024-0250.yaml |
| 6 | CVE-2024-0305 | Ncast busiFacade - Remote Command Execution | high | cve,cve2024,ncast,rce,ncast_project,vkev,vuln | CVE-2024-0305 | 7.5 | http/cves/2024/CVE-2024-0305.yaml |
| 7 | CVE-2024-0337 | Travelpayouts <= 1.1.16 - Open Redirect | medium | wpscan,cve,cve2024,wp,wp-plugin,wordpress,redirect,travelpayouts,vuln | CVE-2024-0337 | NaN | http/cves/2024/CVE-2024-0337.yaml |
| 8 | CVE-2024-0352 | Likeshop < 2.5.7.20210311 - Arbitrary File Upload | critical | cve,cve2024,rce,file-upload,likeshop,instrusive,intrusive,vkev,vuln | CVE-2024-0352 | 9.8 | http/cves/2024/CVE-2024-0352.yaml |
| 9 | CVE-2024-0593 | WordPress Simple Job Board - Unauthorized Data Access | medium | cve,cve2024,wp,wordpress,wp-plugin,simple-job-board,exposure,vuln | CVE-2024-0593 | 5.3 | http/cves/2024/CVE-2024-0593.yaml |
| 10 | CVE-2024-0692 | SolarWinds Security Event Manager - Unauthenticated RCE | high | cve,cve2024,solarwinds,event-manager,cisa,vkev,vuln | CVE-2024-0692 | 8.8 | http/cves/2024/CVE-2024-0692.yaml |
| 11 | CVE-2024-0705 | Stripe Payment Plugin for WooCommerce <= 3.7.9 - Unauthenticated SQL Injection | critical | cve,cve2024,wp-plugin,wp,wordpress,woocommerce,stripe,sqli,unauth,time-based | NaN | NaN | http/cves/2024/CVE-2024-0705.yaml |
Audio explanation for original cell 30
Transcript: This code fetches real ProjectDiscovery Nuclei templates and parses their metadata. It teaches how public template repositories can be treated as datasets for studying signature coverage, severity, tags, and CVE mapping.
AI mentor analysis for original cell 30: Analyzing Nuclei Templates for CVE Detection
This code cell retrieves and parses YAML templates from the Nuclei repository, focusing on CVEs for 2024. It extracts metadata such as severity, tags, and CVE identifiers, providing insight into the structure and content of these templates. By analyzing these templates, practitioners can understand how Nuclei detects specific vulnerabilities and the role of community contributions in maintaining an up-to-date scanning capability. This analysis is crucial for assessing the effectiveness of Nuclei in identifying vulnerabilities and ensuring the accuracy of its detections.
Discussion question: How does community contribution impact the effectiveness of Nuclei's template-driven scanning approach?
cluster_input = nuclei_templates_df.fillna("")
cluster_text = (cluster_input["name"].astype(str) + " " + cluster_input["severity"].astype(str) + " " + cluster_input["tags"].astype(str)).tolist()
if len(cluster_text) >= 4 and any(text.strip() for text in cluster_text):
vectorizer = TfidfVectorizer(min_df=1, max_features=1000)
X_templates = vectorizer.fit_transform(cluster_text)
n_clusters = min(4, len(cluster_text))
kmeans = KMeans(n_clusters=n_clusters, n_init=10, random_state=7)
nuclei_templates_df["cluster"] = kmeans.fit_predict(X_templates)
cluster_terms = []
terms = np.array(vectorizer.get_feature_names_out())
for cluster_id in range(n_clusters):
centroid = kmeans.cluster_centers_[cluster_id]
top_terms = terms[np.argsort(centroid)[-8:][::-1]].tolist()
cluster_terms.append({"cluster": cluster_id, "top_terms": ", ".join(top_terms), "count": int((nuclei_templates_df["cluster"] == cluster_id).sum())})
display(pd.DataFrame(cluster_terms))
display(nuclei_templates_df[["id", "name", "severity", "tags", "cluster"]].head(15))
else:
print("Not enough template metadata to cluster.")
| cluster | top_terms | count | |
|---|---|---|---|
| 0 | 0 | wp, wordpress, plugin, sqli, wpscan, vuln, medium, exposure | 10 |
| 1 | 1 | upload, file, intrusive, lms, high, monitorr, likeshop, smart | 7 |
| 2 | 2 | bypass, rce, vkev, critical, authenticated, cve, cve2024, vuln | 14 |
| 3 | 3 | redirect, open, analytics, travelpayouts, fastchat, medium, oss, wp | 4 |
| id | name | severity | tags | cluster | |
|---|---|---|---|---|---|
| 0 | CVE-2024-0012 | PAN-OS Management Web Interface - Authentication Bypass | critical | cve,cve2024,paloalto,globalprotect,kev,vkev,vuln | 2 |
| 1 | CVE-2024-0195 | SpiderFlow Crawler Platform - Remote Code Execution | critical | cve,cve2024,spiderflow,crawler,unauth,rce,ssssssss,vuln | 2 |
| 2 | CVE-2024-0200 | Github Enterprise Authenticated Remote Code Execution | critical | cve,cve2024,rce,github,enterprise,vuln | 2 |
| 3 | CVE-2024-0204 | Fortra GoAnywhere MFT - Authentication Bypass | critical | packetstorm,cve,cve2024,auth-bypass,goanywhere,fortra,vkev,vuln | 2 |
| 4 | CVE-2024-0235 | EventON (Free < 2.2.8, Premium < 4.5.5) - Information Disclosure | medium | cve,cve2024,wp,wordpress,wp-plugin,exposure,eventon,wpscan,myeventon,vkev,vuln | 0 |
| 5 | CVE-2024-0250 | Analytics Insights for Google Analytics 4 < 6.3 - Open Redirect | medium | cve,cve2024,wpscan,redirect,wp,wp-plugin,wordpress,analytics-insights,vuln | 3 |
| 6 | CVE-2024-0305 | Ncast busiFacade - Remote Command Execution | high | cve,cve2024,ncast,rce,ncast_project,vkev,vuln | 2 |
| 7 | CVE-2024-0337 | Travelpayouts <= 1.1.16 - Open Redirect | medium | wpscan,cve,cve2024,wp,wp-plugin,wordpress,redirect,travelpayouts,vuln | 3 |
| 8 | CVE-2024-0352 | Likeshop < 2.5.7.20210311 - Arbitrary File Upload | critical | cve,cve2024,rce,file-upload,likeshop,instrusive,intrusive,vkev,vuln | 1 |
| 9 | CVE-2024-0593 | WordPress Simple Job Board - Unauthorized Data Access | medium | cve,cve2024,wp,wordpress,wp-plugin,simple-job-board,exposure,vuln | 0 |
| 10 | CVE-2024-0692 | SolarWinds Security Event Manager - Unauthenticated RCE | high | cve,cve2024,solarwinds,event-manager,cisa,vkev,vuln | 2 |
| 11 | CVE-2024-0705 | Stripe Payment Plugin for WooCommerce <= 3.7.9 - Unauthenticated SQL Injection | critical | cve,cve2024,wp-plugin,wp,wordpress,woocommerce,stripe,sqli,unauth,time-based | 0 |
| 12 | CVE-2024-0713 | Monitorr Services Configuration - Arbitrary File Upload | high | cve,cve2024,file-upload,intrusive,monitorr,vuln | 1 |
| 13 | CVE-2024-0799 | Arcserve Unified Data Protection - Authentication Bypass | critical | cve,cve2024,arcserve,auth-bypass,vkev | 2 |
| 14 | CVE-2024-0801 | Arcserve Unified Data Protection - Unauthenticated DoS in ASNative.dll | high | cve,cve2024,arcserve,dos,intrusive,vkev | 1 |
Audio explanation for original cell 31
Transcript: This code clusters Nuclei template metadata without labels. It teaches unsupervised learning: the model groups templates by textual similarity so students can see structure emerge from names, tags, and severities.
AI mentor analysis for original cell 31: Clustering Nuclei Templates
This cell demonstrates clustering of Nuclei vulnerability templates using KMeans. It preprocesses template metadata by filling missing values and concatenating fields like name, severity, and tags. TF-IDF vectorization converts text data into numerical format, suitable for clustering. The number of clusters is determined by the minimum of 4 or the number of templates. Top terms for each cluster are extracted to provide insight into common characteristics. This approach helps identify patterns in vulnerability data, aiding in prioritization and response strategies.
Discussion question: How can clustering improve vulnerability management processes?
Plotly visual analytics
This Plotly cell visualizes template severity mix and unsupervised cluster structure from real Nuclei metadata.
# Plotly visual analytics: Nuclei template metadata and unsupervised clusters.
template_plot = nuclei_templates_df.fillna("")
if "severity" in template_plot.columns:
severity_counts = template_plot["severity"].replace("", "unknown").value_counts().reset_index()
severity_counts.columns = ["severity", "count"]
fig = px.bar(severity_counts, x="severity", y="count", color="severity", title="Sampled Nuclei CVE Template Severity Mix")
fig.show()
if "cluster" in template_plot.columns:
fig = px.scatter(
template_plot.reset_index(),
x="index",
y="severity",
color="cluster" if "cluster" in template_plot.columns else "severity",
hover_name="id",
hover_data=["name", "tags"],
title="Unsupervised Nuclei Template Clusters",
)
fig.show()
Mermaid concept diagram before original cell 32: Local-Only Scanner Safety Boundary
This diagram makes the authorization boundary explicit before the notebook runs the real Nuclei engine.
class LocalFirmwareHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/device":
body = b"deviceInfo vendor=SEAS8414 model=LocalLab firmwareVersion=V5.5.52 build=teaching-only"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
return
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), LocalFirmwareHandler)
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
server_thread.start()
target_url = f"http://127.0.0.1:{server.server_port}"
template_path = OUT / "seas8414-local-firmware-disclosure.yaml"
jsonl_path = OUT / "nuclei-local-results.jsonl"
template_path.write_text("\n".join([
"id: seas8414-local-firmware-disclosure",
"info:",
" name: SEAS 8414 Local Firmware Disclosure Teaching Check",
" author: seas8414",
" severity: low",
" description: Detects a local lab firmware version string served by this notebook.",
" tags: local,teaching,firmware",
"http:",
" - method: GET",
" path:",
" - '{{BaseURL}}/device'",
" matchers-condition: and",
" matchers:",
" - type: word",
" words:",
" - 'firmwareVersion=V5.5.52'",
" - type: status",
" status:",
" - 200",
]))
nuclei_bin = shutil.which("nuclei")
if nuclei_bin is None:
print("nuclei is not installed; skipping local engine execution.")
else:
if jsonl_path.exists():
jsonl_path.unlink()
cmd = [nuclei_bin, "-silent", "-duc", "-jsonl", "-ot", "-t", str(template_path), "-it", str(template_path), "-u", target_url, "-timeout", "5", "-retries", "0", "-o", str(jsonl_path)]
print("Running local-only nuclei command:", " ".join(cmd))
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
print("return code:", proc.returncode)
if proc.stderr.strip():
print("stderr sample:", proc.stderr.strip()[:1000])
if proc.stdout.strip():
print("stdout sample:", proc.stdout.strip()[:1000])
if jsonl_path.exists():
results = [json.loads(line) for line in jsonl_path.read_text().splitlines() if line.strip()]
display(pd.DataFrame(results))
else:
print("No JSONL result file was produced.")
server.shutdown()
server.server_close()
Running local-only nuclei command: /opt/homebrew/bin/nuclei -silent -duc -jsonl -ot -t notebook-runtime/seas8414_ch03_master_teaching/outputs/seas8414-local-firmware-disclosure.yaml -it notebook-runtime/seas8414_ch03_master_teaching/outputs/seas8414-local-firmware-disclosure.yaml -u http://127.0.0.1:56543 -timeout 5 -retries 0 -o notebook-runtime/seas8414_ch03_master_teaching/outputs/nuclei-local-results.jsonl
return code: 0
stdout sample: {"template-id":"seas8414-local-firmware-disclosure","template-path":"notebook-runtime/seas8414_ch03_master_teaching/outputs/seas8414-local-firmware-disclosure.yaml","info":{"name":"SEAS 8414 Local Firmware Disclosure Teaching Check","author":["seas8414"],"tags":["local","teaching","firmware"],"description":"Detects a local lab firmware version string served by this notebook.","severity":"low"},"type":"http","host":"127.0.0.1","port":"56543","scheme":"http","url":"http://127.0.0.1:56543","matched-at":"http://127.0.0.1:56543/device","request":"GET /device HTTP/1.1\r\nHost: 127.0.0.1:56543\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/537.36 (KHTML, like Gecko) Version/8.0 Safari/537.36\r\nConnection: close\r\nAccept: */*\r\nAccept-Language: en\r\nAccept-Encoding: gzip\r\n\r\n","response":"HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Length: 85\r\nContent-Type: text/plain\r\nDate: Fri, 29 May 2026 19:20:51 GMT\r\nServer: BaseHTTP/0.6 Python/3.11.15
| template-id | template-path | info | type | host | port | scheme | url | matched-at | request | response | ip | timestamp | curl-command | matcher-status | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | seas8414-local-firmware-disclosure | notebook-runtime/seas8414_ch03_master_teaching/outputs/seas8414-local-firmware-disclosure.yaml | {'name': 'SEAS 8414 Local Firmware Disclosure Teaching Check', 'author': ['seas8414'], 'tags': ['local', 'teaching', 'firmware'], 'descr... | http | 127.0.0.1 | 56543 | http | http://127.0.0.1:56543 | http://127.0.0.1:56543/device | GET /device HTTP/1.1\r\nHost: 127.0.0.1:56543\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/537.36 (KHTML, li... | HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Length: 85\r\nContent-Type: text/plain\r\nDate: Fri, 29 May 2026 19:20:51 GMT\r\nServer:... | 127.0.0.1 | 2026-05-29T15:20:51.946052-04:00 | curl -X 'GET' -d '' -H 'Accept: */*' -H 'Accept-Language: en' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/... | True |
Audio explanation for original cell 32
Transcript: This code starts a local HTTP service, writes a custom Nuclei template, and runs the real Nuclei engine only against loopback. It teaches safe scanner execution, matcher logic, and JSONL evidence capture without touching external targets.
AI mentor analysis for original cell 32: Simulated Local Firmware Disclosure
This cell sets up a simulated HTTP server to mimic a firmware disclosure scenario. It serves a static response containing firmware information when accessed at a specific endpoint. A Nuclei template is created to detect this disclosure by matching the firmware version string and HTTP status code. This setup is used to teach how to create and test vulnerability detection templates in a controlled environment. It emphasizes the importance of understanding server responses and crafting precise detection rules.
Discussion question: What are the benefits of using simulated environments for vulnerability detection training?
Mermaid concept diagram before original cell 33: Default Credential Check Controls
Use this diagram to reinforce that default credential validation must be scoped and low-impact.
7. Default Credential Checking¶
Concept¶
Default credential checking is deceptively simple and operationally dangerous. Internally, a platform should:
- Identify the product or service family first.
- Select a small vendor-specific candidate set.
- Verify only in authorized scope.
- Rate-limit attempts and respect account lockout policies.
- Confirm success through a low-impact authenticated page or endpoint.
- Store evidence without exposing secrets.
Brute forcing is not the lesson here. The lesson is controlled evidence validation. This section loads a real public default-credential dataset but performs checks only against a local HTTP Basic server created by the notebook.
Audio explanation for original cell 33
Transcript: This default credential concept cell explains why credential checks need tight controls. It teaches vendor-first candidate selection, authorization boundaries, rate limits, lockout awareness, and evidence collection without exposing secrets.
AI mentor analysis for original cell 33: Default Credential Checking Concept
This markdown cell outlines the principles of default credential checking, emphasizing controlled and ethical practices. It highlights the importance of identifying the correct product, using a vendor-specific credential set, and respecting security policies like rate limits and account lockouts. The focus is on validating evidence of default credentials without resorting to brute force. This section prepares practitioners to understand the ethical boundaries and technical nuances of credential testing.
Discussion question: Why is it important to limit default credential checks to authorized scopes?
SECLISTS_DEFAULT_CREDS = "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Default-Credentials/default-passwords.csv"
creds_bytes = get_bytes(SECLISTS_DEFAULT_CREDS, "seclists_default_passwords.csv", cache_seconds=7 * 24 * 3600)
try:
default_creds_df = pd.read_csv(io.BytesIO(creds_bytes), on_bad_lines="skip")
except Exception:
default_creds_df = pd.read_csv(io.BytesIO(creds_bytes), header=None, names=["name", "username", "password"], on_bad_lines="skip")
# Normalize likely username/password columns across dataset variants.
cols_lower = {c.lower(): c for c in default_creds_df.columns}
username_col = cols_lower.get("username") or cols_lower.get("user") or default_creds_df.columns[min(1, len(default_creds_df.columns) - 1)]
password_col = cols_lower.get("password") or cols_lower.get("pass") or default_creds_df.columns[min(2, len(default_creds_df.columns) - 1)]
print(f"Default credential rows loaded: {len(default_creds_df):,}")
display(default_creds_df.head(10))
Default credential rows loaded: 2,875
| Vendor | Username | Password | Comments | |
|---|---|---|---|---|
| 0 | 2Wire, Inc. | http | <BLANK> | NaN |
| 1 | 360 Systems | factory | factory | NaN |
| 2 | 3COM | 3comcso | RIP000 | Resets all passwords to defaults |
| 3 | 3COM | <BLANK> | 12345 | NaN |
| 4 | 3COM | <BLANK> | 1234admin | NaN |
| 5 | 3COM | <BLANK> | <BLANK> | NaN |
| 6 | 3COM | <BLANK> | ANYCOM | NaN |
| 7 | 3COM | <BLANK> | ILMI | NaN |
| 8 | 3COM | <BLANK> | PASSWORD | NaN |
| 9 | 3COM | <BLANK> | admin | NaN |
Audio explanation for original cell 34
Transcript: This code loads the real SecLists default credential dataset and normalizes username and password columns. It teaches how public credential corpora can support scoped validation, while avoiding brute-force behavior.
AI mentor analysis for original cell 34: Loading Default Credentials Dataset
This cell loads a public dataset of default credentials from SecLists, handling potential CSV format variations. It normalizes column names to ensure consistent access to username and password data. The dataset provides a basis for testing against local services, illustrating the prevalence of default credentials across vendors. This exercise demonstrates the importance of data preprocessing and normalization when dealing with real-world datasets, ensuring accurate and reliable analysis.
Discussion question: How does normalizing dataset columns aid in credential analysis?
EXPECTED_USER = "admin"
EXPECTED_PASS = "admin"
EXPECTED_TOKEN = "Basic " + base64.b64encode(f"{EXPECTED_USER}:{EXPECTED_PASS}".encode()).decode()
class LocalBasicAuthHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
auth = self.headers.get("Authorization", "")
if auth == EXPECTED_TOKEN:
body = b"authenticated local lab console"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
self.send_response(401)
self.send_header("WWW-Authenticate", 'Basic realm="seas8414-local"')
self.end_headers()
def log_message(self, format, *args):
return
def is_loopback_url(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
return parsed.hostname in {"127.0.0.1", "localhost", "::1"}
def check_http_basic_local(url: str, username: str, password: str) -> dict[str, Any]:
if not is_loopback_url(url):
raise ValueError("Credential checks are restricted to local loopback targets in this notebook.")
response = requests.get(url, auth=(username, password), timeout=3)
return {"username": username, "password_supplied": bool(password), "status_code": response.status_code, "success": response.status_code == 200}
basic_server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), LocalBasicAuthHandler)
basic_thread = threading.Thread(target=basic_server.serve_forever, daemon=True)
basic_thread.start()
login_url = f"http://127.0.0.1:{basic_server.server_port}/"
candidate_pairs = [("admin", "admin"), ("admin", "password"), ("root", "root")]
# Add a few real dataset candidates with short values to demonstrate dataset-driven selection.
for _, row in default_creds_df.head(50).iterrows():
user = str(row.get(username_col, "")).strip()
password = str(row.get(password_col, "")).strip()
if user and password and len(user) <= 16 and len(password) <= 16:
candidate_pairs.append((user, password))
if len(candidate_pairs) >= 8:
break
credential_results = [check_http_basic_local(login_url, u, p) for u, p in candidate_pairs]
basic_server.shutdown()
basic_server.server_close()
display(pd.DataFrame(credential_results))
| username | password_supplied | status_code | success | |
|---|---|---|---|---|
| 0 | admin | True | 200 | True |
| 1 | admin | True | 401 | False |
| 2 | root | True | 401 | False |
| 3 | http | True | 401 | False |
| 4 | factory | True | 401 | False |
| 5 | 3comcso | True | 401 | False |
| 6 | <BLANK> | True | 401 | False |
| 7 | <BLANK> | True | 401 | False |
Audio explanation for original cell 35
Transcript: This code creates a local HTTP Basic Auth target and tests a small candidate set only on loopback. It teaches proof validation with an allowlist, clear success criteria, and no external authentication attempts.
AI mentor analysis for original cell 35: Simulated Local Basic Auth Server
This cell implements a local HTTP server with Basic Authentication to simulate credential checking. It checks incoming requests against expected credentials and returns appropriate HTTP responses. The function `check_http_basic_local` is designed to test credentials against this server, ensuring that checks are restricted to local loopback addresses. This setup provides a safe environment to practice credential validation techniques without impacting real systems.
Discussion question: What are the security implications of testing credentials on non-local targets?
Plotly visual analytics
This Plotly cell visualizes the local-only default credential validation outcomes and reinforces the scope boundary.
# Plotly visual analytics: scoped default credential validation outcomes.
cred_plot = pd.DataFrame(credential_results)
fig = px.histogram(
cred_plot,
x="status_code",
color="success",
barmode="group",
title="Local-Only Default Credential Check Outcomes",
labels={"status_code": "HTTP status", "success": "Authentication succeeded"},
)
fig.show()
Mermaid concept diagram before original cell 36: Firmware Integrity Workflow
This diagram explains why firmware analysis is stronger than banner-only vulnerability inference.
8. Firmware Integrity and Entropy Analysis¶
Concept¶
Firmware analysis connects vulnerability management to supply-chain and embedded-system evidence. A platform may inspect:
- Vendor hashes or signed manifests.
- Bootloader, kernel, filesystem, and package metadata.
- Entropy anomalies that suggest compression, encryption, packed blobs, or appended payloads.
- Known vulnerable components extracted from filesystem images.
- Runtime observations from devices that cannot be safely scanned actively.
The kernel and interrupt vector table matter because low-level firmware can alter control flow before userland services are visible. A network scanner generally cannot prove the integrity of those layers from a banner. It needs firmware files, hardware attestation, secure boot evidence, or on-device collection.
Audio explanation for original cell 36
Transcript: This firmware concept cell connects vulnerability management to supply-chain and embedded evidence. It teaches why firmware hashes, bootloaders, kernels, filesystems, package manifests, and attestation matter beyond service banners.
AI mentor analysis for original cell 36: Firmware Integrity and Entropy Analysis Concept
This markdown cell introduces the concept of firmware integrity and entropy analysis. It explains how analyzing firmware can reveal vulnerabilities related to supply chain and embedded systems. Key aspects include verifying hashes, detecting entropy anomalies, and examining known vulnerable components. The discussion emphasizes the limitations of network scanners in assessing firmware integrity and the need for direct firmware analysis or secure boot evidence.
Discussion question: Why is entropy analysis important in firmware security assessments?
FIRMWARE_URL = "https://downloads.openwrt.org/releases/23.05.0/targets/ath79/generic/openwrt-23.05.0-ath79-generic-8dev_carambola2-initramfs-kernel.bin"
SHA256SUMS_URL = "https://downloads.openwrt.org/releases/23.05.0/targets/ath79/generic/sha256sums"
firmware_name = Path(urllib.parse.urlparse(FIRMWARE_URL).path).name
firmware_bytes = get_bytes(FIRMWARE_URL, firmware_name, cache_seconds=30 * 24 * 3600)
sha256sums_text = get_bytes(SHA256SUMS_URL, "openwrt_23.05.0_ath79_generic_sha256sums.txt", cache_seconds=30 * 24 * 3600).decode("utf-8", errors="replace")
actual_sha256 = hashlib.sha256(firmware_bytes).hexdigest()
expected_sha256 = None
for line in sha256sums_text.splitlines():
parts = line.strip().split()
if len(parts) >= 2 and parts[-1].endswith(firmware_name):
expected_sha256 = parts[0]
break
integrity_result = pd.DataFrame([{
"firmware_file": firmware_name,
"bytes": len(firmware_bytes),
"actual_sha256": actual_sha256,
"expected_sha256": expected_sha256,
"hash_matches_manifest": actual_sha256 == expected_sha256,
}])
display(integrity_result)
| firmware_file | bytes | actual_sha256 | expected_sha256 | hash_matches_manifest | |
|---|---|---|---|---|---|
| 0 | openwrt-23.05.0-ath79-generic-8dev_carambola2-initramfs-kernel.bin | 5608724 | 5e09d72bcff539a2470340e72d7e55180f05d23ef1121938ddaa1a1e67b7d816 | 5e09d72bcff539a2470340e72d7e55180f05d23ef1121938ddaa1a1e67b7d816 | True |
Audio explanation for original cell 37
Transcript: This code downloads an OpenWrt firmware image and the vendor sha256sums manifest, then verifies the hash. It teaches measured firmware integrity checking with a real public image and a real vendor manifest.
AI mentor analysis for original cell 37: Firmware Integrity Verification
This cell verifies the integrity of a downloaded firmware file by comparing its SHA-256 hash against a published manifest. It retrieves both the firmware and the hash manifest from OpenWRT's official release, calculates the hash of the firmware, and checks for a match. This process demonstrates the importance of hash verification in ensuring firmware authenticity and integrity, a critical step in preventing supply chain attacks.
Discussion question: How does hash verification contribute to firmware security?
def shannon_entropy(block: bytes) -> float:
if not block:
return 0.0
counts = np.bincount(np.frombuffer(block, dtype=np.uint8), minlength=256)
probabilities = counts[counts > 0] / len(block)
return float(-np.sum(probabilities * np.log2(probabilities)))
window_size = 4096
step_size = 4096
sample_bytes = firmware_bytes[: min(len(firmware_bytes), 1024 * 1024)]
entropy_rows = []
for offset in range(0, len(sample_bytes) - window_size + 1, step_size):
entropy_rows.append({"offset": offset, "entropy": shannon_entropy(sample_bytes[offset:offset + window_size])})
entropy_df = pd.DataFrame(entropy_rows)
entropy_df["classification"] = np.where(
entropy_df["entropy"] > 7.5,
"high entropy: compressed/encrypted/random-like",
np.where(entropy_df["entropy"] < 1.0, "very low entropy: padding/sparse", "structured code/data"),
)
print("Entropy sample from first MiB of firmware:")
display(entropy_df.head(12))
plt.figure(figsize=(10, 3))
plt.plot(entropy_df["offset"], entropy_df["entropy"], linewidth=1.5)
plt.axhline(7.5, color="red", linestyle="--", linewidth=1, label="high entropy threshold")
plt.title("Firmware Shannon Entropy Profile - first MiB")
plt.xlabel("Byte offset")
plt.ylabel("Entropy bits/byte")
plt.ylim(0, 8.2)
plt.legend()
plt.tight_layout()
plt.show()
Entropy sample from first MiB of firmware:
| offset | entropy | classification | |
|---|---|---|---|
| 0 | 0 | 7.948197 | high entropy: compressed/encrypted/random-like |
| 1 | 4096 | 7.949938 | high entropy: compressed/encrypted/random-like |
| 2 | 8192 | 7.954134 | high entropy: compressed/encrypted/random-like |
| 3 | 12288 | 7.954260 | high entropy: compressed/encrypted/random-like |
| 4 | 16384 | 7.954951 | high entropy: compressed/encrypted/random-like |
| 5 | 20480 | 7.954126 | high entropy: compressed/encrypted/random-like |
| 6 | 24576 | 7.951626 | high entropy: compressed/encrypted/random-like |
| 7 | 28672 | 7.954324 | high entropy: compressed/encrypted/random-like |
| 8 | 32768 | 7.953237 | high entropy: compressed/encrypted/random-like |
| 9 | 36864 | 7.951955 | high entropy: compressed/encrypted/random-like |
| 10 | 40960 | 7.949364 | high entropy: compressed/encrypted/random-like |
| 11 | 45056 | 7.954018 | high entropy: compressed/encrypted/random-like |
Audio explanation for original cell 38
Transcript: This code computes Shannon entropy over firmware windows and plots the profile. It teaches how entropy highlights compression, encryption, sparse regions, or packed data, while also warning that entropy alone is not a malware verdict.
AI mentor analysis for original cell 38: Shannon Entropy Analysis of Firmware
This cell calculates the Shannon entropy of a firmware file to assess its data structure. High entropy indicates compressed or encrypted data, while low entropy suggests padding or sparse data. The analysis uses a sliding window approach to profile entropy across the firmware, classifying sections based on entropy levels. This technique helps identify potentially suspicious areas within firmware, guiding further investigation.
Discussion question: What insights can entropy analysis provide about firmware data structures?
Plotly visual analytics
This Plotly cell turns firmware entropy into an interactive profile for identifying structural regions that deserve follow-up analysis.
# Plotly visual analytics: interactive firmware entropy profile.
fig = px.line(
entropy_df,
x="offset",
y="entropy",
color="classification",
title="Interactive Firmware Entropy Profile - First MiB",
labels={"offset": "Byte offset", "entropy": "Shannon entropy bits/byte"},
)
fig.add_hline(y=7.5, line_dash="dash", line_color="red", annotation_text="high entropy threshold")
fig.show()
Interpretation: firmware evidence vs scanner signatures¶
Entropy is not a malware detector by itself. High entropy often means compression, not compromise. The useful workflow is to combine entropy with extraction, file signatures, hash manifests, SBOM/package correlation, vendor advisories, and runtime evidence.
This is also where zero-days enter the discussion. A zero-day has no published CVE signature at first. Detection shifts toward behavior, exploit preconditions, protocol anomalies, memory-safety crash signals, and compensating controls.
Audio explanation for original cell 39
Transcript: This interpretation cell explains the limits of firmware signatures. It teaches that high entropy must be combined with extraction, manifests, SBOMs, advisories, runtime evidence, and zero-day-aware behavioral reasoning.
AI mentor analysis for original cell 39: Interpreting Firmware Evidence vs Scanner Signatures
This markdown cell discusses the limitations of using entropy as a standalone indicator of compromise. It emphasizes the need to combine entropy analysis with other evidence types, such as file signatures, hash manifests, and runtime observations, to form a comprehensive security assessment. The discussion also touches on the challenges of detecting zero-day vulnerabilities, which require behavioral analysis and anomaly detection rather than reliance on known signatures.
Discussion question: How can multiple evidence types be integrated to improve firmware security assessments?
Mermaid concept diagram before original cell 40: Protocol Grammar Inference
Use this diagram before the PCAP cells to explain how raw packets become structural protocol evidence.
9. Protocol Grammar Inference from PCAP¶
Concept¶
Protocol grammar inference attempts to learn message structure from examples. It supports protocol-aware fuzzing, passive asset identification, anomaly detection, and safe test generation.
The internal workflow is:
- Capture frames or load PCAP evidence.
- Extract protocol stack, frame lengths, fields, timing, and payload bytes.
- Align messages by direction or message type.
- Identify fixed fields, length fields, command bytes, delimiters, checksums, and variable payload regions.
- Use the inferred grammar to drive safer tests and better detection.
This section uses a real public Wireshark DHCP PCAP and local tshark, then performs a simple byte-position profile directly from the pcap bytes.
Audio explanation for original cell 40
Transcript: This protocol grammar concept cell explains how message structure can be inferred from captures. It teaches the workflow from PCAP evidence to fields, frame geometry, byte alignment, and safer protocol-aware fuzzing.
AI mentor analysis for original cell 40: Protocol Grammar Inference from PCAP
This markdown cell introduces the concept of protocol grammar inference from PCAP files. It outlines the process of extracting protocol structures from captured network traffic, which aids in fuzzing, asset identification, and anomaly detection. The workflow involves analyzing protocol stacks, message alignment, and identifying key fields. This approach enhances understanding of network protocols and supports the development of safer and more effective security tests.
Discussion question: What are the benefits of inferring protocol grammar from network traffic?
PCAP_URL = "https://raw.githubusercontent.com/wireshark/wireshark/master/test/captures/dhcp.pcap"
pcap_bytes = get_bytes(PCAP_URL, "wireshark_dhcp.pcap", cache_seconds=30 * 24 * 3600)
pcap_path = DATA / "wireshark_dhcp.pcap"
pcap_path.write_bytes(pcap_bytes)
tshark_bin = shutil.which("tshark")
if tshark_bin is None:
print("tshark is not installed; skipping tshark field extraction.")
tshark_df = pd.DataFrame()
else:
cmd = [
tshark_bin, "-r", str(pcap_path), "-T", "fields",
"-e", "frame.number",
"-e", "frame.len",
"-e", "frame.protocols",
"-e", "udp.srcport",
"-e", "udp.dstport",
"-c", "20",
]
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if proc.returncode != 0:
print(proc.stderr)
tshark_df = pd.DataFrame()
else:
rows = []
for line in proc.stdout.splitlines():
parts = line.split("\t")
while len(parts) < 5:
parts.append("")
rows.append({"frame": parts[0], "len": parts[1], "protocols": parts[2], "udp_src": parts[3], "udp_dst": parts[4]})
tshark_df = pd.DataFrame(rows)
display(tshark_df)
| frame | len | protocols | udp_src | udp_dst | |
|---|---|---|---|---|---|
| 0 | 1 | 314 | eth:ethertype:ip:udp:dhcp | 68 | 67 |
| 1 | 2 | 342 | eth:ethertype:ip:udp:dhcp | 67 | 68 |
| 2 | 3 | 314 | eth:ethertype:ip:udp:dhcp | 68 | 67 |
| 3 | 4 | 342 | eth:ethertype:ip:udp:dhcp | 67 | 68 |
Audio explanation for original cell 41
Transcript: This code downloads a real Wireshark DHCP PCAP and extracts frame fields with local tshark. It teaches that protocol analysis starts by making raw packets into structured field evidence.
AI mentor analysis for original cell 41: PCAP Analysis with Tshark
This cell demonstrates the use of Tshark, a command-line network protocol analyzer, to extract specific fields from a DHCP PCAP file. The script first checks if Tshark is installed, then uses it to parse the PCAP file and extract fields such as frame number, length, protocols, and UDP ports. The extracted data is stored in a DataFrame for further analysis. This approach highlights the importance of automating packet analysis to quickly identify network behaviors and potential vulnerabilities.
Discussion question: How does automating packet analysis with tools like Tshark enhance vulnerability detection?
def parse_classic_pcap_packets(data: bytes, max_packets: int = 25) -> list[dict[str, Any]]:
if len(data) < 24:
return []
magic = data[:4]
if magic == b"\xd4\xc3\xb2\xa1":
endian = "<"
elif magic == b"\xa1\xb2\xc3\xd4":
endian = ">"
else:
raise ValueError(f"Unsupported pcap magic: {magic.hex()}")
packets = []
offset = 24
index = 0
while offset + 16 <= len(data) and index < max_packets:
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(endian + "IIII", data[offset:offset + 16])
offset += 16
payload = data[offset:offset + incl_len]
offset += incl_len
packets.append({"index": index, "ts_sec": ts_sec, "incl_len": incl_len, "orig_len": orig_len, "payload": payload})
index += 1
return packets
packets = parse_classic_pcap_packets(pcap_bytes)
packet_summary = pd.DataFrame([
{"packet_index": p["index"], "incl_len": p["incl_len"], "orig_len": p["orig_len"], "first_16_bytes": p["payload"][:16].hex()}
for p in packets
])
display(packet_summary.head(12))
max_profile_len = min(64, max((len(p["payload"]) for p in packets), default=0))
profile_rows = []
for pos in range(max_profile_len):
values = [p["payload"][pos] for p in packets if len(p["payload"]) > pos]
if not values:
continue
counts = Counter(values)
common_byte, common_count = counts.most_common(1)[0]
profile_rows.append({
"byte_position": pos,
"observed_packets": len(values),
"unique_values": len(counts),
"most_common_hex": f"0x{common_byte:02x}",
"most_common_ratio": common_count / len(values),
})
byte_profile_df = pd.DataFrame(profile_rows)
print("Byte-position stability profile. Fixed fields have low unique_values and high common_ratio.")
display(byte_profile_df.head(24))
| packet_index | incl_len | orig_len | first_16_bytes | |
|---|---|---|---|---|
| 0 | 0 | 314 | 314 | ffffffffffff000b8201fc4208004500 |
| 1 | 1 | 342 | 342 | 000b8201fc42000874adf19b08004500 |
| 2 | 2 | 314 | 314 | ffffffffffff000b8201fc4208004500 |
| 3 | 3 | 342 | 342 | 000b8201fc42000874adf19b08004500 |
Byte-position stability profile. Fixed fields have low unique_values and high common_ratio.
| byte_position | observed_packets | unique_values | most_common_hex | most_common_ratio | |
|---|---|---|---|---|---|
| 0 | 0 | 4 | 2 | 0xff | 0.50 |
| 1 | 1 | 4 | 2 | 0xff | 0.50 |
| 2 | 2 | 4 | 2 | 0xff | 0.50 |
| 3 | 3 | 4 | 2 | 0xff | 0.50 |
| 4 | 4 | 4 | 2 | 0xff | 0.50 |
| 5 | 5 | 4 | 2 | 0xff | 0.50 |
| 6 | 6 | 4 | 1 | 0x00 | 1.00 |
| 7 | 7 | 4 | 2 | 0x0b | 0.50 |
| 8 | 8 | 4 | 2 | 0x82 | 0.50 |
| 9 | 9 | 4 | 2 | 0x01 | 0.50 |
| 10 | 10 | 4 | 2 | 0xfc | 0.50 |
| 11 | 11 | 4 | 2 | 0x42 | 0.50 |
| 12 | 12 | 4 | 1 | 0x08 | 1.00 |
| 13 | 13 | 4 | 1 | 0x00 | 1.00 |
| 14 | 14 | 4 | 1 | 0x45 | 1.00 |
| 15 | 15 | 4 | 1 | 0x00 | 1.00 |
| 16 | 16 | 4 | 1 | 0x01 | 1.00 |
| 17 | 17 | 4 | 2 | 0x2c | 0.50 |
| 18 | 18 | 4 | 2 | 0xa8 | 0.50 |
| 19 | 19 | 4 | 4 | 0x36 | 0.25 |
| 20 | 20 | 4 | 1 | 0x00 | 1.00 |
| 21 | 21 | 4 | 1 | 0x00 | 1.00 |
| 22 | 22 | 4 | 2 | 0xfa | 0.50 |
| 23 | 23 | 4 | 1 | 0x11 | 1.00 |
Audio explanation for original cell 42
Transcript: This code manually parses classic PCAP bytes and builds a byte-position stability profile. It teaches how fixed headers, variable fields, and candidate grammar boundaries can be inferred from repeated packet observations.
AI mentor analysis for original cell 42: PCAP Packet Parsing and Byte Profiling
This cell introduces a function to parse classic PCAP files, extracting packet metadata and payloads. It identifies the file's endianness using the magic number and iterates through packets, extracting timestamps, lengths, and payloads. The analysis includes a byte-position stability profile, highlighting byte positions with low variability, which may indicate fixed fields. This parsing method is crucial for understanding packet structures and identifying anomalies that could signify security issues.
Discussion question: What are the advantages of profiling byte positions in packet analysis?
Plotly visual analytics
This Plotly cell visualizes packet lengths and byte-position stability for protocol grammar inference.
# Plotly visual analytics: PCAP frame geometry and byte-position stability.
if not packet_summary.empty:
fig = px.bar(packet_summary, x="packet_index", y="incl_len", title="PCAP Packet Length Geometry", labels={"incl_len": "Captured length"})
fig.show()
if not byte_profile_df.empty:
fig = px.line(
byte_profile_df,
x="byte_position",
y="most_common_ratio",
hover_data=["unique_values", "most_common_hex", "observed_packets"],
title="Byte-Position Stability Profile for Protocol Grammar Inference",
labels={"most_common_ratio": "Most-common byte ratio"},
)
fig.show()
Mermaid concept diagram before original cell 43: AI-Guided Fuzzing Loop
This diagram explains the safe local fuzzing loop before the toy protocol parser is introduced.
10. LLM-Guided Semantic Mutation Fuzzing, Reinforcement Learning, and Transformers¶
Concept¶
Traditional mutation fuzzing flips bytes and hopes to reach deeper parser states. Protocol fuzzing benefits from semantic mutation: preserve the outer structure while changing length fields, command IDs, checksums, boundary values, and state transitions.
An LLM can assist by reading parser feedback and proposing structured mutations. In a safe production workflow, the LLM should not directly attack third-party targets. It should generate hypotheses, seeds, or templates inside a scoped harness. The harness enforces authorization, rate limits, crash collection, and reproducibility.
This section uses a local toy protocol:
CMD(1 byte) | LEN(1 byte) | DATA(variable) | CHECKSUM(1 byte)
The checksum is sum(frame_without_checksum) mod 256. A special semantic state exists only when the frame is structurally valid and CMD=0x03 with payload CRASH. This is a teaching target, not a real exploit.
Audio explanation for original cell 43
Transcript: This concept cell introduces LLM-guided mutation fuzzing, reinforcement learning, and transformer-based reasoning. It teaches that AI should propose hypotheses inside a scoped harness, not attack arbitrary third-party targets.
AI mentor analysis for original cell 43: LLM-Guided Semantic Mutation Fuzzing
This markdown cell introduces the concept of using large language models (LLMs) to guide semantic mutation fuzzing. Unlike traditional fuzzing, which randomly alters bytes, semantic mutation maintains protocol structure while modifying specific fields. The LLM assists by proposing structured mutations based on parser feedback. This approach is safer and more efficient, as it targets specific protocol states. The cell emphasizes the importance of using a controlled environment to ensure safe fuzzing practices.
Discussion question: How can LLMs improve the efficiency of fuzzing in protocol analysis?
def toy_checksum(data: bytes) -> int:
return sum(data) % 256
def make_toy_frame(cmd: int, payload: bytes) -> bytes:
prefix = bytes([cmd & 0xFF, len(payload) & 0xFF]) + payload
return prefix + bytes([toy_checksum(prefix)])
def parse_toy_frame(frame: bytes) -> dict[str, Any]:
if len(frame) < 3:
return {"state": "too_short", "accepted": False}
cmd = frame[0]
declared_len = frame[1]
expected_total = 3 + declared_len
if len(frame) != expected_total:
return {"state": "length_mismatch", "accepted": False, "declared_len": declared_len, "actual_len": len(frame)}
body = frame[:-1]
expected_crc = toy_checksum(body)
if frame[-1] != expected_crc:
return {"state": "bad_checksum", "accepted": False, "expected_crc": expected_crc, "actual_crc": frame[-1]}
payload = frame[2:-1]
if cmd not in {1, 2, 3}:
return {"state": "unknown_command", "accepted": False, "cmd": cmd}
if cmd == 3 and payload == b"CRASH":
return {"state": "semantic_crash", "accepted": True, "cmd": cmd, "payload": payload.decode()}
return {"state": "valid_no_crash", "accepted": True, "cmd": cmd, "payload_len": len(payload)}
seed_frame = make_toy_frame(1, b"PING")
print("Seed frame:", seed_frame.hex(), parse_toy_frame(seed_frame))
Seed frame: 010450494e4733 {'state': 'valid_no_crash', 'accepted': True, 'cmd': 1, 'payload_len': 4}
Audio explanation for original cell 44
Transcript: This code defines the toy protocol, checksum, frame builder, and parser. It teaches the target model for fuzzing: structure, length, payload, checksum, accepted states, rejection states, and a controlled semantic crash state.
AI mentor analysis for original cell 44: Toy Protocol Frame Parsing
This cell defines functions to create and parse frames for a toy protocol, demonstrating checksum calculation and frame validation. The protocol includes a command, length, data, and checksum. The parser checks for length mismatches, invalid checksums, and unknown commands, and identifies a special semantic state when certain conditions are met. This exercise illustrates the importance of robust protocol parsing and validation in detecting malformed or malicious packets.
Discussion question: Why is it important to validate checksums and lengths in protocol parsing?
def mutate_bit_flip(frame: bytes) -> bytes:
data = bytearray(frame)
idx = random.randrange(len(data))
data[idx] ^= 1 << random.randrange(8)
return bytes(data)
def mutate_length_overflow(frame: bytes) -> bytes:
data = bytearray(frame)
if len(data) >= 2:
data[1] = 0xFF
return bytes(data)
def mutate_bad_checksum(frame: bytes) -> bytes:
data = bytearray(frame)
data[-1] ^= 0xFF
return bytes(data)
def mutate_command_walk(frame: bytes) -> bytes:
payload = frame[2:-1] if len(frame) >= 3 else b""
return make_toy_frame((frame[0] + 1) % 256 if frame else 1, payload)
def mutate_semantic_boundary(frame: bytes) -> bytes:
return make_toy_frame(3, b"CRASH")
operators = {
"bit_flip": mutate_bit_flip,
"length_overflow": mutate_length_overflow,
"bad_checksum": mutate_bad_checksum,
"command_walk": mutate_command_walk,
"semantic_boundary": mutate_semantic_boundary,
}
random_results = []
for i in range(40):
operator_name = random.choice(["bit_flip", "length_overflow", "bad_checksum", "command_walk"])
mutated = operators[operator_name](seed_frame)
feedback = parse_toy_frame(mutated)
random_results.append({"iteration": i, "operator": operator_name, "mutated_hex": mutated.hex(), **feedback})
semantic_results = []
current = seed_frame
for i in range(6):
feedback = parse_toy_frame(current)
if feedback["state"] in {"valid_no_crash", "unknown_command"}:
operator_name = "semantic_boundary"
elif feedback["state"] == "bad_checksum":
operator_name = "command_walk"
else:
operator_name = "command_walk"
current = operators[operator_name](current)
semantic_results.append({"iteration": i, "operator": operator_name, "mutated_hex": current.hex(), **parse_toy_frame(current)})
print("Random mutator state counts:")
display(pd.DataFrame(random_results)["state"].value_counts().rename_axis("state").to_frame("count"))
print("Semantic mutator trace:")
display(pd.DataFrame(semantic_results))
Random mutator state counts:
| count | |
|---|---|
| state | |
| bad_checksum | 20 |
| length_mismatch | 10 |
| valid_no_crash | 10 |
Semantic mutator trace:
| iteration | operator | mutated_hex | state | accepted | cmd | payload | |
|---|---|---|---|---|---|---|---|
| 0 | 0 | semantic_boundary | 0305435241534879 | semantic_crash | True | 3 | CRASH |
| 1 | 1 | command_walk | 040543524153487a | unknown_command | False | 4 | NaN |
| 2 | 2 | semantic_boundary | 0305435241534879 | semantic_crash | True | 3 | CRASH |
| 3 | 3 | command_walk | 040543524153487a | unknown_command | False | 4 | NaN |
| 4 | 4 | semantic_boundary | 0305435241534879 | semantic_crash | True | 3 | CRASH |
| 5 | 5 | command_walk | 040543524153487a | unknown_command | False | 4 | NaN |
Audio explanation for original cell 45
Transcript: This code compares random mutations with semantic mutations. It teaches why protocol-aware mutation reaches deeper parser states than blind byte flipping when length fields, commands, and checksums must remain coherent.
AI mentor analysis for original cell 45: Mutation Operators for Fuzzing
This cell implements various mutation operators for fuzzing a toy protocol, including bit flipping, length overflow, and checksum alteration. Each operator modifies the frame differently to test the protocol's resilience to malformed inputs. The results are analyzed to determine the effectiveness of each mutation strategy. This approach highlights the role of diverse mutation techniques in uncovering protocol vulnerabilities and improving fuzzing strategies.
Discussion question: How do different mutation strategies affect the outcomes of fuzzing tests?
q_values = defaultdict(float)
counts = defaultdict(int)
observed_states = set()
rl_trace = []
current = seed_frame
operator_names = list(operators.keys())
def choose_operator(epsilon: float = 0.25) -> str:
if random.random() < epsilon:
return random.choice(operator_names)
return max(operator_names, key=lambda name: q_values[name])
for step in range(80):
op_name = choose_operator()
mutated = operators[op_name](current)
feedback = parse_toy_frame(mutated)
state = feedback["state"]
reward = 0.0
if state not in observed_states:
reward += 2.0
observed_states.add(state)
if feedback.get("accepted"):
reward += 1.0
if state == "semantic_crash":
reward += 10.0
counts[op_name] += 1
alpha = 1.0 / counts[op_name]
q_values[op_name] = q_values[op_name] + alpha * (reward - q_values[op_name])
current = mutated if feedback.get("accepted") else seed_frame
rl_trace.append({"step": step, "operator": op_name, "state": state, "reward": reward, "q_value": q_values[op_name]})
q_table = pd.DataFrame([{"operator": op, "q_value": q_values[op], "count": counts[op]} for op in operator_names]).sort_values("q_value", ascending=False)
print("Reinforcement-learning operator preferences after local fuzzing:")
display(q_table)
display(pd.DataFrame(rl_trace).tail(12))
Reinforcement-learning operator preferences after local fuzzing:
| operator | q_value | count | |
|---|---|---|---|
| 4 | semantic_boundary | 11.032787 | 61 |
| 3 | command_walk | 1.166667 | 6 |
| 1 | length_overflow | 0.400000 | 5 |
| 2 | bad_checksum | 0.333333 | 6 |
| 0 | bit_flip | 0.000000 | 2 |
| step | operator | state | reward | q_value | |
|---|---|---|---|---|---|
| 68 | 68 | semantic_boundary | semantic_crash | 11.0 | 11.037037 |
| 69 | 69 | length_overflow | length_mismatch | 0.0 | 0.400000 |
| 70 | 70 | semantic_boundary | semantic_crash | 11.0 | 11.036364 |
| 71 | 71 | bad_checksum | bad_checksum | 0.0 | 0.333333 |
| 72 | 72 | semantic_boundary | semantic_crash | 11.0 | 11.035714 |
| 73 | 73 | semantic_boundary | semantic_crash | 11.0 | 11.035088 |
| 74 | 74 | semantic_boundary | semantic_crash | 11.0 | 11.034483 |
| 75 | 75 | command_walk | unknown_command | 0.0 | 1.166667 |
| 76 | 76 | semantic_boundary | semantic_crash | 11.0 | 11.033898 |
| 77 | 77 | semantic_boundary | semantic_crash | 11.0 | 11.033333 |
| 78 | 78 | semantic_boundary | semantic_crash | 11.0 | 11.032787 |
| 79 | 79 | bit_flip | bad_checksum | 0.0 | 0.000000 |
Audio explanation for original cell 46
Transcript: This code implements an epsilon-greedy reinforcement-learning loop over mutation operators. It teaches how rewards for novelty, acceptance, and crash discovery can guide future fuzzing actions.
AI mentor analysis for original cell 46: Reinforcement Learning in Fuzzing
This cell applies reinforcement learning to optimize fuzzing strategies by adjusting operator preferences based on feedback. It uses a Q-learning approach to assign rewards for discovering new states or achieving specific outcomes, such as a semantic crash. The Q-values guide the selection of mutation operators, improving the efficiency of the fuzzing process. This method demonstrates how reinforcement learning can enhance fuzzing by dynamically adapting strategies based on past successes.
Discussion question: What are the benefits of using reinforcement learning to guide fuzzing operations?
Plotly visual analytics
This Plotly cell visualizes parser-state coverage and reinforcement-learning operator preferences from local fuzzing feedback.
# Plotly visual analytics: fuzzing state coverage and RL operator values.
random_state_counts = pd.DataFrame(random_results)["state"].value_counts().reset_index()
random_state_counts.columns = ["state", "count"]
fig = px.bar(random_state_counts, x="state", y="count", color="state", title="Random Mutator Parser-State Coverage")
fig.update_layout(xaxis_tickangle=-25)
fig.show()
fig = px.bar(q_table, x="operator", y="q_value", color="count", title="RL Mutation Operator Preference After Feedback")
fig.update_layout(xaxis_tickangle=-25)
fig.show()
Mermaid concept diagram before original cell 47: Transformer Self-Attention
Use this diagram to explain attention as relationships among protocol tokens before the NumPy implementation.
Transformer and GenAI concept¶
A transformer is useful because self-attention can learn which fields in a sequence influence each other. In protocol analysis, the length byte should attend to the data region, a checksum should attend to every covered byte, and a command byte may attend to a state-specific payload.
The miniature NumPy example below is not a trained LLM. It implements the core attention calculation:
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k))V
That mechanism is the foundation used by larger GenAI and LLM systems. In a vulnerability workflow, transformers can classify reports, summarize evidence, extract affected products, suggest fuzzing mutations, or generate graph queries. They still need scoped execution controls and source-grounded outputs.
Audio explanation for original cell 47
Transcript: This transformer concept cell explains self-attention. It teaches why transformers are relevant to protocol analysis: fields such as length bytes, payload bytes, commands, and checksums influence each other across a sequence.
AI mentor analysis for original cell 47: Transformer and GenAI Concept
This markdown cell explains the application of transformers in protocol analysis, focusing on self-attention mechanisms. Transformers can identify dependencies between fields, such as length bytes and data regions, enhancing protocol understanding. The cell outlines potential uses of transformers in vulnerability workflows, including report classification and evidence extraction. This highlights the transformative potential of AI in cybersecurity, particularly in automating complex analytical tasks.
Discussion question: How can transformers improve the analysis of protocol structures in cybersecurity?
tokens = ["CMD_WRITE", "LEN_05", "DATA_C", "DATA_R", "DATA_A", "DATA_S", "DATA_H", "CRC_OK"]
vocab = {token: i for i, token in enumerate(sorted(set(tokens)))}
embed_dim = 8
rng = np.random.default_rng(7)
embedding_table = rng.normal(0, 0.5, size=(len(vocab), embed_dim))
X_tok = np.vstack([embedding_table[vocab[token]] for token in tokens])
Wq = rng.normal(0, 0.5, size=(embed_dim, embed_dim))
Wk = rng.normal(0, 0.5, size=(embed_dim, embed_dim))
Wv = rng.normal(0, 0.5, size=(embed_dim, embed_dim))
Q = X_tok @ Wq
K = X_tok @ Wk
V = X_tok @ Wv
scores = (Q @ K.T) / math.sqrt(embed_dim)
scores = scores - scores.max(axis=1, keepdims=True)
attention = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True)
context = attention @ V
attention_df = pd.DataFrame(np.round(attention, 3), index=tokens, columns=tokens)
print("Self-attention matrix over toy protocol tokens:")
display(attention_df)
plt.figure(figsize=(8, 5))
plt.imshow(attention, cmap="viridis")
plt.xticks(range(len(tokens)), tokens, rotation=45, ha="right")
plt.yticks(range(len(tokens)), tokens)
plt.colorbar(label="attention weight")
plt.title("Transformer-Style Self-Attention Over Protocol Tokens")
plt.tight_layout()
plt.show()
Self-attention matrix over toy protocol tokens:
| CMD_WRITE | LEN_05 | DATA_C | DATA_R | DATA_A | DATA_S | DATA_H | CRC_OK | |
|---|---|---|---|---|---|---|---|---|
| CMD_WRITE | 0.109 | 0.129 | 0.088 | 0.117 | 0.092 | 0.182 | 0.163 | 0.120 |
| LEN_05 | 0.141 | 0.141 | 0.121 | 0.147 | 0.087 | 0.121 | 0.101 | 0.139 |
| DATA_C | 0.115 | 0.096 | 0.131 | 0.076 | 0.239 | 0.106 | 0.137 | 0.100 |
| DATA_R | 0.115 | 0.117 | 0.188 | 0.178 | 0.103 | 0.103 | 0.088 | 0.108 |
| DATA_A | 0.104 | 0.101 | 0.067 | 0.048 | 0.233 | 0.176 | 0.186 | 0.085 |
| DATA_S | 0.151 | 0.125 | 0.086 | 0.179 | 0.051 | 0.139 | 0.116 | 0.154 |
| DATA_H | 0.124 | 0.143 | 0.118 | 0.118 | 0.110 | 0.148 | 0.121 | 0.117 |
| CRC_OK | 0.110 | 0.120 | 0.126 | 0.120 | 0.134 | 0.149 | 0.134 | 0.106 |
Audio explanation for original cell 48
Transcript: This code implements the self-attention equation with NumPy over toy protocol tokens. It teaches the core transformer mechanism without requiring a large model or GPU, and connects attention weights to protocol-field relationships.
AI mentor analysis for original cell 48: Self-Attention in Toy Protocol Tokens
This cell demonstrates a basic implementation of self-attention using NumPy, applied to tokens of a toy protocol. It calculates attention scores, showing how each token influences others. The visualization of the attention matrix provides insights into token relationships, such as command and data dependencies. This exercise illustrates the core mechanism of transformers, emphasizing their ability to model complex dependencies in sequences, which is crucial for protocol analysis and vulnerability detection.
Discussion question: What insights can self-attention provide in understanding protocol token dependencies?
Plotly visual analytics
This Plotly cell visualizes transformer-style attention across protocol tokens.
# Plotly visual analytics: transformer-style protocol-token attention.
fig = px.imshow(
attention_df.astype(float),
text_auto=True,
color_continuous_scale="Viridis",
title="Interactive Self-Attention Heatmap Over Protocol Tokens",
)
fig.update_layout(xaxis_title="Key token", yaxis_title="Query token")
fig.show()
Mermaid concept diagram before original cell 49: Evidence Graph and AR Export
This diagram explains why vulnerability evidence becomes a graph and how that graph can feed later visual interfaces.
11. Evidence Graphs and AR-Ready Export¶
Concept¶
Graph analytics are a natural representation for vulnerability evidence:
- Asset nodes connect to observed services.
- Services connect to CPEs.
- CPEs connect to CVEs.
- CVEs connect to KEV, EPSS, exploit references, controls, owners, tickets, and compensating detections.
The same graph can support prioritization, blast-radius analysis, attack-path reasoning, and augmented-reality or 3D visualizations. The export below includes 3D coordinates so it can be loaded by a future Streamlit, WebGL, or AR viewer.
Audio explanation for original cell 49
Transcript: This evidence graph concept cell explains why vulnerabilities are naturally graph-shaped. It teaches how assets, services, CPEs, CVEs, KEV signals, EPSS values, and actions connect into an analysis graph.
AI mentor analysis for original cell 49: Evidence Graphs and AR-Ready Export
This markdown cell discusses the use of graph analytics to represent vulnerability evidence, connecting assets, services, CPEs, and CVEs. Graphs facilitate prioritization, attack-path analysis, and visualization, including augmented reality. The cell emphasizes the versatility of graphs in cybersecurity, enabling comprehensive analysis and visualization of complex relationships between vulnerabilities and their contexts. This approach underscores the importance of structured data representation in vulnerability management.
Discussion question: How do graph representations enhance the analysis and visualization of vulnerability data?
G = nx.DiGraph()
asset_id = "asset:lab-openssh-router"
service_id = "service:ssh:22"
cpe_id = f"cpe:{selected_cpe}"
G.add_node(asset_id, label="Lab OpenSSH Router", type="asset")
G.add_node(service_id, label="SSH TCP/22", type="service")
G.add_node(cpe_id, label=selected_cpe, type="cpe")
G.add_edge(asset_id, service_id, relation="exposes")
G.add_edge(service_id, cpe_id, relation="identified_as")
for _, row in kev_triage.sort_values("triage_score", ascending=False).head(10).iterrows():
cve_node = f"cve:{row['cve_id']}"
G.add_node(cve_node, label=row["cve_id"], type="cve", base_score=float(row["base_score"]), epss=float(row["epss"]), in_kev=bool(row["in_kev"]))
G.add_edge(cpe_id, cve_node, relation="affected_by")
if row["in_kev"]:
kev_node = "intel:CISA_KEV"
G.add_node(kev_node, label="CISA KEV", type="intel")
G.add_edge(cve_node, kev_node, relation="listed_in")
action_node = f"action:patch-or-mitigate:{row['cve_id']}"
G.add_node(action_node, label=f"Patch or mitigate {row['cve_id']}", type="action", triage_score=float(row["triage_score"]))
G.add_edge(cve_node, action_node, relation="drives")
positions = nx.spring_layout(G, dim=3, seed=7)
graph_payload = {
"metadata": {
"course": "SEAS-8414",
"chapter": 3,
"description": "AR-ready vulnerability evidence graph export with 3D coordinates.",
"selected_cpe": selected_cpe,
},
"nodes": [
{
"id": node,
"label": attrs.get("label", node),
"type": attrs.get("type", "unknown"),
"attributes": {k: v for k, v in attrs.items() if k not in {"label", "type"}},
"position": {"x": float(positions[node][0]), "y": float(positions[node][1]), "z": float(positions[node][2])},
}
for node, attrs in G.nodes(data=True)
],
"edges": [
{"source": u, "target": v, "relation": attrs.get("relation", "related")}
for u, v, attrs in G.edges(data=True)
],
}
graph_path = OUT / "chapter03_evidence_graph_ar_ready.json"
graph_path.write_text(json.dumps(graph_payload, indent=2))
print(f"Graph exported: {graph_path}")
print(f"Nodes: {G.number_of_nodes()}, edges: {G.number_of_edges()}")
type_counts = pd.Series([attrs.get("type", "unknown") for _, attrs in G.nodes(data=True)]).value_counts().rename_axis("node_type").to_frame("count")
display(type_counts)
Graph exported: notebook-runtime/seas8414_ch03_master_teaching/outputs/chapter03_evidence_graph_ar_ready.json Nodes: 23, edges: 22
| count | |
|---|---|
| node_type | |
| cve | 10 |
| action | 10 |
| asset | 1 |
| service | 1 |
| cpe | 1 |
Audio explanation for original cell 50
Transcript: This code builds a NetworkX evidence graph and exports AR-ready JSON with 3D coordinates. It teaches how graph analytics can feed future Streamlit, WebGL, or augmented-reality visualizations.
AI mentor analysis for original cell 50: Building and Exporting an Evidence Graph
This cell constructs a directed graph representing vulnerability evidence, linking assets, services, CPEs, and CVEs. It includes nodes for actions and intelligence sources, with edges denoting relationships like exposure and impact. The graph is exported with 3D coordinates for potential use in AR applications. This exercise demonstrates the practical application of graph theory in cybersecurity, enabling detailed analysis and visualization of vulnerability data for better decision-making and communication.
Discussion question: What are the advantages of using 3D graphs for visualizing vulnerability evidence?
Plotly visual analytics
This Plotly cell visualizes the AR-ready evidence graph in 3D and summarizes node types.
# Plotly visual analytics: AR-ready 3D evidence graph coordinates.
graph_nodes = pd.DataFrame(graph_payload["nodes"])
graph_nodes["x"] = graph_nodes["position"].apply(lambda p: p["x"])
graph_nodes["y"] = graph_nodes["position"].apply(lambda p: p["y"])
graph_nodes["z"] = graph_nodes["position"].apply(lambda p: p["z"])
fig = px.scatter_3d(
graph_nodes,
x="x",
y="y",
z="z",
color="type",
hover_name="label",
title="AR-Ready 3D Vulnerability Evidence Graph Coordinates",
)
fig.show()
node_type_counts = graph_nodes["type"].value_counts().reset_index()
node_type_counts.columns = ["type", "count"]
fig = px.bar(node_type_counts, x="type", y="count", color="type", title="Evidence Graph Node-Type Mix")
fig.show()
plt.figure(figsize=(9, 6))
pos2d = {node: (coords[0], coords[1]) for node, coords in positions.items()}
node_colors = []
color_map = {"asset": "#4c78a8", "service": "#72b7b2", "cpe": "#f58518", "cve": "#e45756", "intel": "#54a24b", "action": "#b279a2"}
for node, attrs in G.nodes(data=True):
node_colors.append(color_map.get(attrs.get("type", "unknown"), "#bab0ac"))
nx.draw_networkx_nodes(G, pos2d, node_color=node_colors, node_size=450, alpha=0.9)
nx.draw_networkx_edges(G, pos2d, arrows=True, arrowstyle="-|>", width=1.0, alpha=0.5)
labels = {node: attrs.get("label", node).replace("cpe:2.3:a:openbsd:openssh:", "OpenSSH ")[:30] for node, attrs in G.nodes(data=True)}
nx.draw_networkx_labels(G, pos2d, labels=labels, font_size=7)
plt.title("Chapter 3 Evidence Graph")
plt.axis("off")
plt.tight_layout()
plt.show()
Audio explanation for original cell 51
Transcript: This code plots the evidence graph in two dimensions. It teaches how the same graph can become an instructor-facing visualization of asset, service, vulnerability, intelligence, and remediation relationships.
AI mentor analysis for original cell 51: Visualizing Evidence Graph
This cell generates a visual representation of an evidence graph using NetworkX and Matplotlib. It assigns colors to nodes based on their type, such as assets, services, and vulnerabilities, using a predefined color map. The graph is drawn with nodes, directed edges, and labels, which are truncated for readability. This visualization aids in understanding the relationships and interactions between different cybersecurity elements, providing a clear overview of the network's security posture. It is a simulated/local visualization, not based on live data.
Discussion question: How can visualizing an evidence graph enhance our understanding of network vulnerabilities?
Mermaid concept diagram before original cell 52: Commercial IoT and OT Platform Pattern
Use this diagram to compare scanner, passive, agent, and enrichment layers in commercial platforms.
12. Commercial IoT/OT Platform Patterns¶
Concept¶
Commercial IoT/OT security platforms usually combine several detection modes because no single mode is sufficient:
| Platform family | Common internal pattern | Strength | Limitation |
|---|---|---|---|
| Greenbone/OpenVAS and Nessus-style scanners | Active probes, plugin feeds, authenticated checks, report normalization | Direct vulnerability evidence and broad CVE coverage | Operational risk if scans are not tuned; false positives from weak version evidence |
| ProjectDiscovery/Nuclei-style engines | YAML templates, matchers, extractors, fast HTTP/protocol checks | Rapid community-scale checks and CI integration | Template quality and authorization discipline matter |
| Tenable OT / Microsoft Defender for IoT / Claroty / Armis style platforms | Passive network sensors, asset inventory, protocol decoding, threat intel, vulnerability mapping | Safer for fragile OT/IoT environments and strong asset visibility | May infer versions indirectly; often needs integrations for patch truth |
| Agent or authenticated inventory platforms | Local package, kernel, registry, container, and SBOM evidence | Strong patch-state evidence | Deployment friction and credential/agent lifecycle management |
The high-level architecture is typically:
- Discover assets through passive traffic, active scans, agents, cloud APIs, or import connectors.
- Identify device type, vendor, model, firmware, OS, services, and software.
- Normalize evidence to product identifiers such as CPE, PURL, package coordinates, or proprietary fingerprints.
- Enrich with CVE, KEV, EPSS, exploit, malware, and vendor advisory intelligence.
- Prioritize using exposure, business role, compensating controls, exploitability, and policy.
- Integrate into ticketing, SIEM, SOAR, CMDB, patch management, and exception workflows.
For IoT/OT environments, passive discovery is often the default because active scanning can disrupt legacy devices. Advanced platforms still use active checks, but they scope and schedule them carefully.
Audio explanation for original cell 52
Transcript: This commercial platform concept cell compares scanner-style, template-style, passive IoT and OT, and agent-based platforms. It teaches how vendors combine discovery, fingerprinting, vulnerability intelligence, prioritization, and workflow integration.
AI mentor analysis for original cell 52: Commercial IoT/OT Platform Patterns
This markdown cell outlines common patterns in commercial IoT/OT security platforms, highlighting their detection modes, strengths, and limitations. It categorizes platforms into families like scanners, engines, and passive sensors, each with unique capabilities such as vulnerability evidence, rapid checks, and asset visibility. The cell emphasizes the importance of combining multiple detection methods to cover the diverse security needs of IoT/OT environments. This analysis is crucial for practitioners to understand the trade-offs and integration requirements of different security solutions.
Discussion question: What are the key factors to consider when selecting a security platform for IoT/OT environments?
Mermaid concept diagram before original cell 53: Notebook to Streamlit Path
This diagram explains how notebook outputs become reusable application artifacts.
13. Streamlit Conversion Scaffold¶
The notebook is the teaching source. A Streamlit app can reuse the same cached datasets and outputs. The starter file below loads the exported evidence graph and gives you a clean entry point for turning the notebook into an interactive dashboard later.
Audio explanation for original cell 53
Transcript: This Streamlit scaffold concept cell explains the migration path from notebook to application. It teaches that the notebook is the teaching source, while cached outputs and graph artifacts can power a reusable dashboard.
AI mentor analysis for original cell 53: Streamlit Conversion Scaffold
This markdown cell introduces the concept of converting the notebook into a Streamlit app, which can provide an interactive dashboard for vulnerability analytics. It suggests reusing cached datasets and outputs from the notebook to create a dynamic user interface. This approach allows practitioners to leverage the analytical capabilities of the notebook in a more accessible and user-friendly format, facilitating real-time data exploration and decision-making.
Discussion question: What are the benefits of converting a static notebook into an interactive Streamlit app for cybersecurity analysis?
streamlit_app = OUT / "chapter03_streamlit_app.py"
streamlit_source = "\n".join([
"from pathlib import Path",
"import json",
"import pandas as pd",
"import streamlit as st",
"",
"st.set_page_config(page_title='SEAS-8414 Chapter 3 Vulnerability Analytics', layout='wide')",
"st.title('SEAS-8414 Chapter 3 Vulnerability Analytics')",
"base = Path.home() / 'Downloads' / 'seas8414_ch03_master_teaching'",
"graph_path = base / 'outputs' / 'chapter03_evidence_graph_ar_ready.json'",
"if not graph_path.exists():",
" st.warning('Run the notebook first to generate the evidence graph.')",
" st.stop()",
"graph = json.loads(graph_path.read_text())",
"st.subheader('Evidence Graph Summary')",
"st.write(graph.get('metadata', {}))",
"nodes = pd.DataFrame(graph['nodes'])",
"edges = pd.DataFrame(graph['edges'])",
"left, right = st.columns(2)",
"with left:",
" st.metric('Nodes', len(nodes))",
" st.dataframe(nodes[['id', 'label', 'type']], use_container_width=True)",
"with right:",
" st.metric('Edges', len(edges))",
" st.dataframe(edges, use_container_width=True)",
])
streamlit_app.write_text(streamlit_source)
print(f"Starter Streamlit app written to: {streamlit_app}")
print("Run later with: streamlit run", streamlit_app)
Starter Streamlit app written to: notebook-runtime/seas8414_ch03_master_teaching/outputs/chapter03_streamlit_app.py Run later with: streamlit run notebook-runtime/seas8414_ch03_master_teaching/outputs/chapter03_streamlit_app.py
Audio explanation for original cell 54
Transcript: This code writes a starter Streamlit app that loads the evidence graph and displays nodes and edges. It teaches how to preserve the notebook's evidence pipeline while turning it into an interactive teaching application.
AI mentor analysis for original cell 54: Creating a Streamlit App for Vulnerability Analytics
This code cell generates a starter Streamlit application that loads and displays an evidence graph. It checks for the existence of the graph file and provides a warning if it is absent. The app displays metadata, nodes, and edges from the graph, offering an interactive way to explore the data. This setup allows practitioners to visualize and analyze vulnerability data in a structured format, enhancing their ability to derive insights from complex datasets.
Discussion question: How does an interactive dashboard improve the analysis and communication of vulnerability data?
14. Closing Synthesis¶
Chapter 3 is not about memorizing tools. It is about understanding evidence pipelines.
- CPE-to-CVE mapping teaches structured vulnerability correlation.
- KEV and EPSS teach exploit intelligence enrichment.
- OpenVAS/GVM and Nuclei teach scanner execution models.
- Default credential checks teach scoped proof validation and operational safety.
- Firmware integrity teaches supply-chain and embedded evidence limits.
- Protocol grammar inference teaches how raw wire data becomes structured tests.
- LLM-guided mutation, RL, transformers, and graphs teach how AI can assist without replacing source-grounded evidence.
The mature practitioner question is always: what exactly did we observe, how reliable is the mapping, what source supports the claim, and what action follows?
Audio explanation for original cell 55
Transcript: This closing synthesis cell ties the chapter together. It teaches that the mature practitioner focuses on observed evidence, mapping confidence, source support, and the action that follows.
AI mentor analysis for original cell 55: Closing Synthesis on Evidence Pipelines
This markdown cell synthesizes the key learnings from Chapter 3, emphasizing the importance of understanding evidence pipelines rather than memorizing tools. It covers various aspects of vulnerability analysis, such as CPE-to-CVE mapping, exploit intelligence, scanner models, and AI-assisted analysis. The cell encourages practitioners to critically evaluate the reliability and source of evidence, highlighting the need for a mature approach to cybersecurity analysis that focuses on actionable insights.
Discussion question: Why is it important to focus on evidence pipelines rather than individual tools in cybersecurity?
Mermaid concept diagram before original cell 56: Audit Trail and Artifacts
Use this diagram at the end to explain how students verify what the notebook fetched and produced.
print("Recent source events:")
display(show_source_events(20))
print("Notebook artifacts:")
for path in sorted(OUT.glob("*")):
print(path)
Recent source events:
| url | file | source | |
|---|---|---|---|
| 0 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10146.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10146.yaml | cache |
| 1 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10152.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10152.yaml | cache |
| 2 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-1021.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-1021.yaml | cache |
| 3 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10400.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10400.yaml | cache |
| 4 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10486.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10486.yaml | cache |
| 5 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10516.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10516.yaml | cache |
| 6 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10571.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10571.yaml | cache |
| 7 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-1061.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-1061.yaml | cache |
| 8 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10708.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10708.yaml | cache |
| 9 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-1071.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-1071.yaml | cache |
| 10 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10763.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10763.yaml | cache |
| 11 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10783.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10783.yaml | cache |
| 12 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10812.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10812.yaml | cache |
| 13 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10908.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10908.yaml | cache |
| 14 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10914.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10914.yaml | cache |
| 15 | https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/http/cves/2024/CVE-2024-10915.yaml | notebook-runtime/seas8414_ch03_master_teaching/data/nuclei_CVE-2024-10915.yaml | cache |
| 16 | https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Default-Credentials/default-passwords.csv | notebook-runtime/seas8414_ch03_master_teaching/data/seclists_default_passwords.csv | cache |
| 17 | https://downloads.openwrt.org/releases/23.05.0/targets/ath79/generic/openwrt-23.05.0-ath79-generic-8dev_carambola2-initramfs-kernel.bin | notebook-runtime/seas8414_ch03_master_teaching/data/openwrt-23.05.0-ath79-generic-8dev_carambola2-initramfs-kernel.bin | cache |
| 18 | https://downloads.openwrt.org/releases/23.05.0/targets/ath79/generic/sha256sums | notebook-runtime/seas8414_ch03_master_teaching/data/openwrt_23.05.0_ath79_generic_sha256sums.txt | cache |
| 19 | https://raw.githubusercontent.com/wireshark/wireshark/master/test/captures/dhcp.pcap | notebook-runtime/seas8414_ch03_master_teaching/data/wireshark_dhcp.pcap | cache |
Notebook artifacts: notebook-runtime/seas8414_ch03_master_teaching/outputs/__pycache__ notebook-runtime/seas8414_ch03_master_teaching/outputs/ai_cell_enrichment_manifest.json notebook-runtime/seas8414_ch03_master_teaching/outputs/chapter03_evidence_graph_ar_ready.json notebook-runtime/seas8414_ch03_master_teaching/outputs/chapter03_streamlit_app.py notebook-runtime/seas8414_ch03_master_teaching/outputs/nuclei-local-results.jsonl notebook-runtime/seas8414_ch03_master_teaching/outputs/seas8414-local-firmware-disclosure.yaml
Audio explanation for original cell 56
Transcript: This final code cell displays recent source events and generated artifacts. It teaches auditability: a teaching notebook should show where its data came from and what outputs it produced.
AI mentor analysis for original cell 56: Displaying Recent Source Events and Artifacts
This code cell prints recent source events and lists notebook artifacts, providing a snapshot of the latest updates and outputs. It uses a function to display recent events, likely from a vulnerability database or template repository, and lists files generated during the notebook's execution. This information helps practitioners keep track of changes and outputs, ensuring they have the latest data for analysis and decision-making. The cell demonstrates the importance of maintaining an up-to-date view of security events and artifacts.
Discussion question: How can tracking recent source events and artifacts enhance vulnerability management processes?
Plotly visual analytics
This Plotly cell visualizes the notebook source retrieval audit trail so students can inspect provenance.
# Plotly visual analytics: source retrieval audit trail.
source_df = show_source_events(200)
if not source_df.empty and "source" in source_df.columns:
source_counts = source_df["source"].fillna("unknown").value_counts().reset_index()
source_counts.columns = ["source", "count"]
fig = px.bar(source_counts, x="source", y="count", color="source", title="Notebook Data Retrieval Audit Trail")
fig.show()