diff --git a/README.md b/README.md index 653695b..1f6b350 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ For more information: [Extending MISP with Python modules](https://www.misp-proj * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate(s) seen. * [countrycode](misp_modules/modules/expansion/countrycode.py) - a hover module to tell you what country a URL belongs to. +* [CrowdSec](misp_modules/modules/expansion/crowdsec.py) - a hover module to expand using CrowdSec's CTI API. * [CrowdStrike Falcon](misp_modules/modules/expansion/crowdstrike_falcon.py) - an expansion module to expand using CrowdStrike Falcon Intel Indicator API. * [CPE](misp_modules/modules/expansion/cpe.py) - An expansion module to query the CVE Search API with a cpe code, to get its related vulnerabilities. * [CVE](misp_modules/modules/expansion/cve.py) - a hover module to give more information about a vulnerability (CVE). diff --git a/REQUIREMENTS b/REQUIREMENTS index fcdb0a5..0904a16 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -106,6 +106,7 @@ git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12 pyeupi==1.1 pyfaup==1.2 pygeoip==0.3.2 +pycountry==22.3.5 pygments==2.13.0 ; python_version >= '3.6' git+https://github.com/MISP/PyIntel471.git@917272fafa8e12102329faca52173e90c5256968#egg=pyintel471 git+https://github.com/D4-project/IPASN-History.git/@a2853c39265cecdd0c0d16850bd34621c0551b87#egg=pyipasnhistory&subdirectory=client diff --git a/misp_modules/modules/expansion/crowdsec.py b/misp_modules/modules/expansion/crowdsec.py new file mode 100644 index 0000000..597fc1f --- /dev/null +++ b/misp_modules/modules/expansion/crowdsec.py @@ -0,0 +1,155 @@ +import json + +from pymisp import MISPEvent, MISPObject +import pycountry +import requests + +mispattributes = {"input": ["ip-dst", "ip-src"], "output": ["text"]} +moduleinfo = { + "version": "1.0", + "author": "Shivam Sandbhor ", + "description": "Module to access CrowdSec CTI API.", + "module-type": ["hover"], +} +moduleconfig = ["api_key", "api_version"] + + +def handler(q=False): + if q is False: + return False + + request = json.loads(q) + if not request.get("config"): + return {"error": "Missing CrowdSec Config"} + + if not request["config"].get("api_key"): + return {"error": "Missing CrowdSec API key"} + + if not request["config"].get("api_version"): + return {"error": "Missing CrowdSec API version parameter"} + + if request["config"]["api_version"] == "v2": + return _handler_v2(request) + return {"error": f'API version {request["config"]["api_version"]} not supported'} + + +def _handler_v2(request_data): + if request_data.get("ip-dst"): + ip = request_data.get("ip-dst") + elif request_data.get("ip-src"): + ip = request_data.get("ip-src") + + crowdsec_cti = requests.get( + f"https://cti.api.crowdsec.net/v2/smoke/{ip}", + headers={"x-api-key": request_data["config"]["api_key"]}, + ) + crowdsec_cti.raise_for_status() + crowdsec_cti = crowdsec_cti.json() + + misp_event = MISPEvent() + crowdsec_context_object = MISPObject("crowdsec-ip-context") + crowdsec_context_object.add_attribute("IP Address", **{"type": "text", "value": ip}) + crowdsec_context_object.add_attribute( + "IP Range", **{"type": "text", "value": crowdsec_cti["ip_range"]} + ) + crowdsec_context_object.add_attribute( + "IP Range Score", **{"type": "text", "value": crowdsec_cti["ip_range_score"]} + ) + crowdsec_context_object.add_attribute( + "Country", + **{ + "type": "text", + "value": get_country_name_from_alpha_2(crowdsec_cti["location"]["country"]), + }, + ) + if crowdsec_cti["location"]["city"]: + crowdsec_context_object.add_attribute( + "City", **{"type": "text", "value": crowdsec_cti["location"]["city"]} + ) + + crowdsec_context_object.add_attribute( + "Latitude", **{"type": "float", "value": crowdsec_cti["location"]["latitude"]} + ) + crowdsec_context_object.add_attribute( + "Longitude", **{"type": "float", "value": crowdsec_cti["location"]["longitude"]} + ) + + crowdsec_context_object.add_attribute( + "AS Name", **{"type": "text", "value": crowdsec_cti["as_name"]} + ) + + crowdsec_context_object.add_attribute( + "AS Number", **{"type": "AS", "value": crowdsec_cti["as_num"]} + ) + + crowdsec_context_object.add_attribute( + "Reverse DNS", **{"type": "domain", "value": crowdsec_cti["reverse_dns"]} + ) + + crowdsec_context_object.add_attribute( + "Attack Categories", + **{ + "type": "text", + "value": ",".join( + [attack_category["label"] for attack_category in crowdsec_cti["behaviors"]] + ), + }, + ) + + crowdsec_context_object.add_attribute( + "Triggered Scenarios", + **{ + "type": "text", + "value": ",".join([scenario["name"] for scenario in crowdsec_cti["attack_details"]]), + }, + ) + + crowdsec_context_object.add_attribute( + "Top 10 Target Countries", + **{ + "type": "float", + "value": ",".join( + map(get_country_name_from_alpha_2, crowdsec_cti["target_countries"].keys()) + ), + }, + ) + + crowdsec_context_object.add_attribute( + "Trust", **{"type": "float", "value": crowdsec_cti["scores"]["overall"]["trust"]} + ) + + crowdsec_context_object.add_attribute( + "First Seen", **{"type": "datetime", "value": crowdsec_cti["history"]["first_seen"]} + ) + + crowdsec_context_object.add_attribute( + "Last Seen", **{"type": "datetime", "value": crowdsec_cti["history"]["last_seen"]} + ) + + for time_period, indicators in crowdsec_cti["scores"].items(): + tp = " ".join(map(str.capitalize, time_period.split("_"))) + + for indicator_type, indicator_value in indicators.items(): + crowdsec_context_object.add_attribute( + f"{tp} {indicator_type.capitalize()}", **{"type": "float", "value": indicator_value} + ) + + misp_event.add_object(crowdsec_context_object) + + event = json.loads(misp_event.to_json()) + results = {key: event[key] for key in ("Attribute", "Object") if (key in event and event[key])} + return {"results": results} + + +def get_country_name_from_alpha_2(alpha_2): + country_info = pycountry.countries.get(alpha_2=alpha_2) + return country_info.name + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo["config"] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index a7d220d..6e99826 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -15,5 +15,6 @@ __all__ = [ 'csvimport', 'cof2misp', 'joe_import', - 'taxii21' + 'taxii21', + 'crowdsec' ]