From b654a9743b19075a6f121aaf27cf1fcf94e03ad9 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Thu, 11 Aug 2016 16:33:02 +0100 Subject: [PATCH 01/14] Added stix import -- works for IPs/Domains --- misp_modules/modules/expansion/stiximport.py | 89 ++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100755 misp_modules/modules/expansion/stiximport.py diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py new file mode 100755 index 0000000..4304cef --- /dev/null +++ b/misp_modules/modules/expansion/stiximport.py @@ -0,0 +1,89 @@ +import json +import stix +import csv +from stix.core import STIXPackage +import re +import base64 + +misperrors = {'error': 'Error'} +userConfig = {} +inputSource = ['file'] + +moduleinfo = {'version': '0.1', 'author': 'Hannah Ward', + 'description': 'Import some stix stuff', + 'module-type': ['import']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + r = {'results': []} + q = json.loads(q) + #Load the package up + package = str(base64.b64decode(q.get("data", None)), 'utf-8') + if not package: + return json.dumps({"success":0}) + + package = loadPackage(package) + if package.observables: + for obs in package.observables: + r["results"].append(buildObservable(obs)) + + return r + +ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") +def buildObservable(o): + #Life is easier with json + o = json.loads(o.to_json()) + print(o) + r = {"values":[]} + props = o["object"]["properties"] + if props["address_value"]: + #We've got ourselves a nice little address + value = props["address_value"] + #Is it an IP? + if ipre.match(value): + #Yes! + r["values"].append(value) + r["types"] = ["ip-src", "ip-dst"] + else: + #Probably a domain yo + r["values"].append(value) + r["types"] = ["domain", "hostname"] + + return r + +def loadPackage(data): + #Write the stix package to a tmp file + with open("/tmp/stixdump", "w") as f: + f.write(data) + try: + try: + package = STIXPackage().from_xml(open("/tmp/stixdump", "r")) + except: + package = STIXPackage().from_json(open("/tmp/stixdump", "r")) + except: + print("Failed to load package") + raise ValueError("COULD NOT LOAD STIX PACKAGE!") + return package + +def introspection(): + modulesetup = {} + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From c106aa662b378b81db83a25b45be2e1db0da6bf3 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Thu, 11 Aug 2016 16:37:29 +0100 Subject: [PATCH 02/14] Added docs to stiximport --- misp_modules/modules/expansion/stiximport.py | 36 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index 4304cef..ce4ff8f 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -17,38 +17,69 @@ moduleconfig = [] def handler(q=False): + #Just in case we have no data if q is False: return False + + #The return value r = {'results': []} + + #Load up that JSON q = json.loads(q) - #Load the package up + + #It's b64 encoded, so decode that stuff package = str(base64.b64decode(q.get("data", None)), 'utf-8') + + #If something really weird happened if not package: return json.dumps({"success":0}) + #Load up the package into STIX package = loadPackage(package) + + #Build all the observables if package.observables: for obs in package.observables: r["results"].append(buildObservable(obs)) return r +#Quick and dirty regex for IP addresses ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") + def buildObservable(o): + """ + Take a STIX observable + and extract the value + and category + """ + #Life is easier with json o = json.loads(o.to_json()) - print(o) + + #Make a new record to store values in r = {"values":[]} + + #Get the object properties. This contains all the + #fun stuff like values props = o["object"]["properties"] + + #If it has an address_value field, it's gonna be an address + + #Kinda obvious really if props["address_value"]: + #We've got ourselves a nice little address value = props["address_value"] + #Is it an IP? if ipre.match(value): + #Yes! r["values"].append(value) r["types"] = ["ip-src", "ip-dst"] else: + #Probably a domain yo r["values"].append(value) r["types"] = ["domain", "hostname"] @@ -60,6 +91,7 @@ def loadPackage(data): with open("/tmp/stixdump", "w") as f: f.write(data) try: + #Try loading it into every format we know of try: package = STIXPackage().from_xml(open("/tmp/stixdump", "r")) except: From 3f7cdad0c31067988acfd5f3b875a92166735246 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 10:06:53 +0100 Subject: [PATCH 03/14] Threat actors now get imported by stix --- misp_modules/modules/expansion/stiximport.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index ce4ff8f..09be362 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -41,12 +41,26 @@ def handler(q=False): if package.observables: for obs in package.observables: r["results"].append(buildObservable(obs)) - + + if package.threat_actors: + for ta in package.threat_actors: + r["results"].append(buildActor(ta)) return r #Quick and dirty regex for IP addresses ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") +def buildActor(ta): + """ + Extract the name + and comment of a + threat actor + """ + + r = {"values":[ta.title], "types":["threat-actor"]} + + return r + def buildObservable(o): """ Take a STIX observable From 46f8141071cd370033cbd7ed20a29ff8e5cc6314 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 10:10:19 +0100 Subject: [PATCH 04/14] Added STIXImport to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index deaeec2..d042635 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [IPASN](misp_modules/modules/expansion/ipasn.py) - a hover and expansion to get the BGP ASN of an IP address. * [passivetotal](misp_modules/modules/expansion/passivetotal.py) - a [passivetotal](https://www.passivetotal.org/) module that queries a number of different PassiveTotal datasets. * [sourcecache](misp_modules/modules/expansion/sourcecache.py) - a module to cache a specific link from a MISP instance. - +* [stiximport](misp_modules/moduls/expansion/stiximport.py) - An import module to process STIX xml/json ## How to install and start MISP modules? ~~~~bash From 29b57258abc19942c18ed188909680271afc540f Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 10:11:13 +0100 Subject: [PATCH 05/14] I can't spell --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d042635..927766a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [IPASN](misp_modules/modules/expansion/ipasn.py) - a hover and expansion to get the BGP ASN of an IP address. * [passivetotal](misp_modules/modules/expansion/passivetotal.py) - a [passivetotal](https://www.passivetotal.org/) module that queries a number of different PassiveTotal datasets. * [sourcecache](misp_modules/modules/expansion/sourcecache.py) - a module to cache a specific link from a MISP instance. -* [stiximport](misp_modules/moduls/expansion/stiximport.py) - An import module to process STIX xml/json +* [stiximport](misp_modules/modules/expansion/stiximport.py) - An import module to process STIX xml/json ## How to install and start MISP modules? ~~~~bash From 598a030962135005136cb226e4b2b4d3e25b781f Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 11:22:42 +0100 Subject: [PATCH 06/14] stiximport will now identify file hashes --- misp_modules/modules/expansion/stiximport.py | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index 09be362..04d0c07 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -4,6 +4,7 @@ import csv from stix.core import STIXPackage import re import base64 +import hashlib misperrors = {'error': 'Error'} userConfig = {} @@ -45,11 +46,49 @@ def handler(q=False): if package.threat_actors: for ta in package.threat_actors: r["results"].append(buildActor(ta)) + + if package.indicators: + for ind in package.indicators: + r["results"].append(buildIndicator(ind)) return r #Quick and dirty regex for IP addresses ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") +def identifyHash(hsh): + """ + What's that hash!? + """ + + possible_hashes = [] + + hashes = [x for x in hashlib.algorithms_guaranteed] + + for h in hashes: + if len(str(hsh.value)) == len(hashlib.new(h).hexdigest()): + possible_hashes.append(h) + possible_hashes.append("filename|{}".format(h)) + + return possible_hashes + +def buildIndicator(ind): + """ + Extract hashes + and other fun things + like that + """ + r = {"values":[], "types":[]} + + #Try to get hashes. I hate stix + if ind.observable: + if ind.observable.object_: + #Get some hashes + hashes = ind.observable.object_.properties.hashes + for hsh in hashes: + r["values"].append(hsh.simple_hash_value.value) + r["types"] = identifyHash(hsh.simple_hash_value) + return r + def buildActor(ta): """ Extract the name @@ -110,7 +149,7 @@ def loadPackage(data): package = STIXPackage().from_xml(open("/tmp/stixdump", "r")) except: package = STIXPackage().from_json(open("/tmp/stixdump", "r")) - except: + except Exception as ex: print("Failed to load package") raise ValueError("COULD NOT LOAD STIX PACKAGE!") return package From faddf8378eabc3903abdf5302ddc2b49200a78f8 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 11:34:43 +0100 Subject: [PATCH 07/14] Stiximport will now consume campaigns --- misp_modules/modules/expansion/stiximport.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index 04d0c07..909981f 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -50,11 +50,43 @@ def handler(q=False): if package.indicators: for ind in package.indicators: r["results"].append(buildIndicator(ind)) + + if package.exploit_targets: + for et in package.exploit_targets: + r["results"].append(buildExploitTarget(et)) + + if package.campaigns: + for cpn in package.campaigns: + r["results"].append(buildCampaign(cpn)) + #Clean up results + #Don't send on anything that didn't have a value + r["results"] = [x for x in r["results"] if len(x["values"]) != 0] return r #Quick and dirty regex for IP addresses ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") +def buildCampaign(cpn): + """ + Extract a campaign name + """ + + return {"values":[cpn.title], "types":["campaign-name"]} + +def buildExploitTarget(et): + """ + Extract CVEs from exploit targets + """ + + r = {"values":[], "types":["vulnerability"]} + + if et.vulnerabilities: + for v in et.vulnerabilities: + if v.cve_id: + r["values"].append(v.cve_id) + + return r + def identifyHash(hsh): """ What's that hash!? From a34014e245c064505a439c46f3f8cae4813e10ab Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 11:56:48 +0100 Subject: [PATCH 08/14] Fixed observables within an indicator not being added --- misp_modules/modules/expansion/stiximport.py | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index 909981f..e3e9f20 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -97,7 +97,7 @@ def identifyHash(hsh): hashes = [x for x in hashlib.algorithms_guaranteed] for h in hashes: - if len(str(hsh.value)) == len(hashlib.new(h).hexdigest()): + if len(str(hsh)) == len(hashlib.new(h).hexdigest()): possible_hashes.append(h) possible_hashes.append("filename|{}".format(h)) @@ -113,12 +113,7 @@ def buildIndicator(ind): #Try to get hashes. I hate stix if ind.observable: - if ind.observable.object_: - #Get some hashes - hashes = ind.observable.object_.properties.hashes - for hsh in hashes: - r["values"].append(hsh.simple_hash_value.value) - r["types"] = identifyHash(hsh.simple_hash_value) + return buildObservable(ind.observable) return r def buildActor(ta): @@ -150,15 +145,19 @@ def buildObservable(o): props = o["object"]["properties"] #If it has an address_value field, it's gonna be an address - + print(props) #Kinda obvious really - if props["address_value"]: - + if "address_value" in props: + #We've got ourselves a nice little address value = props["address_value"] + if isinstance(value, dict): + #Sometimes it's embedded in a dictionary + value = value["value"] + #Is it an IP? - if ipre.match(value): + if ipre.match(str(value)): #Yes! r["values"].append(value) @@ -169,6 +168,10 @@ def buildObservable(o): r["values"].append(value) r["types"] = ["domain", "hostname"] + if "hashes" in props: + for hsh in props["hashes"]: + r["values"].append(hsh["simple_hash_value"]["value"]) + r["types"] = identifyHash(hsh["simple_hash_value"]["value"]) return r def loadPackage(data): From c02a452c05f17219ad19b42f069e79d1c54083f4 Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 12:16:49 +0100 Subject: [PATCH 09/14] added tests, also disregards related_observables. Because they're useless --- misp_modules/modules/expansion/stiximport.py | 8 +- tests/stix.xml | 331 +++++++++++++++++++ tests/test.py | 21 +- 3 files changed, 354 insertions(+), 6 deletions(-) create mode 100644 tests/stix.xml diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py index e3e9f20..5ea0010 100755 --- a/misp_modules/modules/expansion/stiximport.py +++ b/misp_modules/modules/expansion/stiximport.py @@ -135,13 +135,17 @@ def buildObservable(o): """ #Life is easier with json - o = json.loads(o.to_json()) - + if not isinstance(o, dict): + o = json.loads(o.to_json()) #Make a new record to store values in r = {"values":[]} #Get the object properties. This contains all the #fun stuff like values + if "observable_composition" in o: + #May as well be useless + return r + props = o["object"]["properties"] #If it has an address_value field, it's gonna be an address diff --git a/tests/stix.xml b/tests/stix.xml new file mode 100644 index 0000000..a4a60d8 --- /dev/null +++ b/tests/stix.xml @@ -0,0 +1,331 @@ + + + + CNC Server 1 + + + 82.146.166.56 + + + + + CNC Server 2 + + + 209.239.79.47 + + + + + CNC Server 3 + + + 41.213.121.180 + + + + + Watering Hole Wordpress + + + eu-society.com + + + + + Watering Hole Wordpress + + + aromatravel.org + + + + + Watering Hole Wordpress + + + bss.servebbs.com + + + + + + + Watering Hole Detected + URL Watchlist + + + + C2 List + + + C2 List + + + C2 List + + + + + + CnC Beaconing Detected + C2 + + + + + + + + + + + + + + + Malware CnC Channels + + Advantage + + + + Hosting + + + + + + + + + + + + + Fingerprinting and whitelisting during watering-hole operations + + Theft - Credential Theft + + + + Domain Registration + + + C2 List + + + C2 List + + + C2 List + + + + + + + + + + Spear-phishing in tandem with 0-day exploits + + Unauthorized Access + + + + + + + Infiltration of organisations via third party supplier/partner + + Unauthorized Access + + + + + + + Custom recon tool to compromise and identify credentials of the network + + Theft - Credential Theft + + + + + + + Multiple means of C2 communications given the diversity of the attacker toolset + + Advantage + + + + + + + rootkit communicates during the same time as network activity, encoded with an XOR key + + Advantage + + + + + + + Kernel-centric rootkit waits for network trigger before launching + + Advantage + + + + + + + Kernel centric exfiltration over TCP/UDP/DNS/ICMP/HTTP + + Theft + + + + + + + Exfiltration over HTTP/HTTPS + + Theft + + + + + + + Use of previously undocumented functions in their Kernel centric attacks + + Advantage + + + + + + + + + + + + + + + + + Privilage Escalation Vulnerability + + CVE-2013-5065 + + + + + + The Epic Turla Campaign + The Epic Turla Campaign + + Advantage - Political + + + + + + + + + + SNAKE Campaign + The SNAKE Campaign + + Advantage - Political + + + + + + + + + + + + SNAKE + +The group behind the SNAKE campaign are a top tier nation-state threat. Their capabilities extend from subtle watering-hole attacks to sophisticated server rootkits – virtually undetectable by conventional security products. +This threat actor group has been operating continuously for over a decade, infiltrating governments and strategic private sector networks in that time. The most notorious of their early campaigns led to a breach of classified US military systems, an extensive clean-up called ‘Operation Buckshot Yankee’, and led to the creation of the US Cyber Command. +Whilst the sophisticated rootkit is used for persistent access to networks, the group also leverage more straight-forward capabilities for gaining an initial toe-hold on targets. This includes the use of watering-hole attacks and basic remote access tools. + + +The group behind the SNAKE campaign are a top tier nation-state threat. Their capabilities extend from subtle watering-hole attacks to sophisticated server rootkits – virtually undetectable by conventional security products. + + + + + + SNAKE + + + Turla + + + WRAITH + + + + + + Russia + + + Moscow + + + + + snake@gmail.com + twitter.com/snake + + + Russian + + + + + Political + + + Expert + + + Advantage - Political + + + Theft - Intellectual Property + + + + diff --git a/tests/test.py b/tests/test.py index 4732358..c2f2fb0 100755 --- a/tests/test.py +++ b/tests/test.py @@ -3,24 +3,37 @@ import unittest import requests - +import base64 +import json class TestModules(unittest.TestCase): def setUp(self): self.maxDiff = None self.headers = {'Content-Type': 'application/json'} + self.url = "http://127.0.0.1:6666/" def test_introspection(self): - response = requests.get('http://127.0.0.1:6666/modules') + response = requests.get(self.url + "modules") print(response.json()) def test_cve(self): with open('tests/bodycve.json', 'r') as f: - response = requests.post('http://127.0.0.1:6666/query', data=f.read()) + response = requests.post(self.url + "query", data=f.read()) print(response.json()) def test_dns(self): with open('tests/body.json', 'r') as f: - response = requests.post('http://127.0.0.1:6666/query', data=f.read()) + response = requests.post(self.url + "query", data=f.read()) print(response.json()) + + def test_stix(self): + with open("tests/stix.xml", "r") as f: + data = json.dumps({"module":"stiximport", + "data":str(base64.b64encode(bytes(f.read(), 'utf-8')), 'utf-8') + }) + response = requests.post(self.url + "query", data=data) + print(response.json()) + +if __name__ == '__main__': + unittest.main() From 4a4c4ab9a7c9d15e978355f3d2e8a49913d13fca Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 12:26:52 +0100 Subject: [PATCH 10/14] Added STIX to reqs --- REQUIREMENTS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/REQUIREMENTS b/REQUIREMENTS index 7656078..063978b 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -1,3 +1,5 @@ +stix +cybox tornado dnspython3 requests From 38205910df6b9ecad54176483751be483d6ca8aa Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 12:33:08 +0100 Subject: [PATCH 11/14] Added STIX to setup.py --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 1628cba..241f689 100644 --- a/setup.py +++ b/setup.py @@ -33,5 +33,7 @@ setup( 'pyeupi', 'ipasn-redis', 'asnhistory', + 'stix', + 'cybox' ] ) From af8fc70883b1d663def22dc10b65648d14958fcc Mon Sep 17 00:00:00 2001 From: Hannah Ward Date: Fri, 12 Aug 2016 12:37:16 +0100 Subject: [PATCH 12/14] There was a missing comma --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec30f92..d48e13e 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ setup( 'ipasn-redis', 'asnhistory', 'stix', - 'cybox' + 'cybox', 'pillow', 'pytesseract', ] From 91675a635c73264eb1e1ad81ebd292c2dbf440b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 12 Aug 2016 14:08:47 +0200 Subject: [PATCH 13/14] Move stiximport.py to misp_modules/modules/import_mod/ --- misp_modules/modules/import_mod/__init__.py | 2 +- misp_modules/modules/{expansion => import_mod}/stiximport.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename misp_modules/modules/{expansion => import_mod}/stiximport.py (100%) diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index 5716751..b53259e 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -1 +1 @@ -__all__ = ['testimport', 'ocr'] +__all__ = ['testimport', 'ocr', 'stiximport'] diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/import_mod/stiximport.py similarity index 100% rename from misp_modules/modules/expansion/stiximport.py rename to misp_modules/modules/import_mod/stiximport.py From c6fccf1b7e2808e6f6c39155473f004c737eb0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 12 Aug 2016 14:09:59 +0200 Subject: [PATCH 14/14] Make PEP8 happy \o/ --- misp_modules/modules/import_mod/stiximport.py | 249 +++++++++--------- 1 file changed, 125 insertions(+), 124 deletions(-) diff --git a/misp_modules/modules/import_mod/stiximport.py b/misp_modules/modules/import_mod/stiximport.py index 5ea0010..a701ba5 100755 --- a/misp_modules/modules/import_mod/stiximport.py +++ b/misp_modules/modules/import_mod/stiximport.py @@ -1,6 +1,4 @@ import json -import stix -import csv from stix.core import STIXPackage import re import base64 @@ -18,180 +16,183 @@ moduleconfig = [] def handler(q=False): - #Just in case we have no data + # Just in case we have no data if q is False: return False - #The return value + # The return value r = {'results': []} - #Load up that JSON + # Load up that JSON q = json.loads(q) - #It's b64 encoded, so decode that stuff + # It's b64 encoded, so decode that stuff package = str(base64.b64decode(q.get("data", None)), 'utf-8') - - #If something really weird happened - if not package: - return json.dumps({"success":0}) - #Load up the package into STIX + # If something really weird happened + if not package: + return json.dumps({"success": 0}) + + # Load up the package into STIX package = loadPackage(package) - #Build all the observables + # Build all the observables if package.observables: - for obs in package.observables: - r["results"].append(buildObservable(obs)) + for obs in package.observables: + r["results"].append(buildObservable(obs)) if package.threat_actors: - for ta in package.threat_actors: - r["results"].append(buildActor(ta)) - + for ta in package.threat_actors: + r["results"].append(buildActor(ta)) + if package.indicators: - for ind in package.indicators: - r["results"].append(buildIndicator(ind)) + for ind in package.indicators: + r["results"].append(buildIndicator(ind)) if package.exploit_targets: - for et in package.exploit_targets: - r["results"].append(buildExploitTarget(et)) + for et in package.exploit_targets: + r["results"].append(buildExploitTarget(et)) if package.campaigns: - for cpn in package.campaigns: - r["results"].append(buildCampaign(cpn)) - #Clean up results - #Don't send on anything that didn't have a value + for cpn in package.campaigns: + r["results"].append(buildCampaign(cpn)) + # Clean up results + # Don't send on anything that didn't have a value r["results"] = [x for x in r["results"] if len(x["values"]) != 0] return r -#Quick and dirty regex for IP addresses +# Quick and dirty regex for IP addresses ipre = re.compile("([0-9]{1,3}.){3}[0-9]{1,3}") + def buildCampaign(cpn): - """ - Extract a campaign name - """ - - return {"values":[cpn.title], "types":["campaign-name"]} + """ + Extract a campaign name + """ + return {"values": [cpn.title], "types": ["campaign-name"]} + def buildExploitTarget(et): - """ - Extract CVEs from exploit targets - """ + """ + Extract CVEs from exploit targets + """ - r = {"values":[], "types":["vulnerability"]} + r = {"values": [], "types": ["vulnerability"]} - if et.vulnerabilities: - for v in et.vulnerabilities: - if v.cve_id: - r["values"].append(v.cve_id) + if et.vulnerabilities: + for v in et.vulnerabilities: + if v.cve_id: + r["values"].append(v.cve_id) + return r - return r def identifyHash(hsh): - """ - What's that hash!? - """ + """ + What's that hash!? + """ - possible_hashes = [] + possible_hashes = [] - hashes = [x for x in hashlib.algorithms_guaranteed] + hashes = [x for x in hashlib.algorithms_guaranteed] + + for h in hashes: + if len(str(hsh)) == len(hashlib.new(h).hexdigest()): + possible_hashes.append(h) + possible_hashes.append("filename|{}".format(h)) + return possible_hashes - for h in hashes: - if len(str(hsh)) == len(hashlib.new(h).hexdigest()): - possible_hashes.append(h) - possible_hashes.append("filename|{}".format(h)) - - return possible_hashes def buildIndicator(ind): - """ - Extract hashes - and other fun things - like that - """ - r = {"values":[], "types":[]} + """ + Extract hashes + and other fun things + like that + """ + r = {"values": [], "types": []} + + # Try to get hashes. I hate stix + if ind.observable: + return buildObservable(ind.observable) + return r + - #Try to get hashes. I hate stix - if ind.observable: - return buildObservable(ind.observable) - return r - def buildActor(ta): - """ - Extract the name - and comment of a - threat actor - """ - - r = {"values":[ta.title], "types":["threat-actor"]} - - return r + """ + Extract the name + and comment of a + threat actor + """ + + r = {"values": [ta.title], "types": ["threat-actor"]} + + return r + def buildObservable(o): - """ - Take a STIX observable - and extract the value - and category - """ + """ + Take a STIX observable + and extract the value + and category + """ - #Life is easier with json - if not isinstance(o, dict): - o = json.loads(o.to_json()) - #Make a new record to store values in - r = {"values":[]} + # Life is easier with json + if not isinstance(o, dict): + o = json.loads(o.to_json()) + # Make a new record to store values in + r = {"values": []} - #Get the object properties. This contains all the - #fun stuff like values - if "observable_composition" in o: - #May as well be useless - return r - - props = o["object"]["properties"] + # Get the object properties. This contains all the + # fun stuff like values + if "observable_composition" in o: + # May as well be useless + return r - #If it has an address_value field, it's gonna be an address - print(props) - #Kinda obvious really - if "address_value" in props: - - #We've got ourselves a nice little address - value = props["address_value"] + props = o["object"]["properties"] - if isinstance(value, dict): - #Sometimes it's embedded in a dictionary - value = value["value"] + # If it has an address_value field, it's gonna be an address + # print(props) + # Kinda obvious really + if "address_value" in props: - #Is it an IP? - if ipre.match(str(value)): + # We've got ourselves a nice little address + value = props["address_value"] - #Yes! - r["values"].append(value) - r["types"] = ["ip-src", "ip-dst"] - else: + if isinstance(value, dict): + # Sometimes it's embedded in a dictionary + value = value["value"] - #Probably a domain yo - r["values"].append(value) - r["types"] = ["domain", "hostname"] + # Is it an IP? + if ipre.match(str(value)): + # Yes! + r["values"].append(value) + r["types"] = ["ip-src", "ip-dst"] + else: + # Probably a domain yo + r["values"].append(value) + r["types"] = ["domain", "hostname"] + + if "hashes" in props: + for hsh in props["hashes"]: + r["values"].append(hsh["simple_hash_value"]["value"]) + r["types"] = identifyHash(hsh["simple_hash_value"]["value"]) + return r - if "hashes" in props: - for hsh in props["hashes"]: - r["values"].append(hsh["simple_hash_value"]["value"]) - r["types"] = identifyHash(hsh["simple_hash_value"]["value"]) - return r def loadPackage(data): - #Write the stix package to a tmp file - with open("/tmp/stixdump", "w") as f: - f.write(data) - try: - #Try loading it into every format we know of + # Write the stix package to a tmp file + with open("/tmp/stixdump", "w") as f: + f.write(data) try: - package = STIXPackage().from_xml(open("/tmp/stixdump", "r")) - except: - package = STIXPackage().from_json(open("/tmp/stixdump", "r")) - except Exception as ex: - print("Failed to load package") - raise ValueError("COULD NOT LOAD STIX PACKAGE!") - return package + # Try loading it into every format we know of + try: + package = STIXPackage().from_xml(open("/tmp/stixdump", "r")) + except: + package = STIXPackage().from_json(open("/tmp/stixdump", "r")) + except Exception: + print("Failed to load package") + raise ValueError("COULD NOT LOAD STIX PACKAGE!") + return package + def introspection(): modulesetup = {}