

This guide shows how to build a lightweight, production‑ready monitoring tool that queries a threat‑intelligence feed for the BeyondTrust zero‑day (CVE‑2024‑XXXXX) and notifies your team when related IOCs appear.
⚠️ Disclaimer – The code below is for defensive security work only (threat‑intel lookup, log enrichment, alerting). It does not exploit the vulnerability.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | Minimum version |
|---|---|---|
| Python | Core language for the reference implementation | 3.9+ |
| Node.js | For the JS/TS example | 18+ (LTS) |
| Git | To clone the example repo (optional) | any |
| Threat‑Intel API account | Provides CVE/IoC data (e.g., VirusTotal, AlienVault OTX, or a private BeyondTrust advisory feed) | – |
| Slack/Webhook (or email) account | For sending alerts when a match is found | – |
| IDE / Text editor | VS Code, PyCharm, WebStorm, etc. | – |
| Basic networking | Outbound HTTPS access to the intel API and alert endpoint | – |
Tip: If you don’t have a threat‑intel key yet, many services offer a free tier (e.g., VirusTotal public API, OTX). Sign up, copy the key, and store it securely (see Step 4).
<a name="step-2-installation-and-setup"></a>
git clone https://github.com/icarax-labs/beyondtrust-zero-day-monitor.git
cd beyondtrust-zero-day-monitor
# Create a virtual environment (recommended)
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install --upgrade pip
pip install requests python-dotenv tenacity
# Initialize a new npm project (if you haven't already)
npm init -y
# Install core libraries
npm install axios dotenv typescript ts-node @types/node --save-dev
# Create a basic tsconfig.json
npx tsc --init --rootDir src --outDir dist \
--esModuleInterop --resolveJsonModule --lib es6,dom \
--module commonjs
Your folder should now look like:
beyondtrust-zero-day-monitor/
├─ .env.example
├─ .gitignore
├─ requirements.txt (Python)
├─ package.json (Node)
├─ src/
│ ├─ monitor.py (Python impl)
│ └─ monitor.ts (TS impl)
└─ README.md
<a name="step-3-basic-implementation"></a>
Both implementations follow the same flow:
CVE-2024-XXXXX).src/monitor.py)#!/usr/bin/env python3
"""
BeyondTrust Zero‑Day Monitor – Python version
- Reads CVE ID and API credentials from .env
- Calls a threat‑intel API (example: VirusTotal) to get IOCs for the CVE
- Sends a Slack alert if any IOCs are present
"""
import os
import sys
import json
import logging
from typing import List, Dict, Any
import requests
from dotenv import load_dotenv
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
# ----------------------------------------------------------------------
# Configuration & Logging
# ----------------------------------------------------------------------
load_dotenv() # pulls variables from .env into os.environ
CVE_ID = os.getenv("CVE_ID", "CVE-2024-XXXXX") # override in .env if needed
VT_API_KEY = os.getenv("VT_API_KEY") # VirusTotal API key
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
VT_BASE_URL = "https://www.virustotal.com/api/v3"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s - %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
log = logging.getLogger("beyondtrust_monitor")
# ----------------------------------------------------------------------
# Helper: VirusTotal request with retry/backoff
# ----------------------------------------------------------------------
@retry(
reraise=True,
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((requests.RequestException,)),
)
def _vt_get(path: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
"""Low‑level GET to VirusTotal with auth header."""
headers = {"x-apikey": VT_API_KEY}
url = f"{VT_BASE_URL}{path}"
log.debug(f"Calling VT: {url} params={params}")
resp = requests.get(url, headers=headers, params=params, timeout=10)
resp.raise_for_status() # will trigger retry on HTTP error
return resp.json()
# ----------------------------------------------------------------------
# Core logic: fetch IOCs for a given CVE
# ----------------------------------------------------------------------
def fetch_iocs_for_cve(cve_id: str) -> List[Dict[str, Any]]:
"""
VirusTotal does not have a direct "CVE → IOC" endpoint, but we can:
1. Search for the CVE string in VT's intelligence feed.
2. Pull the first few related files/URLs and extract their hashes/IPs.
For demo purposes we use the `/intelligence/search` endpoint.
"""
log.info(f"Searching VirusTotal for intelligence related to {cve_id}")
try:
data = _vt_get(
"/intelligence/search",
params={"query": cve_id, "limit": 10},
)
except Exception as exc:
log.error(f"Failed to query VirusTotal: {exc}")
return []
iocs: List[Dict[str, Any]] = []
for item in data.get("data", []):
attrs = item.get("attributes", {})
# Example: extract SHA256 hashes from files
if "sha256" in attrs:
iocs.append({"type": "file_hash", "value": attrs["sha256"], "source": "VT"})
# Example: extract IPs from network contacts
for contact in attrs.get("contacts", []):
if contact.get("type") == "ip_address":
iocs.append(
{"type": "ip_address", "value": contact["id"], "source": "VT"}
)
return iocs
# ----------------------------------------------------------------------
# Alerting: Slack webhook
# ----------------------------------------------------------------------
def send_slack_alert(message: str) -> None:
"""POST a simple text message to Slack via Incoming Webhook."""
if not SLACK_WEBHOOK_URL:
log.warning("SLACK_WEBHOOK_URL not set – skipping alert")
return
payload = {"text": message}
try:
resp = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=5)
resp.raise_for_status()
log.info("Slack alert sent successfully")
except Exception as exc:
log.error(f"Failed to send Slack alert: {err}")
# ----------------------------------------------------------------------
# Main orchestration
# ----------------------------------------------------------------------
def main() -> None:
if not VT_API_KEY:
log.error("VT_API_KEY missing – cannot query threat intel")
sys.exit(1)
iocs = fetch_iocs_for_cve(CVE_ID)
if not iocs:
log.info(f"No IOCs found for {CVE_ID}")
return
# Build a readable alert
lines = [f"*BeyondTrust Zero‑Day ({CVE_ID}) – IOCs detected:*"]
for ioc in iocs[:5]: # limit to first 5 for brevity
lines.append(f"- {ioc['type'].upper()}: `{ioc['value']}` (source: {ioc['source']})")
if len(iocs) > 5:
lines.append(f"...and {len(iocs) - 5} more")
alert_text = "\n".join(lines)
send_slack_alert(alert_text)
if __name__ == "__main__":
main()
Key points
tenacity for exponential back‑off retries on network hiccups.VT_API_KEY, SLACK_WEBHOOK_URL).src/monitor.ts)#!/usr/bin/env ts-node
/**
* BeyondTrust Zero‑Day Monitor – TypeScript version
*
* Same functionality as the Python script but using Node.js, Axios and dotenv.
*/
import "dotenv/config";
import axios from "axios";
import { log, error, info, warn } from "console";
const CVE_ID: string = process.env.CVE_ID ?? "CVE-2024-XXXXX";
const VT_API_KEY: string | undefined = process.env.VT_API_KEY;
const SLACK_WEBHOOK_URL: string | undefined = process.env.SLACK_WEBHOOK_URL;
const VT_BASE_URL = "https://www.virustotal.com/api/v3";
if (!VT_API_KEY) {
error("VT_API_KEY is required – exiting");
process.exit(1);
}
/**
* Axios instance with retry logic via manual wrapper (could use axios-retry).
*/
const vt = axios.create({
baseURL: VT_BASE_URL,
timeout: 10_000,
headers: { "x-apikey": VT_API_KEY },
});
async function vtGet<T>(path: string, params?: Record<string, unknown>): Promise<T> {
try {
const resp = await vt.get<T>(path, { params });
return resp.data;
} catch (err: any) {
// Simple exponential back‑off retry (max 3 attempts)
if (err.code === "ECONNABORTED" || err.response?.status >= 500) {
throw err; // will be caught by retry wrapper below
}
throw err;
}
}
/**
* Retry helper – mimics tenacity behavior.
*/
async function retryFn<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
let lastErr: any;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
lastErr = err;
const waitMs = Math.pow(2, i) * 1000; // 1s, 2s, 4s ...
info(`Attempt ${i + 1} failed, retrying in ${waitMs}ms…`);
await new Promise((r) => setTimeout(r, waitMs));
}
}
throw lastErr;
}
/**
* Fetch IOCs related to the CVE using VT intelligence search.
*/
async function fetchIoCsForCve(cve: string): Promise<Array<{type: string; value: string; source: string}>> {
info(`Searching VirusTotal for ${cve}`);
const data = await retryFn(() => vtGet<any>("/intelligence/search", { query: cve, limit: 10 }));
const iocs: Array<{type: string; value: string; source: string}> = [];
for (const item of data.data ?? []) {
const attrs = item.attributes ?? {};
// File hash
if (attrs.sha256) {
iocs.push({ type: "file_hash", value: attrs.sha256, source: "VT" });
}
// IP addresses from contacts
for (const contact of attrs.contacts ?? []) {
if (contact.type === "ip_address") {
iocs.push({ type: "ip_address", value: contact.id, source: "VT" });
}
}
}
return iocs;
}
/**
* Send a Slack message via Incoming Webhook.
*/
async function sendSlackAlert(text: string): Promise<void> {
if (!SLACK_WEBHOOK_URL) {
warn("SLACK_WEBHOOK_URL not set – skipping alert");
return;
}
try {
await axios.post(SLACK_WEBHOOK_URL, { text }, { timeout: 5_000 });
info("Slack alert sent");
} catch (err) {
error(`Failed to send Slack alert: ${err}`);
}
}
/**
* Main entry point.
*/
async function main(): Promise<void> {
const iocs = await fetchIoCsForCve(CVE_ID);
if (iocs.length === 0) {
info(`No IOCs found for ${CVE_ID}`);
return;
}
const lines = [
`*BeyondTrust Zero‑Day (${CVE_ID}) – IOCs detected:*`,
...iocs.slice(0, 5).map(
(ioc) => `- ${ioc.type.toUpperCase()}: \`${ioc.value}\` (source: ${ioc.source})`
),
];
if (iocs.length > 5) {
lines.push(`...and ${iocs.length - 5} more`);
}
await sendSlackAlert(lines.join("\n"));
}
main().catch((err) => {
error(`Unhandled error: ${err}`);
process.exit(1);
});
Key points
axios for HTTP and a tiny manual retry loop (retryFn).dotenv.console (feel free to swap for pino or winston in prod).ts-node src/monitor.ts or compiled to JS (npm run build) and executed with node dist/monitor.js.<a name="step-4-configuration"></a>
Create a .env file in the project root (never commit this file!).
# .env.example – copy to .env and fill in your values
CVE_ID=CVE-2024-XXXXX # The BeyondTrust zero‑day CVE you want to monitor
VT_API_KEY=your_virustotal_key # Get from https://www.virustotal.com/gui/user/<your>/apikey
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
# Optional: adjust how many IOCs you want to display per alert
MAX_IOCS_DISPLAY=5
Loading the file
python-dotenv automatically loads .env when load_dotenv() is called.dotenv.config() (called via import "dotenv/config";) does the same.Secret management tips
| Environment | Recommended approach |
|---|---|
| Local dev | .env file (git‑ignored) |
| CI/CD | Inject variables via the platform’s secret store (GitHub Secrets, GitLab CI variables, Azure Key Vault, AWS Secrets Manager) |
| Kubernetes | Use Secret objects and mount as env vars or files |
| Docker | Pass via --env-file or -e KEY=VALUE at runtime |
<a name="step-5-common-patterns"></a>
Below are reusable snippets you’ll see in many security‑automation scripts.
# Python – tenacity decorator (see monitor.py)
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def call_api(...):
...
// TypeScript – manual wrapper (see monitor.ts)
async function retryFn<T>(fn: () => Promise<T>, attempts = 3): Promise<T> { ... }
logging with JSON formatter (python-json-logger) if you ship logs to ELK/Splunk.pino or winston with format.combine(format.timestamp(), format.json()).Many intel APIs return 429 Too Many Requests. Detect the status and respect the Retry-After header:
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", "60"))
time.sleep(retry_after)
continue
if (err.response?.status === 429) {
const delay = Number(err.response.headers["retry-after"]) * 1000 || 60000;
await new Promise((r) => setTimeout(r, delay));
// retry...
}
When pulling from multiple feeds, store seen values in a Set (or Redis cache) to avoid duplicate alerts.
seen = set()
for ioc in iocs:
key = f"{ioc['type']}:{ioc['value']}"
if key not in seen:
seen.add(key)
# forward to alert...
<a name="step-6-troubleshooting"></a>
| Symptom | Likely cause | Fix |
|---|---|---|
VT_API_KEY missing error | .env not loaded or variable misspelled | Verify .env exists, run cat .env to confirm keys, ensure load_dotenv() (Python) or import "dotenv/config" (TS) is called before accessing os.getenv/process.env. |
401 Unauthorized from VirusTotal | Invalid or expired API key | Regenerate key in VT account, update .env. |
| No IOCs returned | Query too restrictive, or the feed doesn’t contain the CVE yet | Try a broader search (query: "BeyondTrust"), increase limit, or check a different intel source (OTX, AlienVault, etc.). |
Slack webhook returns 400 Bad Request | Payload too large or malformed | Truncate IOC list (see MAX_IOCS_DISPLAY), ensure JSON is valid ({ "text": "..." }). |
Network timeout (ECONNABORTED) | Outbound firewall blocking virustotal.com or Slack webhook | Allow outbound HTTPS (port 443) to those domains, or run from a permitted subnet. |
| Duplicate alerts | Script runs on a schedule without deduping | Implement a simple in‑memory Set or persistent store (Redis) of already‑seen IOC hashes. |
ModuleNotFoundError: dotenv | Dependency not installed | pip install python-dotenv (Python) or npm install dotenv (Node). |
Cannot find module 'axios' | Node modules missing | npm install (or yarn install). |
Debug tip: Run the script with LOG_LEVEL=DEBUG (adjust the logging config to read this env var) to see full request/response payloads.
<a name="step-7-production-checklist"></a>
Before you push this monitor to production, verify the following:
| ✅ Item | Why it matters |
|---|---|
| Secrets management – never hard‑code keys; use a vault or CI/CD secret injection. | |
Immutable container image – build a Docker image with COPY . /app and RUN pip install -r requirements.txt (or npm ci). | |
Health endpoint – expose /healthz (returns 200 if last run succeeded) for Kubernetes liveness/readiness probes. | |
Structured logging – ship logs to a central system (ELK, Splunk, Loki) with fields: timestamp, level, service, cve_id, ioc_count. | |
| Alert throttling – limit Slack messages to e.g., max 5 per minute to avoid channel spam. | |
Metrics – expose Prometheus counters: monitor_run_total, monitor_ioc_detected_total, monitor_errors_total. | |
Automated testing – unit tests for fetch_iocs_for_cve (mock HTTP responses) and integration test in a staging environment. | |
Version pinning – lock dependencies (requirements.txt, package-lock.json) to avoid surprise breakage. | |
Patch management – schedule a weekly check for new versions of requests, axios, dotenv, etc., and apply security updates. | |
Documentation – keep a README.md with runbook: how to deploy, how to update the CVE ID, how to rotate keys. | |
| Compliance – ensure data retention policy aligns with your organization’s rules (e.g., delete raw IOC JSON after 30 days). | |
| Fail‑open vs fail‑closed – decide whether a missing intel feed should suppress alerts (fail‑closed) or just log and continue (fail‑open). This script chooses fail‑closed (no alert if intel unavailable). |
Sample Dockerfile (Python)
# ---- Build ----
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# ---- Runtime ----
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY src/monitor.py .
COPY .env.example .env.example # keep example, not actual secrets
# Secrets injected at runtime via -e or Docker secrets
CMD ["python", "monitor.py"]
Sample Dockerfile (Node/TS)
# ---- Build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json .
COPY src ./src
RUN npm run build # outputs to ./dist
# ---- Runtime ----
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/monitor.js"]
Deploy with Kubernetes (example snippet):
apiVersion: apps/v1
kind: Deployment
metadata:
name: beyondtrust-monitor
spec:
replicas: 1
selector:
matchLabels:
app: beyondtrust-monitor
template:
metadata:
labels:
app: beyondtrust-monitor
spec:
containers:
- name: monitor
image: your-registry/beyondtrust-monitor:latest
env:
- name: CVE_ID
valueFrom:
secretKeyRef:
name: monitor-secrets
key: CVE_ID
- name: VT_API_KEY
valueFrom:
secretKeyRef:
name: monitor-secrets
key: VT_API_KEY
- name: SLACK_WEBHOOK_URL
valueFrom:
secretKeyRef:
name: monitor-secrets
key: SLACK_WEBHOOK_URL
ports:
- name: http
containerPort: 8080 # if you add a health endpoint
---
apiVersion: v1
kind: Service
metadata:
name: beyondtrust-monitor
spec:
selector:
app: beyondtrust-monitor
ports:
- port: 80
targetPort: http
type: ClusterIP
.env.Stay safe, keep your intel feeds fresh, and let the defenders know the moment the Chinese APT’s BeyondTrust zero‑day shows up in the wild.
Happy hunting! 🚀
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
