#!/bin/sh # CVE Triage - Collector Module # Collects installed packages and CVE data CACHE_DIR="/var/cache/cve-triage" # Initialize cache directory init_cache() { mkdir -p "$CACHE_DIR" } # Collect installed opkg packages with versions collect_opkg_packages() { opkg list-installed 2>/dev/null | while read -r pkg rest; do local version=$(echo "$rest" | awk '{print $1}') printf '{"name":"%s","version":"%s","source":"opkg"}\n' "$pkg" "$version" done | json_slurp } # Collect LXC container packages (if containers exist) collect_lxc_packages() { local packages='[]' for container_dir in /srv/lxc/*/; do [ -d "$container_dir" ] || continue local name=$(basename "$container_dir") # Check if container has dpkg if [ -f "${container_dir}rootfs/var/lib/dpkg/status" ]; then grep -E "^Package:|^Version:" "${container_dir}rootfs/var/lib/dpkg/status" 2>/dev/null | \ paste - - | sed 's/Package: //; s/Version: //' | while read -r pkg ver; do printf '{"name":"%s","version":"%s","source":"lxc:%s"}\n' "$pkg" "$ver" "$name" done fi # Check if container has apk (Alpine) if [ -f "${container_dir}rootfs/lib/apk/db/installed" ]; then awk '/^P:/{pkg=$2} /^V:/{print pkg,$2}' "${container_dir}rootfs/lib/apk/db/installed" 2>/dev/null | \ while read -r pkg ver; do printf '{"name":"%s","version":"%s","source":"lxc:%s"}\n' "$pkg" "$ver" "$name" done fi done | json_slurp } # Collect Docker image info collect_docker_images() { command -v docker >/dev/null 2>&1 || return docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | while read -r image; do printf '{"name":"%s","version":"","source":"docker"}\n' "$image" done | json_slurp } # Collect all installed packages collect_all_packages() { init_cache local cache_file="$CACHE_DIR/packages.json" { echo '{"timestamp":"'"$(date -Iseconds)"'","packages":[' local first=1 # opkg packages if [ "$(uci_get packages.opkg.enabled)" = "1" ]; then collect_opkg_packages | jsonfilter -e '@[*]' 2>/dev/null | while read -r pkg; do [ $first -eq 0 ] && printf ',' first=0 printf '%s' "$pkg" done fi # LXC packages if [ "$(uci_get packages.lxc.enabled)" = "1" ]; then collect_lxc_packages | jsonfilter -e '@[*]' 2>/dev/null | while read -r pkg; do [ $first -eq 0 ] && printf ',' first=0 printf '%s' "$pkg" done fi # Docker images if [ "$(uci_get packages.docker.enabled)" = "1" ]; then collect_docker_images | jsonfilter -e '@[*]' 2>/dev/null | while read -r pkg; do [ $first -eq 0 ] && printf ',' first=0 printf '%s' "$pkg" done fi echo ']}' } > "$cache_file" cat "$cache_file" } # Fetch CVEs from CrowdSec alerts (filter CVE-related scenarios) collect_crowdsec_cves() { local cscli_path=$(command -v cscli 2>/dev/null) [ -z "$cscli_path" ] && return # Get recent alerts with CVE in scenario name $cscli_path alerts list -o json 2>/dev/null | \ jsonfilter -e '@[*]' 2>/dev/null | \ grep -i "cve" | \ while read -r alert; do local scenario=$(echo "$alert" | jsonfilter -e '@.scenario' 2>/dev/null) local cve_id=$(echo "$scenario" | grep -oE 'CVE-[0-9]{4}-[0-9]+' | head -1) [ -n "$cve_id" ] || continue local ip=$(echo "$alert" | jsonfilter -e '@.source.ip' 2>/dev/null) local timestamp=$(echo "$alert" | jsonfilter -e '@.created_at' 2>/dev/null) printf '{"cve":"%s","scenario":"%s","source_ip":"%s","detected":"%s","source":"crowdsec"}\n' \ "$cve_id" "$scenario" "$ip" "$timestamp" done | json_slurp } # Fetch CVE data from NVD API (with caching) fetch_nvd_cves() { local keywords="$1" local cache_file="$CACHE_DIR/nvd_recent.json" local cache_hours=$(uci_get source.nvd.cache_hours) cache_hours="${cache_hours:-24}" # Check cache freshness if [ -f "$cache_file" ]; then local cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) local max_age=$((cache_hours * 3600)) if [ $cache_age -lt $max_age ]; then cat "$cache_file" return fi fi # Fetch from NVD API (last 7 days, high severity) local nvd_url=$(uci_get source.nvd.url) nvd_url="${nvd_url:-https://services.nvd.nist.gov/rest/json/cves/2.0}" local end_date=$(date -u +%Y-%m-%dT%H:%M:%S.000) local start_date=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S.000 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S.000) # NVD API requires date range local api_url="${nvd_url}?pubStartDate=${start_date}&pubEndDate=${end_date}&cvssV3Severity=HIGH" log_info "Fetching CVEs from NVD API..." local response=$(wget -q -O - --timeout=30 "$api_url" 2>/dev/null) if [ -n "$response" ]; then echo "$response" > "$cache_file" echo "$response" else log_warn "Failed to fetch NVD data" [ -f "$cache_file" ] && cat "$cache_file" fi } # Parse NVD response to extract CVE details parse_nvd_cves() { local nvd_data="$1" echo "$nvd_data" | jsonfilter -e '@.vulnerabilities[*]' 2>/dev/null | while read -r vuln; do local cve_id=$(echo "$vuln" | jsonfilter -e '@.cve.id' 2>/dev/null) local description=$(echo "$vuln" | jsonfilter -e '@.cve.descriptions[0].value' 2>/dev/null | head -c 500) local published=$(echo "$vuln" | jsonfilter -e '@.cve.published' 2>/dev/null) # Get CVSS score (try v3.1, then v3.0, then v2) local cvss=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV31[0].cvssData.baseScore' 2>/dev/null) [ -z "$cvss" ] && cvss=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV30[0].cvssData.baseScore' 2>/dev/null) [ -z "$cvss" ] && cvss=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV2[0].cvssData.baseScore' 2>/dev/null) [ -z "$cvss" ] && cvss="0" local severity="low" [ "$(echo "$cvss >= 4.0" | bc 2>/dev/null || echo 0)" = "1" ] && severity="medium" [ "$(echo "$cvss >= 7.0" | bc 2>/dev/null || echo 0)" = "1" ] && severity="high" [ "$(echo "$cvss >= 9.0" | bc 2>/dev/null || echo 0)" = "1" ] && severity="critical" # Escape description for JSON description=$(echo "$description" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') printf '{"cve":"%s","cvss":%s,"severity":"%s","published":"%s","description":"%s"}\n' \ "$cve_id" "$cvss" "$severity" "$published" "$description" done | json_slurp } # Match CVEs to installed packages (basic keyword matching) match_cves_to_packages() { local cves="$1" local packages="$2" echo "$cves" | jsonfilter -e '@[*]' 2>/dev/null | while read -r cve; do local cve_id=$(echo "$cve" | jsonfilter -e '@.cve' 2>/dev/null) local description=$(echo "$cve" | jsonfilter -e '@.description' 2>/dev/null | tr '[:upper:]' '[:lower:]') # Check if any installed package name appears in the CVE description echo "$packages" | jsonfilter -e '@.packages[*].name' 2>/dev/null | while read -r pkg_name; do pkg_lower=$(echo "$pkg_name" | tr '[:upper:]' '[:lower:]') if echo "$description" | grep -qw "$pkg_lower"; then echo "$cve" | sed "s/}$/,\"affected_package\":\"$pkg_name\"}/" fi done done | json_slurp } # Build JSON array from newline-delimited JSON objects json_slurp() { printf '[' local first=1 while IFS= read -r line; do [ -z "$line" ] && continue echo "$line" | jsonfilter -e '@' >/dev/null 2>&1 || continue [ $first -eq 0 ] && printf ',' first=0 printf '%s' "$line" done printf ']' }