Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##########################################################################################
|
||||
#### cisa-kev-monitor.sh — Monitor CISA Known Exploited Vulnerabilities catalog ####
|
||||
#### Polls the KEV JSON feed, detects new entries, alerts via email/Slack/Telegram ####
|
||||
#### Requires: bash 4+, curl, jq ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.00 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./cisa-kev-monitor.sh ####
|
||||
#### ./cisa-kev-monitor.sh --filter linux,kernel ####
|
||||
#### ./cisa-kev-monitor.sh --telegram --filter linux ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
##########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
KEV_URL="https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
||||
STATE_DIR="${KEV_STATE_DIR:-${HOME:-/tmp}/.cisa-kev-monitor}"
|
||||
STATE_FILE="$STATE_DIR/known-cves.txt"
|
||||
FILTER_KEYWORDS="${KEV_FILTER:-}"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
|
||||
# Notification channels
|
||||
SMTP_TO="${KEV_SMTP_TO:-}"
|
||||
SMTP_FROM="${KEV_SMTP_FROM:-cisa-kev-monitor@$(hostname -f 2>/dev/null || echo localhost)}"
|
||||
SLACK_WEBHOOK="${KEV_SLACK_WEBHOOK:-}"
|
||||
TELEGRAM_BOT_TOKEN="${KEV_TELEGRAM_BOT_TOKEN:-}"
|
||||
TELEGRAM_CHAT_ID="${KEV_TELEGRAM_CHAT_ID:-}"
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
NEW_CVES=()
|
||||
NEW_COUNT=0
|
||||
TOTAL_COUNT=0
|
||||
START_TIME=""
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" DIM="" RESET=""
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then return; fi
|
||||
if [[ "$COLOR" == "auto" && ! -t 1 ]]; then return; fi
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[0;33m"
|
||||
BLUE="\033[0;34m"
|
||||
BOLD="\033[1m"
|
||||
DIM="\033[2m"
|
||||
RESET="\033[0m"
|
||||
}
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
die() { printf "%b\n" "${RED}[ERROR]${RESET} $*" >&2; exit 1; }
|
||||
log_info() { printf "%b\n" "${GREEN}[INFO]${RESET} $*"; }
|
||||
log_warn() { printf "%b\n" "${YELLOW}[WARN]${RESET} $*"; }
|
||||
log_verbose() { [[ "$VERBOSE" == "true" ]] && printf "%b\n" "${DIM}[DEBUG]${RESET} $*" || true; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [OPTIONS]
|
||||
|
||||
Monitor the CISA Known Exploited Vulnerabilities (KEV) catalog for new entries.
|
||||
On first run, initializes the state file. Subsequent runs detect and alert on new CVEs.
|
||||
|
||||
Options:
|
||||
--filter KEYWORDS Comma-separated keywords to match (e.g., linux,kernel,apache)
|
||||
Matches against vendor, product, and description fields
|
||||
Without --filter, all new KEV entries trigger alerts
|
||||
--email ADDRESS Send alerts via email (requires sendmail/msmtp)
|
||||
--slack WEBHOOK_URL Send alerts to Slack webhook
|
||||
--telegram Send alerts via Telegram (requires KEV_TELEGRAM_BOT_TOKEN
|
||||
and KEV_TELEGRAM_CHAT_ID env vars)
|
||||
--list List all current KEV entries matching the filter and exit
|
||||
--list-new DAYS List entries added in the last N days
|
||||
--stats Show KEV catalog statistics and exit
|
||||
--state-dir DIR State directory (default: ~/.cisa-kev-monitor)
|
||||
--reset Delete state file and re-initialize
|
||||
--dry-run Show what would be alerted without sending notifications
|
||||
--verbose Debug output
|
||||
--no-color Disable colored output
|
||||
--help Show this help
|
||||
|
||||
Environment Variables:
|
||||
KEV_STATE_DIR State directory (default: ~/.cisa-kev-monitor)
|
||||
KEV_FILTER Default filter keywords (comma-separated)
|
||||
KEV_SMTP_TO Default email recipient
|
||||
KEV_SMTP_FROM Email sender address
|
||||
KEV_SLACK_WEBHOOK Default Slack webhook URL
|
||||
KEV_TELEGRAM_BOT_TOKEN Telegram bot token
|
||||
KEV_TELEGRAM_CHAT_ID Telegram chat ID
|
||||
|
||||
Examples:
|
||||
$SCRIPT_NAME # Check for any new KEV entries
|
||||
$SCRIPT_NAME --filter linux,kernel # Only alert on Linux/kernel CVEs
|
||||
$SCRIPT_NAME --filter linux --telegram # Alert via Telegram for Linux CVEs
|
||||
$SCRIPT_NAME --filter windows,microsoft --email ops@example.com
|
||||
$SCRIPT_NAME --list-new 7 # Show entries from last 7 days
|
||||
$SCRIPT_NAME --stats # Show catalog statistics
|
||||
|
||||
Cron example (check every 6 hours):
|
||||
0 */6 * * * /opt/scripts/cisa-kev-monitor.sh --filter linux,kernel --telegram --no-color 2>&1 | logger -t kev-monitor
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Dependency Check ──────────────────────────────────────────────────
|
||||
check_deps() {
|
||||
local missing=()
|
||||
for cmd in curl jq; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
missing+=("$cmd")
|
||||
fi
|
||||
done
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
die "Missing required commands: ${missing[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Argument Parsing ──────────────────────────────────────────────────
|
||||
DRY_RUN="false"
|
||||
LIST_MODE="false"
|
||||
LIST_NEW_DAYS=""
|
||||
STATS_MODE="false"
|
||||
RESET_MODE="false"
|
||||
NOTIFY_EMAIL="false"
|
||||
NOTIFY_SLACK="false"
|
||||
NOTIFY_TELEGRAM="false"
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--filter) FILTER_KEYWORDS="${2:?--filter requires keywords}"; shift 2 ;;
|
||||
--email) SMTP_TO="${2:?--email requires an address}"; NOTIFY_EMAIL="true"; shift 2 ;;
|
||||
--slack) SLACK_WEBHOOK="${2:?--slack requires a webhook URL}"; NOTIFY_SLACK="true"; shift 2 ;;
|
||||
--telegram) NOTIFY_TELEGRAM="true"; shift ;;
|
||||
--list) LIST_MODE="true"; shift ;;
|
||||
--list-new) LIST_NEW_DAYS="${2:?--list-new requires days}"; shift 2 ;;
|
||||
--stats) STATS_MODE="true"; shift ;;
|
||||
--state-dir) STATE_DIR="${2:?--state-dir requires a path}"; STATE_FILE="$STATE_DIR/known-cves.txt"; shift 2 ;;
|
||||
--reset) RESET_MODE="true"; shift ;;
|
||||
--dry-run) DRY_RUN="true"; shift ;;
|
||||
--verbose) VERBOSE="true"; shift ;;
|
||||
--no-color) COLOR="never"; shift ;;
|
||||
--help) usage ;;
|
||||
*) die "Unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$NOTIFY_TELEGRAM" == "true" ]]; then
|
||||
[[ -z "$TELEGRAM_BOT_TOKEN" ]] && die "KEV_TELEGRAM_BOT_TOKEN not set"
|
||||
[[ -z "$TELEGRAM_CHAT_ID" ]] && die "KEV_TELEGRAM_CHAT_ID not set"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Fetch KEV Feed ────────────────────────────────────────────────────
|
||||
fetch_kev() {
|
||||
log_verbose "Fetching KEV catalog from CISA..."
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
if ! curl -sS --max-time 30 --retry 2 -o "$tmpfile" "$KEV_URL" 2>/dev/null; then
|
||||
rm -f "$tmpfile"
|
||||
die "Failed to fetch KEV catalog from $KEV_URL"
|
||||
fi
|
||||
|
||||
# Validate JSON
|
||||
if ! jq empty "$tmpfile" 2>/dev/null; then
|
||||
rm -f "$tmpfile"
|
||||
die "Invalid JSON received from KEV feed"
|
||||
fi
|
||||
|
||||
echo "$tmpfile"
|
||||
}
|
||||
|
||||
# ── Filter Entries ────────────────────────────────────────────────────
|
||||
filter_entries() {
|
||||
local json_file="$1"
|
||||
|
||||
if [[ -z "$FILTER_KEYWORDS" ]]; then
|
||||
jq -r '.vulnerabilities[]' "$json_file"
|
||||
return
|
||||
fi
|
||||
|
||||
# Build jq filter from comma-separated keywords
|
||||
local jq_filter=""
|
||||
IFS=',' read -ra keywords <<< "$FILTER_KEYWORDS"
|
||||
for kw in "${keywords[@]}"; do
|
||||
kw=$(echo "$kw" | xargs) # trim whitespace
|
||||
kw_lower=$(echo "$kw" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ -n "$jq_filter" ]]; then
|
||||
jq_filter="$jq_filter or"
|
||||
fi
|
||||
jq_filter="$jq_filter ((.vendorProject // \"\" | ascii_downcase | contains(\"$kw_lower\")) or (.product // \"\" | ascii_downcase | contains(\"$kw_lower\")) or (.shortDescription // \"\" | ascii_downcase | contains(\"$kw_lower\")) or (.vulnerabilityName // \"\" | ascii_downcase | contains(\"$kw_lower\")))"
|
||||
done
|
||||
|
||||
jq -r ".vulnerabilities[] | select($jq_filter)" "$json_file"
|
||||
}
|
||||
|
||||
# ── Initialize State ─────────────────────────────────────────────────
|
||||
init_state() {
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
if [[ "$RESET_MODE" == "true" && -f "$STATE_FILE" ]]; then
|
||||
rm -f "$STATE_FILE"
|
||||
log_info "State file reset"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
log_info "First run — initializing state file"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Format CVE for Display ────────────────────────────────────────────
|
||||
format_cve_text() {
|
||||
local cve="$1"
|
||||
local cve_id vendor product name date_added desc due_date ransomware
|
||||
|
||||
cve_id=$(echo "$cve" | jq -r '.cveID')
|
||||
vendor=$(echo "$cve" | jq -r '.vendorProject')
|
||||
product=$(echo "$cve" | jq -r '.product')
|
||||
name=$(echo "$cve" | jq -r '.vulnerabilityName')
|
||||
date_added=$(echo "$cve" | jq -r '.dateAdded')
|
||||
desc=$(echo "$cve" | jq -r '.shortDescription')
|
||||
due_date=$(echo "$cve" | jq -r '.dueDate')
|
||||
ransomware=$(echo "$cve" | jq -r '.knownRansomwareCampaignUse')
|
||||
|
||||
printf "%b%s%b — %s\n" "$BOLD" "$cve_id" "$RESET" "$name"
|
||||
printf " Vendor: %s / %s\n" "$vendor" "$product"
|
||||
printf " Added: %s\n" "$date_added"
|
||||
printf " Due: %s\n" "$due_date"
|
||||
printf " Ransomware: %s\n" "$ransomware"
|
||||
printf " %s\n" "$desc"
|
||||
printf " NVD: https://nvd.nist.gov/vuln/detail/%s\n" "$cve_id"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── Format CVE for Notifications ──────────────────────────────────────
|
||||
format_cve_plain() {
|
||||
local cve="$1"
|
||||
local cve_id vendor product name date_added desc
|
||||
|
||||
cve_id=$(echo "$cve" | jq -r '.cveID')
|
||||
vendor=$(echo "$cve" | jq -r '.vendorProject')
|
||||
product=$(echo "$cve" | jq -r '.product')
|
||||
name=$(echo "$cve" | jq -r '.vulnerabilityName')
|
||||
date_added=$(echo "$cve" | jq -r '.dateAdded')
|
||||
desc=$(echo "$cve" | jq -r '.shortDescription')
|
||||
|
||||
echo "$cve_id — $name"
|
||||
echo "Vendor: $vendor / $product"
|
||||
echo "Added: $date_added"
|
||||
echo "$desc"
|
||||
echo "https://nvd.nist.gov/vuln/detail/$cve_id"
|
||||
echo ""
|
||||
}
|
||||
|
||||
format_cve_telegram() {
|
||||
local cve="$1"
|
||||
local cve_id vendor product name date_added desc ransomware
|
||||
|
||||
cve_id=$(echo "$cve" | jq -r '.cveID')
|
||||
vendor=$(echo "$cve" | jq -r '.vendorProject')
|
||||
product=$(echo "$cve" | jq -r '.product')
|
||||
name=$(echo "$cve" | jq -r '.vulnerabilityName')
|
||||
date_added=$(echo "$cve" | jq -r '.dateAdded')
|
||||
desc=$(echo "$cve" | jq -r '.shortDescription' | head -c 200)
|
||||
ransomware=$(echo "$cve" | jq -r '.knownRansomwareCampaignUse')
|
||||
|
||||
local emoji="🔴"
|
||||
[[ "$ransomware" == "Known" ]] && emoji="🔴🛑"
|
||||
|
||||
echo "${emoji} <b>${cve_id}</b> — ${name}"
|
||||
echo "📦 ${vendor} / ${product}"
|
||||
echo "📅 Added: ${date_added}"
|
||||
[[ "$ransomware" == "Known" ]] && echo "💀 Known ransomware use"
|
||||
echo ""
|
||||
echo "${desc}..."
|
||||
echo ""
|
||||
echo "🔗 <a href=\"https://nvd.nist.gov/vuln/detail/${cve_id}\">NVD</a>"
|
||||
}
|
||||
|
||||
# ── Notification: Email ───────────────────────────────────────────────
|
||||
send_email() {
|
||||
local subject="$1"
|
||||
local body="$2"
|
||||
|
||||
if ! command -v sendmail &>/dev/null && ! command -v msmtp &>/dev/null; then
|
||||
log_warn "No sendmail or msmtp found — skipping email"
|
||||
return
|
||||
fi
|
||||
|
||||
local mailer="sendmail"
|
||||
command -v msmtp &>/dev/null && mailer="msmtp"
|
||||
|
||||
{
|
||||
echo "From: $SMTP_FROM"
|
||||
echo "To: $SMTP_TO"
|
||||
echo "Subject: $subject"
|
||||
echo "Content-Type: text/plain; charset=utf-8"
|
||||
echo ""
|
||||
echo "$body"
|
||||
} | "$mailer" -t "$SMTP_TO"
|
||||
|
||||
log_verbose "Email sent to $SMTP_TO"
|
||||
}
|
||||
|
||||
# ── Notification: Slack ───────────────────────────────────────────────
|
||||
send_slack() {
|
||||
local text="$1"
|
||||
|
||||
# Truncate for Slack's 3000 char limit
|
||||
text=$(echo "$text" | head -c 2900)
|
||||
|
||||
local payload
|
||||
payload=$(jq -n --arg text "$text" '{text: $text}')
|
||||
|
||||
curl -sS --max-time 10 -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"$SLACK_WEBHOOK" >/dev/null 2>&1
|
||||
|
||||
log_verbose "Slack notification sent"
|
||||
}
|
||||
|
||||
# ── Notification: Telegram ────────────────────────────────────────────
|
||||
send_telegram() {
|
||||
local text="$1"
|
||||
|
||||
# Telegram message limit is 4096 chars
|
||||
text=$(echo "$text" | head -c 4000)
|
||||
|
||||
curl -sS --max-time 10 -X POST \
|
||||
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
||||
-d "parse_mode=HTML" \
|
||||
-d "disable_web_page_preview=true" \
|
||||
--data-urlencode "text=$text" >/dev/null 2>&1
|
||||
|
||||
log_verbose "Telegram notification sent"
|
||||
}
|
||||
|
||||
# ── Notify All Channels ──────────────────────────────────────────────
|
||||
notify() {
|
||||
local count=${#NEW_CVES[@]}
|
||||
local filter_label=""
|
||||
[[ -n "$FILTER_KEYWORDS" ]] && filter_label=" (filter: $FILTER_KEYWORDS)"
|
||||
|
||||
# Build plain text body
|
||||
local plain_body=""
|
||||
plain_body+="CISA KEV Monitor — $count new CVE(s) detected${filter_label}"
|
||||
plain_body+=$'\n\n'
|
||||
for cve_json in "${NEW_CVES[@]}"; do
|
||||
plain_body+=$(format_cve_plain "$cve_json")
|
||||
plain_body+=$'\n'
|
||||
done
|
||||
|
||||
# Build Telegram body
|
||||
local tg_body=""
|
||||
tg_body+="🚨 <b>CISA KEV — ${count} new CVE(s)</b>${filter_label}"
|
||||
tg_body+=$'\n\n'
|
||||
for cve_json in "${NEW_CVES[@]}"; do
|
||||
tg_body+=$(format_cve_telegram "$cve_json")
|
||||
tg_body+=$'\n'
|
||||
done
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
log_warn "DRY-RUN — would send notifications to:"
|
||||
[[ "$NOTIFY_EMAIL" == "true" ]] && echo " Email: $SMTP_TO"
|
||||
[[ "$NOTIFY_SLACK" == "true" ]] && echo " Slack: (webhook configured)"
|
||||
[[ "$NOTIFY_TELEGRAM" == "true" ]] && echo " Telegram: chat $TELEGRAM_CHAT_ID"
|
||||
return
|
||||
fi
|
||||
|
||||
[[ "$NOTIFY_EMAIL" == "true" ]] && send_email "CISA KEV: $count new CVE(s)${filter_label}" "$plain_body"
|
||||
[[ "$NOTIFY_SLACK" == "true" ]] && send_slack "$plain_body"
|
||||
[[ "$NOTIFY_TELEGRAM" == "true" ]] && send_telegram "$tg_body"
|
||||
}
|
||||
|
||||
# ── Mode: Stats ───────────────────────────────────────────────────────
|
||||
run_stats() {
|
||||
local json_file
|
||||
json_file=$(fetch_kev)
|
||||
|
||||
local total last_updated
|
||||
total=$(jq '.vulnerabilities | length' "$json_file")
|
||||
last_updated=$(jq -r '.catalogVersion' "$json_file")
|
||||
|
||||
local last_7d last_30d
|
||||
local cutoff_7d cutoff_30d
|
||||
cutoff_7d=$(date -u -d "7 days ago" '+%Y-%m-%d' 2>/dev/null || date -u -v-7d '+%Y-%m-%d' 2>/dev/null)
|
||||
cutoff_30d=$(date -u -d "30 days ago" '+%Y-%m-%d' 2>/dev/null || date -u -v-30d '+%Y-%m-%d' 2>/dev/null)
|
||||
|
||||
last_7d=$(jq --arg d "$cutoff_7d" '[.vulnerabilities[] | select(.dateAdded >= $d)] | length' "$json_file")
|
||||
last_30d=$(jq --arg d "$cutoff_30d" '[.vulnerabilities[] | select(.dateAdded >= $d)] | length' "$json_file")
|
||||
|
||||
local ransomware_known
|
||||
ransomware_known=$(jq '[.vulnerabilities[] | select(.knownRansomwareCampaignUse == "Known")] | length' "$json_file")
|
||||
|
||||
echo ""
|
||||
printf "%bCISA KEV Catalog Statistics%b\n" "$BOLD" "$RESET"
|
||||
echo "Catalog version: $last_updated"
|
||||
echo "Total CVEs: $total"
|
||||
echo "Last 7 days: $last_7d"
|
||||
echo "Last 30 days: $last_30d"
|
||||
echo "Ransomware use: $ransomware_known"
|
||||
|
||||
if [[ -n "$FILTER_KEYWORDS" ]]; then
|
||||
local filtered
|
||||
filtered=$(filter_entries "$json_file" | jq -s 'length')
|
||||
echo "Matching filter: $filtered (keywords: $FILTER_KEYWORDS)"
|
||||
fi
|
||||
|
||||
rm -f "$json_file"
|
||||
}
|
||||
|
||||
# ── Mode: List New ────────────────────────────────────────────────────
|
||||
run_list_new() {
|
||||
local days="$1"
|
||||
local json_file
|
||||
json_file=$(fetch_kev)
|
||||
|
||||
local cutoff
|
||||
cutoff=$(date -u -d "$days days ago" '+%Y-%m-%d' 2>/dev/null || date -u -v-"${days}d" '+%Y-%m-%d' 2>/dev/null)
|
||||
|
||||
echo ""
|
||||
printf "%bCISA KEV — entries added in the last %s days%b\n\n" "$BOLD" "$days" "$RESET"
|
||||
|
||||
local count=0
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
local date_added
|
||||
date_added=$(echo "$entry" | jq -r '.dateAdded')
|
||||
if [[ "$date_added" > "$cutoff" || "$date_added" == "$cutoff" ]]; then
|
||||
format_cve_text "$entry"
|
||||
count=$((count + 1))
|
||||
fi
|
||||
done < <(filter_entries "$json_file" | jq -c '.')
|
||||
|
||||
log_info "$count entries found"
|
||||
rm -f "$json_file"
|
||||
}
|
||||
|
||||
# ── Mode: List ────────────────────────────────────────────────────────
|
||||
run_list() {
|
||||
local json_file
|
||||
json_file=$(fetch_kev)
|
||||
|
||||
echo ""
|
||||
printf "%bCISA KEV — all matching entries%b\n" "$BOLD" "$RESET"
|
||||
[[ -n "$FILTER_KEYWORDS" ]] && echo "Filter: $FILTER_KEYWORDS"
|
||||
echo ""
|
||||
|
||||
local count=0
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
format_cve_text "$entry"
|
||||
count=$((count + 1))
|
||||
done < <(filter_entries "$json_file" | jq -c '.')
|
||||
|
||||
log_info "$count entries"
|
||||
rm -f "$json_file"
|
||||
}
|
||||
|
||||
# ── Mode: Monitor ─────────────────────────────────────────────────────
|
||||
run_monitor() {
|
||||
local json_file
|
||||
json_file=$(fetch_kev)
|
||||
|
||||
TOTAL_COUNT=$(jq '.vulnerabilities | length' "$json_file")
|
||||
|
||||
# Initialize state on first run
|
||||
if ! init_state; then
|
||||
jq -r '.vulnerabilities[].cveID' "$json_file" | sort > "$STATE_FILE"
|
||||
local init_count
|
||||
init_count=$(wc -l < "$STATE_FILE")
|
||||
log_info "Initialized with $init_count CVEs. Future runs will detect new entries."
|
||||
rm -f "$json_file"
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract current CVE IDs
|
||||
local current_cves
|
||||
current_cves=$(mktemp)
|
||||
jq -r '.vulnerabilities[].cveID' "$json_file" | sort > "$current_cves"
|
||||
|
||||
# Find new CVEs not in state file
|
||||
local new_ids
|
||||
new_ids=$(comm -13 "$STATE_FILE" "$current_cves")
|
||||
|
||||
if [[ -z "$new_ids" ]]; then
|
||||
log_info "No new KEV entries (catalog: $TOTAL_COUNT CVEs)"
|
||||
rm -f "$current_cves" "$json_file"
|
||||
return
|
||||
fi
|
||||
|
||||
# Collect new CVE details, applying filter
|
||||
while IFS= read -r cve_id; do
|
||||
[[ -z "$cve_id" ]] && continue
|
||||
|
||||
local cve_json
|
||||
cve_json=$(jq -c --arg id "$cve_id" '.vulnerabilities[] | select(.cveID == $id)' "$json_file")
|
||||
|
||||
# Apply filter if set
|
||||
if [[ -n "$FILTER_KEYWORDS" ]]; then
|
||||
local matches="false"
|
||||
IFS=',' read -ra keywords <<< "$FILTER_KEYWORDS"
|
||||
for kw in "${keywords[@]}"; do
|
||||
kw=$(echo "$kw" | xargs | tr '[:upper:]' '[:lower:]')
|
||||
if echo "$cve_json" | tr '[:upper:]' '[:lower:]' | grep -q "$kw"; then
|
||||
matches="true"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[[ "$matches" == "false" ]] && continue
|
||||
fi
|
||||
|
||||
NEW_CVES+=("$cve_json")
|
||||
format_cve_text "$cve_json"
|
||||
done <<< "$new_ids"
|
||||
|
||||
NEW_COUNT=${#NEW_CVES[@]}
|
||||
|
||||
# Update state file with all current CVEs
|
||||
mv "$current_cves" "$STATE_FILE"
|
||||
|
||||
if [[ $NEW_COUNT -eq 0 ]]; then
|
||||
local total_new
|
||||
total_new=$(echo "$new_ids" | wc -w)
|
||||
log_info "No new entries matching filter (${total_new} new total, $TOTAL_COUNT in catalog)"
|
||||
rm -f "$json_file"
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "$NEW_COUNT new KEV entry/entries matching filter"
|
||||
|
||||
# Send notifications
|
||||
if [[ "$NOTIFY_EMAIL" == "true" || "$NOTIFY_SLACK" == "true" || "$NOTIFY_TELEGRAM" == "true" ]]; then
|
||||
notify
|
||||
fi
|
||||
|
||||
rm -f "$json_file"
|
||||
}
|
||||
|
||||
# ── Entry Point ───────────────────────────────────────────────────────
|
||||
main() {
|
||||
START_TIME=$(date +%s)
|
||||
setup_colors
|
||||
parse_args "$@"
|
||||
check_deps
|
||||
|
||||
if [[ "$STATS_MODE" == "true" ]]; then
|
||||
run_stats
|
||||
elif [[ -n "$LIST_NEW_DAYS" ]]; then
|
||||
run_list_new "$LIST_NEW_DAYS"
|
||||
elif [[ "$LIST_MODE" == "true" ]]; then
|
||||
run_list
|
||||
else
|
||||
run_monitor
|
||||
fi
|
||||
|
||||
local elapsed=$(( $(date +%s) - START_TIME ))
|
||||
log_verbose "Completed in ${elapsed}s"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user