mirror of https://github.com/MISP/misp-modules
Merge branch 'main' of github.com:MISP/misp-modules
commit
78b06ea51b
|
@ -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
|
||||||
|
|
|
@ -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 |
|
@ -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."
|
||||||
|
}
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ edit_uri: ""
|
||||||
use_directory_urls: true
|
use_directory_urls: true
|
||||||
|
|
||||||
# Copyright
|
# Copyright
|
||||||
copyright: "Copyright © 2019-2022 MISP Project"
|
copyright: "Copyright © 2019-2023 MISP Project"
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
extra:
|
extra:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue