diff --git a/analyzers/IRMA/irma.json b/analyzers/IRMA/irma.json new file mode 100644 index 000000000..9d4605602 --- /dev/null +++ b/analyzers/IRMA/irma.json @@ -0,0 +1,13 @@ +{ + "name": "IRMA_Scan", + "version": "0.1", + "description": "IRMA scan file", + "dataTypeList": ["file"], + "baseConfig": "IRMA", + "config": { + "check_tlp": false, + "service": "scan", + "max_tlp": 1 + }, + "command": "IRMA/irma.py" +} diff --git a/analyzers/IRMA/irma.py b/analyzers/IRMA/irma.py new file mode 100644 index 000000000..c6b5fd091 --- /dev/null +++ b/analyzers/IRMA/irma.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import sys +import time +import hashlib +import requests +import json +import urlparse + +from cortexutils.analyzer import Analyzer + +class IRMA(Analyzer): + + def __init__(self): + Analyzer.__init__(self) + self.service = self.getParam( + 'config.service', None, 'Service parameter is missing') + self.url = self.getParam( + 'config.url', None, 'IRMA URL parameter is missing') + self.timeout = self.getParam( + 'config.timeout', 60) + self.scan = self.getParam( + 'config.scan', 1) + self.force = self.getParam( + 'config.force', 1) + self.verify = self.getParam( + 'config.verify', True) + self.time_start = time.time() + + def summary(self, raw): + result = { + "has_result": True + } + + if 'probe_results' in raw: + result['analysis_results'] = raw['probe_results'] + + return result + + """Gets antivirus signatures from IRMA for various results. + Currently obtains IRMA results for the target sample. + """ + # IRMA statuses https://github.com/quarkslab/irma-cli/blob/master/irma/apiclient.py + IRMA_FINISHED_STATUS = 50 + + def _request_json(self, url, **kwargs): + """Wrapper around doing a request and parsing its JSON output.""" + try: + r = requests.get(url, timeout=self.timeout, verify=self.verify, **kwargs) + return r.json() if r.status_code == 200 else {} + except (requests.ConnectionError, ValueError) as e: + self.unexpectedError(e) + + def _post_json(self, url, **kwargs): + """Wrapper around doing a post and parsing its JSON output.""" + try: + r = requests.post(url, timeout=self.timeout, verify=self.verify, **kwargs) + return r.json() if r.status_code == 200 else {} + except (requests.ConnectionError, ValueError) as e: + self.unexpectedError(e) + + def _scan_file(self, filepath, force): + # Initialize scan in IRMA. + init = self._post_json(urlparse.urljoin(self.url, "/api/v1.1/scans")) + + # Post file for scanning. + files = { + "files": open(filepath, "rb"), + } + url = urlparse.urljoin( + self.url, "/api/v1.1/scans/%s/files" % init.get("id") + ) + self._post_json(url, files=files, ) + + # launch posted file scan + params = { + "force": force, + } + url = urlparse.urljoin( + self.url, "/api/v1.1/scans/%s/launch" % init.get("id") + ) + requests.post(url, json=params, verify=self.verify) + + result = None + + while result is None or result.get( + "status") != self.IRMA_FINISHED_STATUS or time.time() < self.time_start + self.timeout: + url = urlparse.urljoin( + self.url, "/api/v1.1/scans/%s" % init.get("id") + ) + result = self._request_json(url) + time.sleep(10) + + return + + def _get_results(self, sha256): + # Fetch list of scan IDs. + results = self._request_json( + urlparse.urljoin(self.url, "/api/v1.1/files/%s" % sha256) + ) + + if not results.get("items"): + return + + result_id = results["items"][-1]["result_id"] + return self._request_json( + urlparse.urljoin(self.url, "/api/v1.1/results/%s" % result_id) + ) + + + + def run(self): + Analyzer.run(self) + + if self.service == 'scan': + if self.data_type == 'file': + filename = self.getParam('attachment.name', 'noname.ext') + filepath = self.getParam('file', None, 'File is missing') + hashes = self.getParam('attachment.hashes', None) + if hashes is None: + hash = hashlib.sha256(open(filepath, 'r').read()).hexdigest() + else: + # find SHA256 hash + hash = next(h for h in hashes if len(h) == 64) + + results = self._get_results(hash) + + if not self.force and not self.scan and not results: + return {} + elif self.force or (not results and self.scan): + self._scan_file(filepath, self.force) + results = self._get_results(hash) or {} + + """ FIXME! could use a proper fix here + that probably needs changes on IRMA side aswell + -- + related to https://github.com/elastic/elasticsearch/issues/15377 + entropy value is sometimes 0 and sometimes like 0.10191042566270775 + other issue is that results type changes between string and object :/ + """ + for idx, result in enumerate(results["probe_results"]): + if result["name"] == "PE Static Analyzer": + results["probe_results"][idx]["results"] = None + + self.report(results) + + + else: + self.error('Invalid data type') + else: + self.error('Invalid service') + + +if __name__ == '__main__': + IRMA().run() diff --git a/analyzers/IRMA/reference.conf b/analyzers/IRMA/reference.conf new file mode 100644 index 000000000..0822eef0d --- /dev/null +++ b/analyzers/IRMA/reference.conf @@ -0,0 +1,4 @@ +# Add the following to cortex analyzer configuration +IRMA { + url="http://some-irma-url" + } diff --git a/analyzers/IRMA/requirements.txt b/analyzers/IRMA/requirements.txt new file mode 100644 index 000000000..2cac23c03 --- /dev/null +++ b/analyzers/IRMA/requirements.txt @@ -0,0 +1,2 @@ +requests +cortexutils diff --git a/analyzers/WhoisXMLAPI/reference.conf b/analyzers/WhoisXMLAPI/reference.conf new file mode 100644 index 000000000..9ae340770 --- /dev/null +++ b/analyzers/WhoisXMLAPI/reference.conf @@ -0,0 +1,6 @@ +# Add to analyser configuration + +WhoisXMLAPI { + username="someusername" + password="somepassword" +} diff --git a/analyzers/WhoisXMLAPI/requirements.txt b/analyzers/WhoisXMLAPI/requirements.txt new file mode 100644 index 000000000..8ad52a568 --- /dev/null +++ b/analyzers/WhoisXMLAPI/requirements.txt @@ -0,0 +1 @@ +cortexutils diff --git a/analyzers/WhoisXMLAPI/whoisxmlapi.json b/analyzers/WhoisXMLAPI/whoisxmlapi.json new file mode 100644 index 000000000..221c278c6 --- /dev/null +++ b/analyzers/WhoisXMLAPI/whoisxmlapi.json @@ -0,0 +1,13 @@ +{ + "name": "WhoisXMLAPI", + "description": "Query a domain against the WhoisXMLAPI", + "dataTypeList": ["domain", "ip", "fqdn", "ip"], + "version": "0.1", + "baseConfig": "WhoisXMLAPI", + "config": { + "check_tlp": true, + "max_tlp": 3, + "service": "query" + }, + "command": "WhoisXMLAPI/whoisxmlapi.py" +} diff --git a/analyzers/WhoisXMLAPI/whoisxmlapi.py b/analyzers/WhoisXMLAPI/whoisxmlapi.py new file mode 100644 index 000000000..76e914339 --- /dev/null +++ b/analyzers/WhoisXMLAPI/whoisxmlapi.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +####################################### +# Author: Unit777 # +####################################### + +import urllib2 +import json +from cortexutils.analyzer import Analyzer +import logging + +class WhoisXMLAPI(Analyzer): + + def __init__(self): + Analyzer.__init__(self) + self.username = self.getParam( + 'config.username', None, 'Username parameter is missing') + self.password = self.getParam( + 'config.password', None, 'Password parameter is missing') + self.service = self.getParam( + 'config.service', None, 'Service parameter is missing') + + def run(self): + if self.data_type == "domain" or self.data_type == "ip" or self.data_type == "url" or self.data_type == "fqdn": + whoisxmlapiURL = 'https://www.whoisxmlapi.com/whoisserver/WhoisService?domainName=' + self.getData() + '&username=' + self.username + '&password=' + self.password + '&outputFormat=' + "JSON" + #TODO: Add proxy capability + result = json.loads(urllib2.urlopen(whoisxmlapiURL).read().decode('utf8')) + if 'audit' in result: + if 'createdDate' in result['audit']: + if '$' in result['audit']['createdDate']: + result['audit']['createdDate'] = js['audit']['createdDate']['$'] + if 'updatedDate' in result['audit']: + if '$' in result['audit']['updatedDate']: + result['audit']['updatedDate'] = js['audit']['updatedDate']['$'] + + self.report({'whoisxmlapi': result}) + + +if __name__ == '__main__': + WhoisXMLAPI().run() diff --git a/thehive-templates/IRMA/long.html b/thehive-templates/IRMA/long.html new file mode 100644 index 000000000..216cfbb15 --- /dev/null +++ b/thehive-templates/IRMA/long.html @@ -0,0 +1,25 @@ +
+
+
+ Summary +
+
+ + + + + + + + + + + + +
ScannerDetectedResult
+ {{probe.name}} + + + {{probe.results}}
+
+
diff --git a/thehive-templates/WhoisXMLAPI/long.html b/thehive-templates/WhoisXMLAPI/long.html new file mode 100644 index 000000000..248418ec8 --- /dev/null +++ b/thehive-templates/WhoisXMLAPI/long.html @@ -0,0 +1,177 @@ +
+
+ {{artifact.data | fang}} +
+
+ {{content.errorMessage}} +
+
+ +
+
+
+ Summary +
+
+ +
+
ERROR:
+
{{content.errortext}} 
+
+ +
+
Domain:
+
{{content.whoisxmlapi.WhoisRecord.domainName| fang}}
+
+
+
Organisation:
+
{{content.whoisxmlapi.WhoisRecord.registrant.organization}}
+
+
+
Name Servers:
+
{{ns}}
+
{{ns}}
+
+
+
Updated Date:
+
{{content.whoisxmlapi.WhoisRecord.registryData.updatedDate || content.whoisxmlapi.WhoisRecord.registryData.audit.updatedDate}}
+
+
+
Creation Date:
+
{{content.whoisxmlapi.WhoisRecord.registryData.createdDate || content.whoisxmlapi.WhoisRecord.registryData.audit.createdDate}}
+
+
+
Expiration Date:
+
{{content.whoisxmlapi.WhoisRecord.registryData.expiresDate || content.whoisxmlapi.WhoisRecord.registryData.audit.expiresDate}}
+
+
+
Registrar:
+
{{content.whoisxmlapi.WhoisRecord.registrarName || content.whoisxmlapi.WhoisRecord.registryData.registrarName}}
+
+ +
+
+ +
+
+ Registrant Info +
+
+ +
+
Name:
+
{{content.whoisxmlapi.WhoisRecord.registrant.name || content.whoisxmlapi.WhoisRecord.registryData.registrant.name}}
+
+
+
Email:
+
{{content.whoisxmlapi.WhoisRecord.registrant.email || content.whoisxmlapi.WhoisRecord.contactEmail}}
+
+
+
Phone:
+
{{content.whoisxmlapi.WhoisRecord.registrant.telephone || content.whoisxmlapi.WhoisRecord.registryData.registrant.telephone}}
+
+
+
Organisation:
+
{{content.whoisxmlapi.WhoisRecord.registrant.organization || content.whoisxmlapi.WhoisRecord.registryData.registrant.organization}}
+
+
+
Address:
+
{{content.whoisxmlapi.WhoisRecord.registrant.street1 || content.whoisxmlapi.WhoisRecord.registryData.registrant.street1}}, {{content.whoisxmlapi.WhoisRecord.registrant.city || content.whoisxmlapi.WhoisRecord.registryData.registrant.city}}, {{content.whoisxmlapi.WhoisRecord.registrant.state || content.whoisxmlapi.WhoisRecord.registryData.registrant.state}}, {{content.whoisxmlapi.WhoisRecord.registrant.postalCode || content.whoisxmlapi.WhoisRecord.registryData.registrant.postalCode}}, {{content.whoisxmlapi.WhoisRecord.registrant.country || content.whoisxmlapi.WhoisRecord.registryData.registrant.country}}
+
+ +
+
+ +
+
+ Registrar Info +
+
+ +
+
Registrar:
+
{{content.whoisxmlapi.WhoisRecord.registrarName}}
+
+
+
IANA ID:
+
{{content.whoisxmlapi.WhoisRecord.registrarIANAID}}
+
+
+
URL:
+
{{content.whoisxmlapi.WhoisRecord.registryData.referralURL}}
+
+
+
Whois Server:
+
{{content.whoisxmlapi.WhoisRecord.registryData.whoisServer}}
+
+
+
Email:
+
{{content.whoisxmlapi.WhoisRecord.customField1Value}}
+
+
+
Phone:
+
{{content.whoisxmlapi.WhoisRecord.customField2Value}}
+
+ +
+
+ +
+
+ Technical Contact +
+
+ +
+
Name:
+
{{content.whoisxmlapi.WhoisRecord.technicalContact.name || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.name}}
+
+
+
Email:
+
{{content.whoisxmlapi.WhoisRecord.technicalContact.email || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.email}}
+
+
+
Phone:
+
{{content.whoisxmlapi.WhoisRecord.technicalContact.telephone || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.telephone}}
+
+
+
Organisation:
+
{{content.whoisxmlapi.WhoisRecord.technicalContact.organization || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.organization}}
+
+
+
Address:
+
{{content.whoisxmlapi.WhoisRecord.technicalContact.street1 || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.street1}}, {{content.whoisxmlapi.WhoisRecord.technicalContact.city || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.city}}, {{content.whoisxmlapi.WhoisRecord.technicalContact.state || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.state}}, {{content.whoisxmlapi.WhoisRecord.technicalContact.postalCode || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.postalCode}}, {{content.whoisxmlapi.WhoisRecord.technicalContact.country || content.whoisxmlapi.WhoisRecord.registryData.technicalContact.country}}
+
+ +
+
+
+
+ Administrative Contact +
+
+ +
+
Name:
+
{{content.whoisxmlapi.WhoisRecord.administrativeContact.name || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.name}}
+
+
+
Email:
+
{{content.whoisxmlapi.WhoisRecord.administrativeContact.email || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.email}}
+
+
+
Phone:
+
{{content.whoisxmlapi.WhoisRecord.administrativeContact.telephone || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.telephone}}
+
+
+
Organisation:
+
{{content.whoisxmlapi.WhoisRecord.administrativeContact.organization || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.organization}}
+
+
+
Address:
+
{{content.whoisxmlapi.WhoisRecord.administrativeContact.street1 || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.street1}}, {{content.whoisxmlapi.WhoisRecord.administrativeContact.city || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.city}}, {{content.whoisxmlapi.WhoisRecord.administrativeContact.state || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.state}}, {{content.whoisxmlapi.WhoisRecord.administrativeContact.postalCode || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.postalCode}}, {{content.whoisxmlapi.WhoisRecord.administrativeContact.country || content.whoisxmlapi.WhoisRecord.registryData.administrativeContact.country}}
+
+ +
+
+