elkserver/docker/redelk-base/redelkinstalldata/scripts/modules/alarm_filehash/ioc_vt.py (93 lines of code) (raw):
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Part of RedELK
This check queries VirusTotal API given a list of md5 hashes.
Authors:
- Outflank B.V. / Mark Bergman (@xychix)
- Lorenzo Bernardi (@fastlorenzo)
"""
import logging
from datetime import datetime
import requests
from modules.helpers import get_value
# The Public API is limited to 500 requests per day and a rate of 4 requests per minute.
class VT:
"""This check queries VirusTotal API given a list of md5 hashes."""
def __init__(self, api_key):
self.logger = logging.getLogger("alarm_filehash.ioc_vt")
self.api_key = api_key
def get_remaining_quota(self):
"""Returns the number of hashes that could be queried within this run"""
url = f"https://www.virustotal.com/api/v3/users/{self.api_key}/overall_quotas"
headers = {"Accept": "application/json", "x-apikey": self.api_key}
# Get the quotas, if response code != 200, return 0 so we don't query further
response = requests.get(url, headers=headers)
if response.status_code == 200:
json_response = response.json()
else:
self.logger.warning(
"Error retrieving VT Quota (HTTP Status code: %d)", response.status_code
)
return 0
# Extract the hourly, daily and monthly remaining quotas
remaining_hourly = get_value(
"data.api_requests_hourly.user.allowed", json_response, 0
) - get_value("data.api_requests_hourly.user.used", json_response, 0)
remaining_daily = get_value(
"data.api_requests_daily.user.allowed", json_response, 0
) - get_value("data.api_requests_daily.user.used", json_response, 0)
remaining_monthly = get_value(
"data.api_requests_monthly.user.allowed", json_response, 0
) - get_value("data.api_requests_monthly.user.used", json_response, 0)
self.logger.debug(
"Remaining quotas: hourly(%d) / daily(%d) / monthly(%d)",
remaining_hourly,
remaining_daily,
remaining_monthly,
)
# Get the smallest one and return it
remaining_min = min(remaining_hourly, remaining_daily, remaining_monthly)
return remaining_min
def get_vt_file_results(self, filehash):
"""Queries VT API with file hash and returns the results or None if error / nothing found"""
url = f"https://www.virustotal.com/api/v3/files/{filehash}"
headers = {"Accept": "application/json", "x-apikey": self.api_key}
# Get the quotas, if response code != 200, return 0 so we don't query further
response = requests.get(url, headers=headers)
if response.status_code == 200: # Hash found
json_response = response.json()
elif response.status_code == 404: # Hash not found
json_response = None
else: # Unexpected result
self.logger.warning(
"Error retrieving VT File hash results (HTTP Status code: %d): %s",
response.status_code,
response.text,
)
json_response = None
return json_response
def test(self, hash_list):
"""run the query and build the report (results)"""
# Get the remaining quota for this run
remaining_quota = self.get_remaining_quota()
vt_results = {}
# Query VT API for file hashes
count = 0
for md5 in hash_list:
if count < remaining_quota:
# Within quota, let's check the file hash with VT
vt_result = self.get_vt_file_results(md5)
if vt_result is not None:
if isinstance(vt_result, type({})) and "data" in vt_result:
# Get first submission date
first_submitted_ts = get_value(
"data.attributes.first_submission_date", vt_result, None
)
try:
first_submitted_date = datetime.fromtimestamp(
first_submitted_ts
).isoformat()
# pylint: disable=broad-except
except Exception:
first_submitted_date = None
last_analysis_ts = get_value(
"data.attributes.last_analysis_date", vt_result, None
)
try:
last_analysis_date = datetime.fromtimestamp(
last_analysis_ts
).isoformat()
# pylint: disable=broad-except
except Exception:
last_analysis_date = None
# Found
vt_results[md5] = {
"record": vt_result,
"result": "newAlarm",
"first_submitted": first_submitted_date,
"last_seen": last_analysis_date,
}
else:
vt_results[md5] = {"result": "clean"}
else:
# 404 not found
vt_results[md5] = {"result": "clean"}
else:
# Quota reached, skip the check
vt_results[md5] = {"result": "skipped, quota reached"}
count += 1
return vt_results