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