rinoxRinox
enrichbashone-off✓ IRON 90hand-curated

VirusTotal → Splunk

Enrich a list of hashes against VirusTotal and forward results to Splunk

One-shot bash script: reads a file of SHA256 hashes, queries the VirusTotal v3 API for each, and forwards detection summaries to Splunk HEC.

virustotalsplunkhashenrichmentone-offpushmoderate
#!/usr/bin/env bash
# ============================================================
# RINOX INTEGRATION: VirusTotal -> Splunk HEC (one-off enrichment)
# Generated by Rinox (rinox.io)
# Use Case: Enrich a list of SHA256 hashes and forward to Splunk
# Language: bash
# ============================================================
set -euo pipefail

# ============================================================
# SECTION 1: LOGGING
# ============================================================
log()  { printf '%s [%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" "$2" >&2; }
info() { log INFO "$1"; }
err()  { log ERROR "$1"; }

# ============================================================
# SECTION 2: AUTHENTICATION
# ============================================================
: "${VT_API_KEY:?VT_API_KEY is required}"
: "${SPLUNK_HEC_URL:?SPLUNK_HEC_URL is required}"
: "${SPLUNK_HEC_TOKEN:?SPLUNK_HEC_TOKEN is required}"
SPLUNK_INDEX="${SPLUNK_INDEX:-threat_intel}"
HASHES_FILE="${HASHES_FILE:-./hashes.txt}"

if [[ ! -f "$HASHES_FILE" ]]; then
  err "HASHES_FILE not found: $HASHES_FILE"
  exit 1
fi

# ============================================================
# SECTION 3: SOURCE SYSTEM CALLS (VirusTotal)
# ============================================================
vt_lookup() {
  local hash="$1"
  curl -sS -X GET "https://www.virustotal.com/api/v3/files/$hash" \
    -H "x-apikey: $VT_API_KEY" \
    -w '\n%{http_code}'
}

# ============================================================
# SECTION 4: TRANSLATION
# ============================================================
to_splunk_event() {
  local hash="$1" body="$2"
  jq -c --arg hash "$hash" --arg index "$SPLUNK_INDEX" '
    {
      time: ((.data.attributes.last_analysis_date // (now | floor))),
      host: "virustotal",
      sourcetype: "vt:file",
      source: "rinox-vt-splunk",
      index: $index,
      event: {
        hash: $hash,
        type: (.data.attributes.type_description // null),
        size: (.data.attributes.size // null),
        meaningful_name: (.data.attributes.meaningful_name // null),
        last_analysis_stats: (.data.attributes.last_analysis_stats // {}),
        reputation: (.data.attributes.reputation // null),
        names: (.data.attributes.names // []),
        first_submission_date: (.data.attributes.first_submission_date // null),
      }
    }' <<<"$body"
}

# ============================================================
# SECTION 5: TARGET SYSTEM CALLS (Splunk HEC)
# ============================================================
deliver_batch() {
  local payload="$1"
  curl -sS -X POST "$SPLUNK_HEC_URL/services/collector/event" \
    -H "Authorization: Splunk $SPLUNK_HEC_TOKEN" \
    -H 'Content-Type: application/json' \
    --data-binary "$payload" \
    -w '\n%{http_code}'
}

# ============================================================
# SECTION 6: RINOX
# ============================================================
RINOX_RUN_ID="$(date +%s)-$RANDOM"
info "Rinox run started: id=$RINOX_RUN_ID"

# ============================================================
# SECTION 7: MAIN ORCHESTRATOR (one-off: FETCH → TRANSLATE → DELIVER → SUMMARY)
# ============================================================
processed=0
not_found=0
errors=0
batch=""

while IFS= read -r hash || [[ -n "$hash" ]]; do
  hash="${hash//[$'\t\r\n ']}"
  [[ -z "$hash" ]] && continue
  resp="$(vt_lookup "$hash")"
  code="${resp##*$'\n'}"
  body="${resp%$'\n'*}"

  case "$code" in
    200)
      event="$(to_splunk_event "$hash" "$body")" || { err "Failed to translate $hash"; errors=$((errors+1)); continue; }
      batch+="$event"$'\n'
      processed=$((processed+1))
      ;;
    404)
      not_found=$((not_found+1))
      info "Hash not found in VirusTotal: $hash"
      ;;
    429)
      err "Rate limited by VirusTotal; sleeping 60s before retry."
      sleep 60
      continue
      ;;
    *)
      err "VirusTotal $code for $hash"
      errors=$((errors+1))
      ;;
  esac
done <"$HASHES_FILE"

if [[ -z "$batch" ]]; then
  info "Nothing to deliver. processed=$processed not_found=$not_found errors=$errors"
  exit 0
fi

# Iron Phase 4: NDJSON (one event per line), not a JSON array.
resp="$(deliver_batch "$batch")"
code="${resp##*$'\n'}"
if [[ "$code" != 2* ]]; then
  err "Splunk HEC delivery failed: HTTP $code"
  exit 1
fi

info "Run complete: delivered=$processed not_found=$not_found errors=$errors rinox_id=$RINOX_RUN_ID"

Useful?

Used by 0 teams · Viewed 4 times · Last validated 5/17/2026