Merge branch 'main' of github.com:MISP/misp-modules

pull/613/head
Christian Studer 2023-03-27 17:48:30 +02:00
commit 78b06ea51b
8 changed files with 331 additions and 225 deletions

View File

@ -44,7 +44,7 @@ For more information: [Extending MISP with Python modules](https://www.misp-proj
* [GeoIP](misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind. * [GeoIP](misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind.
* [GeoIP_City](misp_modules/modules/expansion/geoip_city.py) - a hover and expansion module to get GeoIP City information from geolite/maxmind. * [GeoIP_City](misp_modules/modules/expansion/geoip_city.py) - a hover and expansion module to get GeoIP City information from geolite/maxmind.
* [GeoIP_ASN](misp_modules/modules/expansion/geoip_asn.py) - a hover and expansion module to get GeoIP ASN information from geolite/maxmind. * [GeoIP_ASN](misp_modules/modules/expansion/geoip_asn.py) - a hover and expansion module to get GeoIP ASN information from geolite/maxmind.
* [Greynoise](misp_modules/modules/expansion/greynoise.py) - a hover to get information from greynoise. * [GreyNoise](misp_modules/modules/expansion/greynoise.py) - a hover and expansion module to get IP and CVE information from GreyNoise.
* [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset. * [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset.
* [hibp](misp_modules/modules/expansion/hibp.py) - a hover module to lookup against Have I Been Pwned? * [hibp](misp_modules/modules/expansion/hibp.py) - a hover module to lookup against Have I Been Pwned?
* [html_to_markdown](misp_modules/modules/expansion/html_to_markdown.py) - Simple HTML to markdown converter * [html_to_markdown](misp_modules/modules/expansion/html_to_markdown.py) - Simple HTML to markdown converter

View File

@ -47,6 +47,7 @@ filelock==3.8.0 ; python_version >= '3.7'
frozenlist==1.3.1 ; python_version >= '3.7' frozenlist==1.3.1 ; python_version >= '3.7'
future==0.18.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' future==0.18.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
geoip2==4.6.0 geoip2==4.6.0
greynoise==2.0.0
h11==0.12.0 ; python_version >= '3.6' h11==0.12.0 ; python_version >= '3.6'
httpcore==0.15.0 ; python_version >= '3.7' httpcore==0.15.0 ; python_version >= '3.7'
httplib2==0.20.4 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' httplib2==0.20.4 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,15 @@
{
"description": "Hover module to lookup an IP in CrowdSec's CTI",
"logo": "crowdsec.png",
"requirements": [
"A CrowdSec CTI API key. Get yours by following https://docs.crowdsec.net/docs/cti_api/getting_started/#getting-an-api-key"
],
"input": "An IP address.",
"output": "IP Lookup information from CrowdSec CTI API",
"references": [
"https://www.crowdsec.net/",
"https://docs.crowdsec.net/docs/cti_api/getting_started",
"https://app.crowdsec.net/"
],
"features": "This module enables IP lookup from CrowdSec CTI API. It provides information about the IP, such as what kind of attacks it has been participant of as seen by CrowdSec's network. It also includes enrichment by CrowdSec like background noise score, aggressivity over time etc."
}

View File

@ -1,254 +1,333 @@
import ipaddress
import json import json
import logging
import requests
from pymisp import MISPEvent, MISPObject try:
from greynoise import GreyNoise
except ImportError:
print("greynoise module not installed.")
from pymisp import MISPAttribute, MISPEvent, MISPObject
from . import check_input_attribute, standard_error_message
logger = logging.getLogger("greynoise")
logger.setLevel(logging.INFO)
misperrors = {"error": "Error"} misperrors = {"error": "Error"}
mispattributes = {"input": ["ip-dst", "ip-src", "vulnerability"], "output": ["text"]} mispattributes = {"input": ["ip-src", "ip-dst", "vulnerability"], "format": "misp_standard"}
moduleinfo = { moduleinfo = {
"version": "1.1", "version": "1.2",
"author": "Brad Chiappetta <brad@greynoise.io>", "author": "Brad Chiappetta <brad@greynoise.io>",
"description": "Module to access GreyNoise.io API.", "description": "Used to query IP and CVE intel from GreyNoise",
"module-type": ["hover"], "module-type": ["expansion", "hover"],
} }
moduleconfig = ["api_key", "api_type"] moduleconfig = ["api_key", "api_type"]
codes_mapping = {
"0x00": "The IP has never been observed scanning the Internet",
"0x01": "The IP has been observed by the GreyNoise sensor network",
"0x02": "The IP has been observed scanning the GreyNoise sensor network, "
"but has not completed a full connection, meaning this can be spoofed",
"0x03": "The IP is adjacent to another host that has been directly observed by the GreyNoise sensor network",
"0x04": "Reserved",
"0x05": "This IP is commonly spoofed in Internet-scan activity",
"0x06": "This IP has been observed as noise, but this host belongs to a cloud provider where IPs can be "
"cycled frequently",
"0x07": "This IP is invalid",
"0x08": "This IP was classified as noise, but has not been observed engaging in Internet-wide scans or "
"attacks in over 90 days",
"0x09": "IP was found in RIOT",
"0x10": "IP has been observed by the GreyNoise sensor network and is in RIOT",
}
vulnerability_mapping = {
"id": ("vulnerability", "CVE #"),
"details": ("text", "Details"),
"count": ("text", "Total Scanner Count"),
}
enterprise_context_basic_mapping = {"ip": ("text", "IP Address"), "code_message": ("text", "Code Message")}
enterprise_context_advanced_mapping = {
"noise": ("text", "Is Internet Background Noise"),
"link": ("link", "Visualizer Link"),
"classification": ("text", "Classification"),
"actor": ("text", "Actor"),
"tags": ("text", "Tags"),
"cve": ("text", "CVEs"),
"first_seen": ("text", "First Seen Scanning"),
"last_seen": ("text", "Last Seen Scanning"),
"vpn": ("text", "Known VPN Service"),
"vpn_service": ("text", "VPN Service Name"),
"bot": ("text", "Known BOT"),
}
enterprise_context_advanced_metadata_mapping = {
"asn": ("text", "ASN"),
"rdns": ("text", "rDNS"),
"category": ("text", "Category"),
"tor": ("text", "Known Tor Exit Node"),
"region": ("text", "Region"),
"city": ("text", "City"),
"country": ("text", "Country"),
"country_code": ("text", "Country Code"),
"organization": ("text", "Organization"),
}
enterprise_riot_mapping = {
"riot": ("text", "Is Common Business Service"),
"link": ("link", "Visualizer Link"),
"category": ("text", "RIOT Category"),
"name": ("text", "Provider Name"),
"trust_level": ("text", "RIOT Trust Level"),
"last_updated": ("text", "Last Updated"),
}
community_found_mapping = {
"ip": ("text", "IP Address"),
"noise": ("text", "Is Internet Background Noise"),
"riot": ("text", "Is Common Business Service"),
"classification": ("text", "Classification"),
"last_seen": ("text", "Last Seen"),
"name": ("text", "Name"),
"link": ("link", "Visualizer Link"),
}
community_not_found_mapping = {
"ip": ("text", "IP Address"),
"noise": ("text", "Is Internet Background Noise"),
"riot": ("text", "Is Common Business Service"),
"message": ("text", "Message"),
}
misp_event = MISPEvent()
def handler(q=False): # noqa: C901 class GreyNoiseParser:
if q is False: def __init__(self, attribute):
return False self.misp_event = MISPEvent()
request = json.loads(q) self.attribute = MISPAttribute()
if not request.get("config") or not request["config"].get("api_key"): self.attribute.from_dict(**attribute)
return {"error": "Missing Greynoise API key."} self.misp_event.add_attribute(**self.attribute)
self.ip_address_enrich_mapping = {
"noise": {"type": "boolean", "object_relation": "noise"},
"riot": {"type": "boolean", "object_relation": "riot"},
"classification": {"type": "text", "object_relation": "classification"},
"actor": {"type": "text", "object_relation": "actor"},
"trust_level": {"type": "text", "object_relation": "trust-level"},
"name": {"type": "text", "object_relation": "provider"},
"first_seen": {"type": "datetime", "object_relation": "first-seen"},
"last_seen": {"type": "datetime", "object_relation": "last-seen"},
"link": {"type": "url", "object_relation": "link"},
"last_updated": {"type": "datetime", "object_relation": "last-seen"},
}
self.ip_address_hover_mapping = {
"noise": {"type": "boolean", "object_relation": "noise"},
"riot": {"type": "boolean", "object_relation": "riot"},
"classification": {"type": "text", "object_relation": "classification"},
"actor": {"type": "text", "object_relation": "actor"},
"tags": {"type": "text", "object_relation": "tags"},
"cve": {"type": "text", "object_relation": "cve"},
"vpn": {"type": "text", "object_relation": "vpn"},
"vpn_service": {"type": "text", "object_relation": "vpn_service"},
"bot": {"type": "text", "object_relation": "bot"},
"first_seen": {"type": "datetime", "object_relation": "first-seen"},
"last_seen": {"type": "datetime", "object_relation": "last-seen"},
"spoofable": {"type": "datetime", "object_relation": "spoofable"},
"link": {"type": "url", "object_relation": "link"},
"category": {"type": "text", "object_relation": "category"},
"name": {"type": "text", "object_relation": "provider"},
"trust_level": {"type": "text", "object_relation": "trust-level"},
"last_updated": {"type": "datetime", "object_relation": "last_updated"},
}
self.ip_address_metadata_mapping = {
"tor": {"type": "text", "object_relation": "tor"},
"asn": {"type": "AS", "object_relation": "asn"},
"city": {"type": "text", "object_relation": "city"},
"country_code": {"type": "text", "object_relation": "country-code"},
"country": {"type": "text", "object_relation": "country"},
"organization": {"type": "text", "object_relation": "organization"},
"destination_country_codes": {"type": "text", "object_relation": "destination-country-codes"},
"destination_countries": {"type": "text", "object_relation": "destination-countries"},
"category": {"type": "text", "object_relation": "category"},
"rdns": {"type": "text", "object_relation": "rdns"},
}
self.vulnerability_mapping = {
"id": {"type": "text", "object_relation": "id"},
"details": {"type": "text", "object_relation": "details"},
"count": {"type": "text", "object_relation": "total-count"},
"benign": {"type": "text", "object_relation": "benign-count"},
"malicious": {"type": "text", "object_relation": "malicious-count"},
"unknown": {"type": "text", "object_relation": "unknown-count"},
}
headers = { def query_greynoise_ip_hover(self, api_key, api_type):
"Accept": "application/json", if api_type == "enterprise":
"key": request["config"]["api_key"], logger.info(f"Starting hover enrichment for: {self.attribute.value} via GreyNoise ENT API")
"User-Agent": "greynoise-misp-module-{}".format(moduleinfo["version"]), integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
} session = GreyNoise(api_key=api_key, integration_name=integration_name)
quick_response = session.quick(self.attribute.value)
if not (request.get("vulnerability") or request.get("ip-dst") or request.get("ip-src")): if len(quick_response) != 1:
misperrors["error"] = "Vulnerability id missing" misperrors["error"] = "Quick IP lookup returned unexpected response"
return misperrors return misperrors
ip = ""
vulnerability = ""
if request.get("ip-dst"):
ip = request.get("ip-dst")
elif request.get("ip-src"):
ip = request.get("ip-src")
else:
vulnerability = request.get("vulnerability")
if ip:
if request["config"]["api_type"] and request["config"]["api_type"] == "enterprise":
greynoise_api_url = "https://api.greynoise.io/v2/noise/quick/"
else:
greynoise_api_url = "https://api.greynoise.io/v3/community/"
response = requests.get(f"{greynoise_api_url}{ip}", headers=headers) # Real request for IP Query
if response.status_code == 200:
if request["config"]["api_type"] == "enterprise":
response = response.json()
enterprise_context_object = MISPObject("greynoise-ip-context")
for feature in ("ip", "code_message"):
if feature == "code_message":
value = codes_mapping[response.get("code")]
else:
value = response.get(feature)
if value:
attribute_type, relation = enterprise_context_basic_mapping[feature]
enterprise_context_object.add_attribute(relation, **{"type": attribute_type, "value": value})
if response["noise"]:
greynoise_api_url = "https://api.greynoise.io/v2/noise/context/"
context_response = requests.get(f"{greynoise_api_url}{ip}", headers=headers)
context_response = context_response.json()
context_response["link"] = "https://www.greynoise.io/viz/ip/" + ip
if "tags" in context_response:
context_response["tags"] = ",".join(context_response["tags"])
if "cve" in context_response:
context_response["cve"] = ",".join(context_response["cve"])
for feature in enterprise_context_advanced_mapping.keys():
value = context_response.get(feature)
if value:
attribute_type, relation = enterprise_context_advanced_mapping[feature]
enterprise_context_object.add_attribute(
relation, **{"type": attribute_type, "value": value}
)
for feature in enterprise_context_advanced_metadata_mapping.keys():
value = context_response["metadata"].get(feature)
if value:
attribute_type, relation = enterprise_context_advanced_metadata_mapping[feature]
enterprise_context_object.add_attribute(
relation, **{"type": attribute_type, "value": value}
)
if response["riot"]:
greynoise_api_url = "https://api.greynoise.io/v2/riot/"
riot_response = requests.get(f"{greynoise_api_url}{ip}", headers=headers)
riot_response = riot_response.json()
riot_response["link"] = "https://www.greynoise.io/viz/riot/" + ip
for feature in enterprise_riot_mapping.keys():
value = riot_response.get(feature)
if value:
attribute_type, relation = enterprise_riot_mapping[feature]
enterprise_context_object.add_attribute(
relation, **{"type": attribute_type, "value": value}
)
misp_event.add_object(enterprise_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}
else: else:
response = response.json() quick_response = quick_response[0]
community_context_object = MISPObject("greynoise-community-ip-context") context_response = session.ip(self.attribute.value)
for feature in community_found_mapping.keys(): riot_response = session.riot(self.attribute.value)
value = response.get(feature)
if value:
attribute_type, relation = community_found_mapping[feature]
community_context_object.add_attribute(relation, **{"type": attribute_type, "value": value})
misp_event.add_object(community_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}
if response.status_code == 404 and request["config"]["api_type"] != "enterprise":
response = response.json()
community_context_object = MISPObject("greynoise-community-ip-context")
for feature in community_not_found_mapping.keys():
value = response.get(feature)
if value:
attribute_type, relation = community_not_found_mapping[feature]
community_context_object.add_attribute(relation, **{"type": attribute_type, "value": value})
misp_event.add_object(community_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}
if vulnerability: if riot_response and "trust_level" in riot_response:
if request["config"]["api_type"] and request["config"]["api_type"] == "enterprise": if riot_response["trust_level"] == "1":
greynoise_api_url = "https://api.greynoise.io/v2/experimental/gnql/stats" riot_response["trust_level"] = "1 - Reasonably Ignore"
querystring = {"query": f"last_seen:1w cve:{vulnerability}"} if riot_response["trust_level"] == "2":
riot_response["trust_level"] = "2 - Commonly Seen"
if context_response and riot_response:
response = context_response.copy()
response.update(riot_response)
response.update(quick_response)
elif context_response:
response = context_response.copy()
response.update(quick_response)
elif riot_response:
response = riot_response.copy()
response.update(quick_response)
response["link"] = "https://viz.greynoise.io/ip/" + self.attribute.value
ip_address_attributes = []
for feature, mapping in self.ip_address_hover_mapping.items():
logger.debug(f"Checking feature {feature}")
if response.get(feature):
if feature in ["cve", "tags"]:
response[feature] = ", ".join(response[feature])
if feature == "vpn_service" and response[feature] == "N/A":
continue
if feature == "actor" and response[feature] == "unknown":
continue
attribute = {"value": response[feature]}
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
attribute.update(mapping)
ip_address_attributes.append(attribute)
if "metadata" in context_response:
for feature, mapping in self.ip_address_metadata_mapping.items():
logger.debug(f"Checking metadata feature {feature}")
if response["metadata"].get(feature):
if feature in ["destination_countries", "destination_country_codes"]:
response["metadata"][feature] = ", ".join(response["metadata"][feature])
attribute = {"value": response["metadata"][feature]}
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
attribute.update(mapping)
ip_address_attributes.append(attribute)
if ip_address_attributes:
logger.debug("creating greynoise ip object")
gn_ip_object = MISPObject("greynoise-ip-details")
for attribute in ip_address_attributes:
logger.debug(f"adding attribute {attribute}")
gn_ip_object.add_attribute(**attribute)
logger.debug(f"attribute id: {self.attribute.uuid}")
gn_ip_object.add_reference(self.attribute.uuid, "describes")
self.misp_event.add_object(gn_ip_object)
else:
logger.info(f"Starting hover enrichment for: {self.attribute.value} via GreyNoise Community API")
integration_name = "greynoise-community-misp-module-{}".format(moduleinfo["version"])
session = GreyNoise(api_key=api_key, integration_name=integration_name, offering="community")
community_response = session.ip(self.attribute.value)
if "noise" in community_response and community_response["noise"]:
community_response["actor"] = community_response["name"]
community_response.pop("name")
ip_address_attributes = []
for feature, mapping in self.ip_address_hover_mapping.items():
if community_response.get(feature):
if feature == "actor" and community_response[feature] == "unknown":
continue
attribute = {"value": community_response[feature]}
attribute.update(mapping)
ip_address_attributes.append(attribute)
if ip_address_attributes:
ip_address_object = MISPObject("greynoise-ip-details")
for attribute in ip_address_attributes:
ip_address_object.add_attribute(**attribute)
ip_address_object.add_reference(self.attribute.uuid, "describes")
self.misp_event.add_object(ip_address_object)
def query_greynoise_ip_expansion(self, api_key, api_type):
if api_type == "enterprise":
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise ENT API")
integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
session = GreyNoise(api_key=api_key, integration_name=integration_name)
quick_response = session.quick(self.attribute.value)
if len(quick_response) != 1:
misperrors["error"] = "Quick IP lookup returned unexpected response"
return misperrors
else:
quick_response = quick_response[0]
context_response = session.ip(self.attribute.value)
riot_response = session.riot(self.attribute.value)
if riot_response and "trust_level" in riot_response:
if riot_response["trust_level"] == "1":
riot_response["trust_level"] = "1 - Reasonably Ignore"
if riot_response["trust_level"] == "2":
riot_response["trust_level"] = "2 - Commonly Seen"
if context_response and riot_response:
response = context_response.copy()
response.update(riot_response)
response.update(quick_response)
elif context_response:
response = context_response.copy()
response.update(quick_response)
elif riot_response:
response = riot_response.copy()
response.update(quick_response)
response["link"] = "https://viz.greynoise.io/ip/" + self.attribute.value
ip_address_attributes = []
for feature, mapping in self.ip_address_enrich_mapping.items():
logger.debug(f"Checking feature {feature}")
if response.get(feature):
if feature == "actor" and response[feature] == "unknown":
continue
attribute = {"value": response[feature]}
logger.debug(f"Adding Feature: {feature}, Attribute: {attribute}")
attribute.update(mapping)
ip_address_attributes.append(attribute)
if ip_address_attributes:
logger.debug("creating greynoise ip object")
gn_ip_object = MISPObject("greynoise-ip")
for attribute in ip_address_attributes:
logger.debug(f"adding attribute {attribute}")
gn_ip_object.add_attribute(**attribute)
logger.debug(f"attribute id: {self.attribute.uuid}")
gn_ip_object.add_reference(self.attribute.uuid, "describes")
self.misp_event.add_object(gn_ip_object)
else:
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise Community API")
integration_name = "greynoise-community-misp-module-{}".format(moduleinfo["version"])
session = GreyNoise(api_key=api_key, integration_name=integration_name, offering="community")
community_response = session.ip(self.attribute.value)
if "noise" in community_response and community_response["noise"]:
community_response["actor"] = community_response["name"]
community_response.pop("name")
ip_address_attributes = []
for feature, mapping in self.ip_address_enrich_mapping.items():
if community_response.get(feature):
if feature == "actor" and community_response[feature] == "unknown":
continue
attribute = {"value": community_response[feature]}
attribute.update(mapping)
ip_address_attributes.append(attribute)
if ip_address_attributes:
ip_address_object = MISPObject("greynoise-ip")
for attribute in ip_address_attributes:
ip_address_object.add_attribute(**attribute)
ip_address_object.add_reference(self.attribute.uuid, "describes")
self.misp_event.add_object(ip_address_object)
def query_greynoise_vulnerability(self, api_key, api_type):
if api_type == "enterprise":
logger.info(f"Starting expansion enrichment for: {self.attribute.value} via GreyNoise ENT API")
integration_name = "greynoise-misp-module-{}".format(moduleinfo["version"])
session = GreyNoise(api_key=api_key, integration_name=integration_name)
querystring = f"last_seen:1w cve:{self.attribute.value}"
else: else:
misperrors["error"] = "Vulnerability Not Supported with Community API Key" misperrors["error"] = "Vulnerability Not Supported with Community API Key"
return misperrors return misperrors
response = requests.get(f"{greynoise_api_url}", headers=headers, params=querystring) # Real request response = session.stats(querystring)
if response.status_code == 200: if "stats" in response:
response = response.json()
vulnerability_object = MISPObject("greynoise-vuln-info")
response["details"] = ( response["details"] = (
"The IP count below reflects the number of IPs seen " "The IP count below reflects the number of IPs seen "
"by GreyNoise in the last 7 days scanning for this CVE." "by GreyNoise in the last 7 days scanning for this CVE."
) )
response["id"] = vulnerability response["id"] = self.attribute.value
for feature in ("id", "details", "count"):
value = response.get(feature)
if value:
attribute_type, relation = vulnerability_mapping[feature]
vulnerability_object.add_attribute(relation, **{"type": attribute_type, "value": value})
classifications = response["stats"].get("classifications") classifications = response["stats"].get("classifications")
for item in classifications: for item in classifications:
if item["classification"] == "benign": if item["classification"] == "benign":
value = item["count"] value = item["count"]
attribute_type, relation = ("text", "Benign Scanner Count") response["benign"] = value
vulnerability_object.add_attribute(relation, **{"type": attribute_type, "value": value})
if item["classification"] == "unknown": if item["classification"] == "unknown":
value = item["count"] value = item["count"]
attribute_type, relation = ("text", "Unknown Scanner Count") response["unknown"] = value
vulnerability_object.add_attribute(relation, **{"type": attribute_type, "value": value})
if item["classification"] == "malicious": if item["classification"] == "malicious":
value = item["count"] value = item["count"]
attribute_type, relation = ("text", "Malicious Scanner Count") response["malicious"] = value
vulnerability_object.add_attribute(relation, **{"type": attribute_type, "value": value}) vulnerability_attributes = []
misp_event.add_object(vulnerability_object) for feature, mapping in self.vulnerability_mapping.items():
event = json.loads(misp_event.to_json()) if response.get(feature):
results = {key: event[key] for key in ("Attribute", "Object") if (key in event and event[key])} attribute = {"value": response[feature]}
return {"results": results} attribute.update(mapping)
vulnerability_attributes.append(attribute)
if vulnerability_attributes:
vulnerability_object = MISPObject("greynoise-vuln-info")
for attribute in vulnerability_attributes:
vulnerability_object.add_attribute(**attribute)
vulnerability_object.add_reference(self.attribute.uuid, "describes")
self.misp_event.add_object(vulnerability_object)
# There is an error def get_result(self):
errors = { event = json.loads(self.misp_event.to_json())
400: "Bad request.", results = {key: event[key] for key in ("Attribute", "Object") if (key in event and event[key])}
404: "IP not observed scanning the internet or contained in RIOT data set.", return {"results": results}
401: "Unauthorized. Please check your API key.",
429: "Too many requests. You've hit the rate-limit.",
} def handler(q=False):
try: if q is False:
misperrors["error"] = errors[response.status_code] return False
except KeyError: request = json.loads(q)
misperrors["error"] = f"GreyNoise API not accessible (HTTP {response.status_code})" if not request.get("config", {}).get("api_key"):
return misperrors return {"error": "GreyNoise API Key required, but missing"}
if not request.get("config", {}).get("api_type"):
return {"error": "GreyNoise API type of enterprise or community required, but missing"}
if not request.get("attribute") or not check_input_attribute(request["attribute"]):
return {"error": f"{standard_error_message}, which should contain at least a type, a value and an uuid."}
attribute = request["attribute"]
if attribute["type"] not in mispattributes["input"]:
return {"error": "Unsupported attribute type."}
greynoise_parser = GreyNoiseParser(attribute)
if attribute["type"] in ["ip-dst", "ip-src"]:
try:
ipaddress.IPv4Address(attribute["value"])
if "persistent" in request:
greynoise_parser.query_greynoise_ip_hover(request["config"]["api_key"], request["config"]["api_type"])
else:
greynoise_parser.query_greynoise_ip_expansion(request["config"]["api_key"], request["config"]["api_type"])
except ValueError:
return {"error": "Not a valid IPv4 address"}
if attribute["type"] == "vulnerability":
greynoise_parser.query_greynoise_vulnerability(request["config"]["api_key"], request["config"]["api_type"])
return greynoise_parser.get_result()
def introspection(): def introspection():

View File

@ -8,7 +8,7 @@ import json
misperrors = {"error": "Error"} misperrors = {"error": "Error"}
types_to_use = ['sha1', 'md5', 'domain', 'ip', 'url'] types_to_use = ['sha256', 'sha1', 'md5', 'domain', 'ip', 'url']
userConfig = { userConfig = {
@ -20,11 +20,17 @@ inputSource = ['event']
outputFileExtension = 'kql' outputFileExtension = 'kql'
responseType = 'application/txt' responseType = 'application/txt'
moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', moduleinfo = {'version': '1.1', 'author': 'Julien Bachmann, Hacknowledge, Maik Wuerth',
'description': 'Defender for Endpoint KQL hunting query export module', 'description': 'Defender for Endpoint KQL hunting query export module',
'module-type': ['export']} 'module-type': ['export']}
def handle_sha256(value, period):
query = f"""find in (DeviceAlertEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceProcessEvents)
where SHA256 == '{value}' or InitiatingProcessSHA1 == '{value}'"""
return query.replace('\n', ' ')
def handle_sha1(value, period): def handle_sha1(value, period):
query = f"""find in (DeviceAlertEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceProcessEvents) query = f"""find in (DeviceAlertEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceProcessEvents)
where SHA1 == '{value}' or InitiatingProcessSHA1 == '{value}'""" where SHA1 == '{value}' or InitiatingProcessSHA1 == '{value}'"""
@ -56,6 +62,7 @@ def handle_url(value, period):
handlers = { handlers = {
'sha256': handle_sha256,
'sha1': handle_sha1, 'sha1': handle_sha1,
'md5': handle_md5, 'md5': handle_md5,
'domain': handle_domain, 'domain': handle_domain,
@ -75,6 +82,10 @@ def handler(q=False):
for attribute in event["Attribute"]: for attribute in event["Attribute"]:
if attribute['type'] in types_to_use: if attribute['type'] in types_to_use:
output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n' output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n'
for obj in event["Object"]:
for attribute in obj["Attribute"]:
if attribute['type'] in types_to_use:
output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n'
r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')}
return r return r

View File

@ -16,7 +16,7 @@ edit_uri: ""
use_directory_urls: true use_directory_urls: true
# Copyright # Copyright
copyright: "Copyright &copy; 2019-2022 MISP Project" copyright: "Copyright &copy; 2019-2023 MISP Project"
# Options # Options
extra: extra:

View File

@ -297,7 +297,7 @@ class TestExpansions(unittest.TestCase):
) )
else: else:
response = self.misp_modules_post(query) response = self.misp_modules_post(query)
self.assertEqual(self.get_errors(response), 'Missing Greynoise API key.') self.assertEqual(self.get_errors(response), 'GreyNoise API Key required, but missing')
@unittest.skip("Service doesn't work") @unittest.skip("Service doesn't work")
def test_ipasn(self): def test_ipasn(self):