| name | malware-hash |
| description | File hash reputation lookup via VirusTotal API v3 for MD5/SHA1/SHA256 detection ratio, threat classification, and vendor results |
| license | MIT |
| metadata | {"category":"incident-response","locale":"en","phase":"v1"} |
What this skill does
Accepts a file hash (MD5/SHA1/SHA256) or file path and queries the VirusTotal API v3 for malware reputation. Parses detection ratio, per-vendor results, tags, and first-seen date to summarize the threat level.
When to use
- Quickly checking whether a suspicious file is a known malware sample
- Assessing the threat level of hashes collected during incident response
- Bulk-querying hashes from an IOC list
- Prioritizing files before forensic analysis
Prerequisites
curl (HTTP requests)
python3 (JSON parsing)
- VirusTotal API key: environment variable
SECSKILL_VT_API_KEY
API key setup (in priority order)
export SECSKILL_VT_API_KEY="your_api_key_here"
mkdir -p ~/.config/security-skill
echo 'SECSKILL_VT_API_KEY=your_api_key_here' >> ~/.config/security-skill/secrets.env
chmod 600 ~/.config/security-skill/secrets.env
Inputs
| Field | Description | Example |
|---|
HASH_OR_FILE | MD5/SHA1/SHA256 hash string or file path | 44d88612fea8a8f36de82e1278abb02f |
Workflow
Step 1: Verify API key credentials
resolve_vt_api_key() {
if [ -n "$SECSKILL_VT_API_KEY" ]; then
echo "[+] API key: loaded from environment variable"
return 0
fi
local secrets_file="$HOME/.config/security-skill/secrets.env"
if [ -f "$secrets_file" ]; then
source "$secrets_file"
if [ -n "$SECSKILL_VT_API_KEY" ]; then
echo "[+] API key: loaded from $secrets_file"
return 0
fi
fi
echo "[!] VirusTotal API key not found."
echo " Get a free key at https://www.virustotal.com/gui/my-apikey"
read -rsp "Enter API key: " SECSKILL_VT_API_KEY
echo
export SECSKILL_VT_API_KEY
read -rp "Save this key to $secrets_file? [y/N] " save_key
if [[ "$save_key" =~ ^[Yy]$ ]]; then
mkdir -p "$(dirname "$secrets_file")"
echo "SECSKILL_VT_API_KEY=$SECSKILL_VT_API_KEY" >> "$secrets_file"
chmod 600 "$secrets_file"
echo "[+] API key saved"
fi
}
resolve_vt_api_key
Step 2: Validate input and compute hash
HASH_OR_FILE="${SECSKILL_HASH_OR_FILE:-${1:-}}"
if [ -z "$HASH_OR_FILE" ]; then
read -rp "Enter hash or file path: " HASH_OR_FILE
fi
if [ -f "$HASH_OR_FILE" ]; then
echo "[+] File detected: computing hashes..."
MD5_HASH=$(md5sum "$HASH_OR_FILE" | cut -d' ' -f1)
SHA1_HASH=$(sha1sum "$HASH_OR_FILE" | cut -d' ' -f1)
SHA256_HASH=$(sha256sum "$HASH_OR_FILE" | cut -d' ' -f1)
echo " MD5: $MD5_HASH"
echo " SHA1: $SHA1_HASH"
echo " SHA256: $SHA256_HASH"
QUERY_HASH="$SHA256_HASH"
echo "[+] Using SHA256 for VirusTotal lookup"
else
QUERY_HASH="$HASH_OR_FILE"
HASH_LEN=${#QUERY_HASH}
case $HASH_LEN in
32) echo "[+] MD5 hash detected" ;;
40) echo "[+] SHA1 hash detected" ;;
64) echo "[+] SHA256 hash detected" ;;
*) echo "[!] Invalid hash length: $HASH_LEN (MD5=32, SHA1=40, SHA256=64)"; exit 1 ;;
esac
fi
Step 3: Query the VirusTotal API
echo "[*] Querying VirusTotal API..."
VT_RESPONSE=$(curl -s \
--request GET \
--url "https://www.virustotal.com/api/v3/files/${QUERY_HASH}" \
--header "x-apikey: ${SECSKILL_VT_API_KEY}")
if echo "$VT_RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if 'data' in d else 1)" 2>/dev/null; then
echo "[+] Query successful"
echo "$VT_RESPONSE" > /tmp/vt_response.json
else
ERROR_MSG=$(echo "$VT_RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('error',{}).get('message','Unknown error'))" 2>/dev/null)
echo "[!] API error: $ERROR_MSG"
echo " Response: $VT_RESPONSE"
exit 1
fi
Step 4: Parse results and assess threat level
python3 - <<'PYEOF'
import json, sys
from datetime import datetime
with open('/tmp/vt_response.json') as f:
data = json.load(f)
attrs = data['data']['attributes']
stats = attrs.get('last_analysis_stats', {})
results = attrs.get('last_analysis_results', {})
tags = attrs.get('tags', [])
malicious = stats.get('malicious', 0)
suspicious = stats.get('suspicious', 0)
undetected = stats.get('undetected', 0)
total = malicious + suspicious + undetected + stats.get('harmless', 0)
if malicious >= 10:
threat_level = "HIGH (likely malicious)"
threat_emoji = "[!!!]"
elif malicious >= 3:
threat_level = "MEDIUM (further investigation needed)"
threat_emoji = "[!]"
elif malicious >= 1 or suspicious >= 1:
threat_level = "LOW (suspicious)"
threat_emoji = "[?]"
else:
threat_level = "CLEAN (not detected)"
threat_emoji = "[OK]"
first_seen_ts = attrs.get('first_submission_date')
first_seen = datetime.fromtimestamp(first_seen_ts).strftime('%Y-%m-%d %H:%M:%S') if first_seen_ts else 'N/A'
last_seen_ts = attrs.get('last_submission_date')
last_seen = datetime.fromtimestamp(last_seen_ts).strftime('%Y-%m-%d %H:%M:%S') if last_seen_ts else 'N/A'
print("=" * 60)
print("VirusTotal Hash Reputation Results")
print("=" * 60)
print(f"\n{threat_emoji} Threat level: {threat_level}")
print(f" Detection ratio: {malicious}/{total} engines")
print(f" Suspicious: {suspicious}, Undetected: {undetected}")
print(f"\n First seen: {first_seen}")
print(f" Last seen: {last_seen}")
if tags:
print(f"\n Tags: {', '.join(tags)}")
detecting_vendors = [
(vendor, info['result'])
for vendor, info in results.items()
if info.get('category') in ('malicious', 'suspicious')
]
if detecting_vendors:
print(f"\n[Detecting vendors] ({len(detecting_vendors)} total)")
for vendor, result in sorted(detecting_vendors):
print(f" - {vendor}: {result}")
file_type = attrs.get('type_description', attrs.get('magic', 'N/A'))
file_size = attrs.get('size', 'N/A')
file_name = attrs.get('meaningful_name', attrs.get('names', ['N/A'])[0] if attrs.get('names') else 'N/A')
print(f"\n[File info]")
print(f" Name: {file_name}")
print(f" Type: {file_type}")
print(f" Size: {file_size} bytes" if isinstance(file_size, int) else f" Size: {file_size}")
print("\n[+] Details: https://www.virustotal.com/gui/file/" + data['data']['id'])
PYEOF
Step 5: Save results
python3 - <<'PYEOF'
import json
with open('/tmp/vt_response.json') as f:
data = json.load(f)
attrs = data['data']['attributes']
stats = attrs.get('last_analysis_stats', {})
malicious = stats.get('malicious', 0)
total = sum(stats.values())
file_id = data['data']['id']
with open('/tmp/vt_result_summary.txt', 'w') as f:
f.write(f"hash={file_id}\n")
f.write(f"malicious={malicious}\n")
f.write(f"total={total}\n")
f.write(f"url=https://www.virustotal.com/gui/file/{file_id}\n")
print("[+] Summary saved: /tmp/vt_result_summary.txt")
print("[+] Full response: /tmp/vt_response.json")
PYEOF
Done when
- Detection ratio (malicious/total) is printed
- Threat level (HIGH/MEDIUM/LOW/CLEAN) is determined
- List of detecting vendors is shown (when detections exist)
- Summary saved to
/tmp/vt_result_summary.txt
Failure modes
| Problem | Cause | Solution |
|---|
401 Unauthorized | Missing or invalid API key | Re-check SECSKILL_VT_API_KEY |
404 Not Found | Hash not in VT database | Upload file directly (via VT website) |
429 Too Many Requests | Free API rate limit exceeded | Wait 1 minute and retry (free tier: 4 req/min) |
| Hash length error | Invalid hash input | MD5=32 chars, SHA1=40 chars, SHA256=64 chars |
| curl not installed | Missing tool | sudo apt install curl |
Notes
- The VirusTotal free API is limited to 4 requests/minute and 500 requests/day.
- New malware may not yet be in VT. A CLEAN result does not guarantee safety.
- Uploading a file directly to VT shares that file and its metadata with VirusTotal. For sensitive files, query by hash only.
- When querying hashes from extracted IOCs, use in conjunction with the
ioc-extract skill.