diff --git a/README.md b/README.md index 3d4ad2a..15511b8 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/modules/expansion/stiximport.py) - An import module to process STIX xml/json ## How to install and start MISP modules? ~~~~bash diff --git a/misp_modules/modules/expansion/stiximport.py b/misp_modules/modules/expansion/stiximport.py new file mode 100755 index 0000000..5ea0010 --- /dev/null +++ b/misp_modules/modules/expansion/stiximport.py @@ -0,0 +1,213 @@ +import json +import stix +import csv +from stix.core import STIXPackage +import re +import base64 +import hashlib + +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): + #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) + + #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)) + + 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)) + + 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!? + """ + + possible_hashes = [] + + 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 + +def buildIndicator(ind): + """ + 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 + +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 + 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":[]} + + #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 + print(props) + #Kinda obvious really + 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(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 + +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 + 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 + +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 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()