-
-
Notifications
You must be signed in to change notification settings - Fork 461
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
fixes #1758 - greynoise labs analyzer #2210
Changes from 1 commit
149aebe
0d6464a
0e0a9f2
90a4c22
c07df95
6cbf881
5397ff1
60e56d0
bf18209
6b1ccf0
327324c
57fc5b6
70b8134
3958630
f75b944
5cdc3ad
80cdaed
cfe1310
5b9025c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
from django.db import migrations | ||
from django.db.models.fields.related_descriptors import ( | ||
ForwardManyToOneDescriptor, | ||
ForwardOneToOneDescriptor, | ||
ManyToManyDescriptor, | ||
) | ||
|
||
plugin = { | ||
"python_module": { | ||
"health_check_schedule": None, | ||
"update_schedule": None, | ||
"module": "greynoise_labs.GreynoiseLabs", | ||
"base_path": "api_app.analyzers_manager.observable_analyzers", | ||
}, | ||
"name": "Greynoise_Labs", | ||
"description": "scan an IP against the Greynoise Labs API (requires authentication token obtained from cookies on greynoise website)", | ||
"disabled": False, | ||
"soft_time_limit": 60, | ||
"routing_key": "default", | ||
"health_check_status": True, | ||
"type": "observable", | ||
"docker_based": False, | ||
"maximum_tlp": "RED", | ||
"observable_supported": ["ip"], | ||
"supported_filetypes": [], | ||
"run_hash": False, | ||
"run_hash_type": "", | ||
"not_supported_filetypes": [], | ||
"model": "analyzers_manager.AnalyzerConfig", | ||
} | ||
|
||
params = [ | ||
{ | ||
"python_module": { | ||
"module": "greynoise_labs.GreynoiseLabs", | ||
"base_path": "api_app.analyzers_manager.observable_analyzers", | ||
}, | ||
"name": "auth_token", | ||
"type": "str", | ||
"description": "Authentication token obtained from cookies on greynoise website.", | ||
"is_secret": True, | ||
"required": True, | ||
} | ||
] | ||
|
||
values = [] | ||
|
||
|
||
def _get_real_obj(Model, field, value): | ||
def _get_obj(Model, other_model, value): | ||
if isinstance(value, dict): | ||
real_vals = {} | ||
for key, real_val in value.items(): | ||
real_vals[key] = _get_real_obj(other_model, key, real_val) | ||
value = other_model.objects.get_or_create(**real_vals)[0] | ||
# it is just the primary key serialized | ||
else: | ||
if isinstance(value, int): | ||
if Model.__name__ == "PluginConfig": | ||
value = other_model.objects.get(name=plugin["name"]) | ||
else: | ||
value = other_model.objects.get(pk=value) | ||
else: | ||
value = other_model.objects.get(name=value) | ||
return value | ||
|
||
if ( | ||
type(getattr(Model, field)) | ||
in [ForwardManyToOneDescriptor, ForwardOneToOneDescriptor] | ||
and value | ||
): | ||
other_model = getattr(Model, field).get_queryset().model | ||
value = _get_obj(Model, other_model, value) | ||
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value: | ||
other_model = getattr(Model, field).rel.model | ||
value = [_get_obj(Model, other_model, val) for val in value] | ||
return value | ||
|
||
|
||
def _create_object(Model, data): | ||
mtm, no_mtm = {}, {} | ||
for field, value in data.items(): | ||
value = _get_real_obj(Model, field, value) | ||
if type(getattr(Model, field)) is ManyToManyDescriptor: | ||
mtm[field] = value | ||
else: | ||
no_mtm[field] = value | ||
try: | ||
o = Model.objects.get(**no_mtm) | ||
except Model.DoesNotExist: | ||
o = Model(**no_mtm) | ||
o.full_clean() | ||
o.save() | ||
for field, value in mtm.items(): | ||
attribute = getattr(o, field) | ||
if value is not None: | ||
attribute.set(value) | ||
return False | ||
return True | ||
|
||
|
||
def migrate(apps, schema_editor): | ||
Parameter = apps.get_model("api_app", "Parameter") | ||
PluginConfig = apps.get_model("api_app", "PluginConfig") | ||
python_path = plugin.pop("model") | ||
Model = apps.get_model(*python_path.split(".")) | ||
if not Model.objects.filter(name=plugin["name"]).exists(): | ||
exists = _create_object(Model, plugin) | ||
if not exists: | ||
for param in params: | ||
_create_object(Parameter, param) | ||
for value in values: | ||
_create_object(PluginConfig, value) | ||
|
||
|
||
def reverse_migrate(apps, schema_editor): | ||
python_path = plugin.pop("model") | ||
Model = apps.get_model(*python_path.split(".")) | ||
Model.objects.get(name=plugin["name"]).delete() | ||
|
||
|
||
class Migration(migrations.Migration): | ||
atomic = False | ||
dependencies = [ | ||
("api_app", "0061_job_depth_analysis"), | ||
("analyzers_manager", "0070_urlhaus_threatfox_disable_param"), | ||
mlodic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
] | ||
|
||
operations = [migrations.RunPython(migrate, reverse_migrate)] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl | ||
# See the file 'LICENSE' for copying permission. | ||
|
||
import requests | ||
|
||
from api_app.analyzers_manager.classes import ObservableAnalyzer | ||
from api_app.analyzers_manager.exceptions import AnalyzerRunException | ||
from tests.mock_utils import MockUpResponse, if_mock_connections, patch | ||
|
||
queries = { | ||
"noiserank": { | ||
"query_string": "query NoiseRank($ip: String) { noiseRank(ip: $ip) \ | ||
{ queryInfo { resultsAvailable resultsLimit } ips { ip noise_score \ | ||
sensor_pervasiveness country_pervasiveness payload_diversity \ | ||
port_diversity request_rate } } }", | ||
"ip_required": True, | ||
}, | ||
"topknocks": { | ||
"query_string": "query TopKnocks($ip: String) { topKnocks(ip: $ip) \ | ||
{ queryInfo { resultsAvailable resultsLimit } knock { last_crawled \ | ||
last_seen source_ip knock_port title favicon_mmh3_32 \ | ||
favicon_mmh3_128 jarm ips emails links tor_exit headers apps } } } ", | ||
"ip_required": True, | ||
}, | ||
"topc2s": { | ||
"query_string": "query TopC2s { topC2s { queryInfo \ | ||
{ resultsAvailable resultsLimit } c2s { source_ip c2_ips \ | ||
c2_domains payload hits pervasiveness } } } " | ||
}, | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the analyzer is cool. The only problem regarding this analyzer is this type of query that does not support IP addresses anymore. For this cases, we usually make the analyzer work in a different way. We maintain a local cache of the data extracted from the Greynoise endpoint (a file in the system) and we open it once the analyzer is triggered. Thanks to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @mlodic , I have added the
|
||
|
||
class GreynoiseLabs(ObservableAnalyzer): | ||
_auth_token: str | ||
|
||
def run(self): | ||
result = {} | ||
url = "https://api.labs.greynoise.io/1/query" | ||
headers = { | ||
"Content-Type": "application/json", | ||
"Authorization": f"Bearer {self._auth_token}", | ||
} | ||
|
||
try: | ||
for key, value in queries.items(): | ||
json_body = {"query": value["query_string"]} | ||
if "ip_required" in value and value["ip_required"]: | ||
json_body["variables"] = {"ip": f"{self.observable_name}"} | ||
|
||
response = requests.post(headers=headers, json=json_body, url=url) | ||
response.raise_for_status() | ||
result[key] = response.json() | ||
except requests.RequestException as e: | ||
raise AnalyzerRunException(e) | ||
|
||
return result | ||
|
||
@classmethod | ||
def _monkeypatch(cls): | ||
patches = [ | ||
if_mock_connections( | ||
patch("requests.post", return_value=MockUpResponse({}, 200)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you please write examples of outputs here in the tests? like the ones that you shared with me In this way tests would run with a real output and we could also save an example of their reports here. Please do a mock for every requests you do (2). See There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like one request whose ip is in noiseRank and one which is not in noiseRank, right? @mlodic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. basically here the tests will cycle through the list of mocks that you write and use one of them every time the analyzer would try to do a http request of the chosen method. So basically one mock for each request that you make, so one for each endpoint in greynoise |
||
) | ||
] | ||
return super()._monkeypatch(patches=patches) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this file is not required cause this analyzer requires additional configuration |
||
# See the file 'LICENSE' for copying permission. | ||
|
||
from django.db import migrations | ||
|
||
|
||
def migrate(apps, schema_editor): | ||
playbook_config = apps.get_model("playbooks_manager", "PlaybookConfig") | ||
AnalyzerConfig = apps.get_model("analyzers_manager", "AnalyzerConfig") | ||
pc = playbook_config.objects.get(name="FREE_TO_USE_ANALYZERS") | ||
pc.analyzers.add(AnalyzerConfig.objects.get(name="Greynoise_Labs").id) | ||
pc.full_clean() | ||
pc.save() | ||
|
||
|
||
def reverse_migrate(apps, schema_editor): | ||
playbook_config = apps.get_model("playbooks_manager", "PlaybookConfig") | ||
AnalyzerConfig = apps.get_model("analyzers_manager", "AnalyzerConfig") | ||
pc = playbook_config.objects.get(name="FREE_TO_USE_ANALYZERS") | ||
pc.analyzers.remove(AnalyzerConfig.objects.get(name="Greynoise_Labs").id) | ||
pc.full_clean() | ||
pc.save() | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("playbooks_manager", "0028_add_bgp_ranking_to_free_to_use"), | ||
("analyzers_manager", "0071_analyzer_config_greynoise_labs"), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(migrate, reverse_migrate), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add markdown with link to the service here so it will be displayed in the gui