How to Build Your Own OT Threat Intelligence Dashboard with Open-Source Tools
Hey, OT defender. Tired of sifting through a dozen threat-intel feeds, Excel spreadsheets, and PDF reports just to figure out what’s relevant to your industrial network? Yeah, me too. That’s why I built my own OT-focused threat intelligence dashboard-and you can too.
I’m Zoroasta (🐟), and today I’m giving you the step-by-step guide to building a dashboard that actually shows you the threats that matter to your OT environment. No six-figure SIEM license required. Just open-source tools, a weekend of tinkering, and a healthy dose of trout-level stubbornness.
Why Bother Building Your Own Dashboard?
- Customization. Generic dashboards are like oversized suits-they cover the basics but don’t fit your specific OT assets.
- Cost. Commercial OT threat-intel platforms can cost more than your annual coffee budget. Open-source is free (minus your time).
- Learning. Building it yourself forces you to understand the data, the pipelines, and the detection logic. That knowledge is priceless during an incident.
- Control. You own the data, the pipeline, and the output. No vendor lock-in, no surprise license renewals.
- Collects OT-relevant threat intelligence from open-source feeds.
- Enriches that data with context (asset mapping, CVSS scores, etc.).
- Correlates it with your internal asset inventory.
- Visualizes it in a dashboard that highlights what you need to know.
- Feed ingestion: Python scripts + RSS/CSV/JSON parsers.
- Data store: PostgreSQL (or SQLite for lightweight).
- Enrichment: VirusTotal API (free tier), Shodan API (free credits), CVE databases.
- Correlation: Simple Python matching against your asset list.
- Dashboard: Grafana (free, gorgeous, flexible).
- Alerting: Telegram bot or email notifications.
- Fetches feed data (using `feedparser` for RSS, `requests` for APIs).
- Parses and extracts key fields (CVE ID, description, affected vendors, CVSS score).
- Stores in a PostgreSQL table.
- VirusTotal API: Check if any IOCs (IPs, domains, hashes) are known malicious.
- Shodan API: See how many of your external IPs are running the vulnerable service.
- CVE Details: Use the `cve-search` project (open-source) to get EPSS scores, exploitability, and more.
- Asset name
- IP address
- Vendor (Siemens, Rockwell, Schneider, etc.)
- Product/version
- Criticality (High/Medium/Low)
- Threat Feed Overview: Bar chart of threats by source (CISA, Dragos, etc.).
- CVE Severity Breakdown: Pie chart of CVSS scores (Critical, High, Medium).
- Affected Assets: Table showing which of your assets are impacted by recent CVEs.
- Exposure Over Time: Line graph of new OT vulnerabilities per week.
- Top Affected Vendors: Bar chart of Siemens vs. Rockwell vs. Schneider, etc.
- New CVE with CVSS ≥ 9.0 that affects your vendor.
- Shodan detection of a vulnerable service on your external IP.
- CISA advisory mentioning a product you use.
- Schedule your feed-ingestion script to run hourly (cron job).
- Version-control your code (GitHub, GitLab).
- Document the setup for your team.
- Test with a tabletop exercise: “A critical CVE drops-how does our dashboard help?”
- CISA publishes ICS advisory for a Siemens SIMATIC vulnerability (CVSS 9.8).
- Your script ingests it within the hour.
- Enrichment finds 500 exposed devices on Shodan.
- Correlation matches against your asset inventory-you have 12 Siemens PLCs.
- Dashboard lights up with a red alert.
- Telegram bot sends you: “🚨 Critical CVE affects Siemens SIMATIC. 12 internal assets impacted. 500 devices exposed worldwide. Take action.”
- Too many feeds. Start with 2-3. Add more only when you’re comfortable.
- Ignoring false positives. Not every CVE is exploitable in your environment. Tune your correlation logic.
- Forgetting maintenance. Update your asset inventory regularly. Out-of-date inventory = useless dashboard.
- No response plan. The dashboard is useless if you don’t act on the alerts. Create playbooks for critical alerts.
- Python 3.x + `pip install requests feedparser psycopg2 python-shodan`
- PostgreSQL (or SQLite for small setups)
- Grafana (docker run -name grafana -p 3000:3000 grafana/grafana)
- cve-search (optional but helpful)
- Telegram Bot API (for alerts)
The Architecture (Keep It Simple)
We’ll build a pipeline that:
Here’s the tech stack:
Step 1: Gather Your OT-Specific Feeds
Start with these free, OT-focused intelligence sources:
| Feed | Format | What it gives you |
|——|——–|——————-|
| CISA ICS Advisories | RSS/JSON | CVEs affecting ICS/OT products |
| ICS-CERT Alerts | RSS | Vulnerability and incident alerts |
| Dragos OT Threat Intelligence (public blog) | RSS | Threat-actor analysis, campaigns |
| MITRE ATT&CK for ICS | GitHub repo | TTPs mapped to OT threats |
| Shodan (search results) | API | Exposed ICS devices, services |
| Twitter lists (follow @CISAgov, @ICS\_CERT, @DragosInc) | API | Real-time updates, chatter |
Pro tip: Don’t overload yourself. Start with CISA ICS advisories and Shodan. Add more as you get comfortable.
Step 2: Set Up the Data Pipeline
We’ll use Python because it’s the duct tape of cybersecurity. Create a script that:
Example table schema:
“`sql
CREATE TABLE ot_threat_intel (
id SERIAL PRIMARY KEY,
source VARCHAR(100),
title TEXT,
description TEXT,
cve_id VARCHAR(20),
affected_vendors TEXT[],
cvss_score FLOAT,
publication_date TIMESTAMP,
raw_data JSONB,
ingested_at TIMESTAMP DEFAULT NOW()
);
“`
Step 3: Enrich with Context
Raw CVEs are boring. Enrich them to answer “So what?” for your environment.
Example enrichment function:
“`python
def enrich_with_shodan(cve):
# Search Shodan for vulnerable service
results = shodan.search(f’product:”{cve.affected_product}”‘)
return {
‘exposed_count’: results[‘total’],
‘top_ports’: [result[‘port’] for result in results[‘matches’][:5]]
}
“`
Step 4: Correlate with Your Asset Inventory
This is where the magic happens. Create a simple asset inventory (CSV is fine) with:
Then, match incoming threats against this list:
“`python
def match_threat_to_assets(threat, assets):
matches = []
for asset in assets:
if threat[‘affected_vendor’] in asset[‘vendor’]:
matches.append(asset)
return matches
“`
Now you know which of your assets are affected by a new CVE.
Step 5: Build the Grafana Dashboard
Grafana is your best friend here. Install it (Docker makes it easy), connect to your PostgreSQL database, and create panels for:
Make it pretty: Use color coding (red for critical, orange for high). Add drill-down links to the CISA advisory.
Step 6: Set Up Alerting
You don’t want to stare at the dashboard all day. Set up alerts for:
Use Grafana’s alerting (to Slack, Telegram, email) or write a simple Python script that sends a Telegram message.
Step 7: Operationalize It
Example Dashboard in Action
Imagine this scenario:
You now have context, priority, and direction-in minutes, not days.
Common Pitfalls (And How to Avoid Them)
The Open-Source Tools You’ll Need
Your Weekend Project Plan
Friday evening: Set up PostgreSQL and Grafana. Write the feed-ingestion script for CISA ICS advisories.
Saturday morning: Add enrichment (Shodan, CVSS). Create asset inventory CSV.
Saturday afternoon: Build Grafana dashboard panels.
Saturday evening: Set up alerting (Telegram bot).
Sunday: Test with real data. Document everything. Pat yourself on the back.
The Bottom Line
Building your own OT threat intelligence dashboard isn’t just a technical exercise-it’s a mindset shift. You move from passive consumer of threat intel to active analyst, correlating global threats with your specific environment.
It’s also a hell of a lot cheaper than buying a commercial platform, and you’ll learn more in one weekend than in a year of vendor demos.
Start small, iterate, and soon you’ll have a dashboard that actually helps you protect your OT network. And when the next big OT vulnerability drops, you’ll be ready-not scrambling.
—
Zoroasta (trout) – Vice President, Cyborama OT Intelligence. Your OT OSINT sidekick who believes the best tools are the ones you build yourself. 🐟
P.S. Want the starter code? Email me at jeffgray@cyborama.com with “OT Dashboard Code” and I’ll send you the Python scripts and Grafana JSON.