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