Skip to content
This repository was archived by the owner on Nov 2, 2024. It is now read-only.

Commit 4ab8c5b

Browse files
g4zeg4ze
authored andcommitted
Spamhaus_WQS Analyzer, closes intelowlproject#1526 (intelowlproject#2378)
* init * init * migration * docs * python * better code * code handling and migrations * better code * docs link * docs link --------- Co-authored-by: g4ze <[email protected]>
1 parent 17ea925 commit 4ab8c5b

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from django.db import migrations
2+
from django.db.models.fields.related_descriptors import (
3+
ForwardManyToOneDescriptor,
4+
ForwardOneToOneDescriptor,
5+
ManyToManyDescriptor,
6+
)
7+
8+
plugin = {
9+
"python_module": {
10+
"health_check_schedule": {
11+
"minute": "0",
12+
"hour": "0",
13+
"day_of_week": "*",
14+
"day_of_month": "*",
15+
"month_of_year": "*",
16+
},
17+
"update_schedule": None,
18+
"module": "dns.dns_malicious_detectors.spamhaus_wqs.SpamhausWQS",
19+
"base_path": "api_app.analyzers_manager.observable_analyzers",
20+
},
21+
"name": "Spamhaus_WQS",
22+
"description": "[Spamhaus_WQS](https://docs.spamhaus.com/datasets/docs/source/70-access-methods/web-query-service/000-intro.html) : The Spamhaus Web Query Service (WQS) is a method of accessing Spamhaus block lists using the HTTPS protocol.",
23+
"disabled": False,
24+
"soft_time_limit": 10,
25+
"routing_key": "default",
26+
"health_check_status": True,
27+
"type": "observable",
28+
"docker_based": False,
29+
"maximum_tlp": "AMBER",
30+
"observable_supported": ["ip", "domain"],
31+
"supported_filetypes": [],
32+
"run_hash": False,
33+
"run_hash_type": "",
34+
"not_supported_filetypes": [],
35+
"model": "analyzers_manager.AnalyzerConfig",
36+
}
37+
38+
params = [
39+
{
40+
"python_module": {
41+
"module": "dns.dns_malicious_detectors.spamhaus_wqs.SpamhausWQS",
42+
"base_path": "api_app.analyzers_manager.observable_analyzers",
43+
},
44+
"name": "api_key",
45+
"type": "str",
46+
"description": "api key for Spamhaus_WQS",
47+
"is_secret": True,
48+
"required": True,
49+
}
50+
]
51+
52+
values = []
53+
54+
55+
def _get_real_obj(Model, field, value):
56+
def _get_obj(Model, other_model, value):
57+
if isinstance(value, dict):
58+
real_vals = {}
59+
for key, real_val in value.items():
60+
real_vals[key] = _get_real_obj(other_model, key, real_val)
61+
value = other_model.objects.get_or_create(**real_vals)[0]
62+
# it is just the primary key serialized
63+
else:
64+
if isinstance(value, int):
65+
if Model.__name__ == "PluginConfig":
66+
value = other_model.objects.get(name=plugin["name"])
67+
else:
68+
value = other_model.objects.get(pk=value)
69+
else:
70+
value = other_model.objects.get(name=value)
71+
return value
72+
73+
if (
74+
type(getattr(Model, field))
75+
in [ForwardManyToOneDescriptor, ForwardOneToOneDescriptor]
76+
and value
77+
):
78+
other_model = getattr(Model, field).get_queryset().model
79+
value = _get_obj(Model, other_model, value)
80+
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value:
81+
other_model = getattr(Model, field).rel.model
82+
value = [_get_obj(Model, other_model, val) for val in value]
83+
return value
84+
85+
86+
def _create_object(Model, data):
87+
mtm, no_mtm = {}, {}
88+
for field, value in data.items():
89+
value = _get_real_obj(Model, field, value)
90+
if type(getattr(Model, field)) is ManyToManyDescriptor:
91+
mtm[field] = value
92+
else:
93+
no_mtm[field] = value
94+
try:
95+
o = Model.objects.get(**no_mtm)
96+
except Model.DoesNotExist:
97+
o = Model(**no_mtm)
98+
o.full_clean()
99+
o.save()
100+
for field, value in mtm.items():
101+
attribute = getattr(o, field)
102+
if value is not None:
103+
attribute.set(value)
104+
return False
105+
return True
106+
107+
108+
def migrate(apps, schema_editor):
109+
Parameter = apps.get_model("api_app", "Parameter")
110+
PluginConfig = apps.get_model("api_app", "PluginConfig")
111+
python_path = plugin.pop("model")
112+
Model = apps.get_model(*python_path.split("."))
113+
if not Model.objects.filter(name=plugin["name"]).exists():
114+
exists = _create_object(Model, plugin)
115+
if not exists:
116+
for param in params:
117+
_create_object(Parameter, param)
118+
for value in values:
119+
_create_object(PluginConfig, value)
120+
121+
122+
def reverse_migrate(apps, schema_editor):
123+
python_path = plugin.pop("model")
124+
Model = apps.get_model(*python_path.split("."))
125+
Model.objects.get(name=plugin["name"]).delete()
126+
127+
128+
class Migration(migrations.Migration):
129+
atomic = False
130+
dependencies = [
131+
("api_app", "0062_alter_parameter_python_module"),
132+
("analyzers_manager", "0098_analyzer_config_crt_sh"),
133+
]
134+
135+
operations = [migrations.RunPython(migrate, reverse_migrate)]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import logging
2+
3+
import requests
4+
5+
from api_app.analyzers_manager import classes
6+
from api_app.analyzers_manager.exceptions import AnalyzerRunException
7+
from tests.mock_utils import MockUpResponse, if_mock_connections, patch
8+
9+
from ..dns_responses import malicious_detector_response
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class SpamhausWQS(classes.ObservableAnalyzer):
15+
url: str = "https://apibl.spamhaus.net/lookup/v1"
16+
_api_key: str = None
17+
18+
def update(self):
19+
pass
20+
21+
def run(self):
22+
headers = {"Authorization": f"Bearer {self._api_key}"}
23+
response = requests.get(
24+
url=f"""{self.url}/
25+
{
26+
"DBL"
27+
if self.observable_classification == self.ObservableTypes.DOMAIN.value
28+
else "AUTHBL"
29+
}
30+
/{self.observable_name}""",
31+
headers=headers,
32+
)
33+
# refer to the link for status code info
34+
# https://docs.spamhaus.com/datasets/docs/source/70-access-methods/web-query-service/060-api-info.html#http-response-status-codes
35+
if response.status_code == 200:
36+
# 200 - Found - The record is listed
37+
return malicious_detector_response(self.observable_name, True)
38+
elif response.status_code == 404:
39+
# 404 - Not found - The record is not listed
40+
return malicious_detector_response(self.observable_name, False)
41+
else:
42+
raise AnalyzerRunException(f"result not expected: {response.status_code}")
43+
44+
@classmethod
45+
def _monkeypatch(cls):
46+
patches = [
47+
if_mock_connections(
48+
patch(
49+
"requests.get",
50+
return_value=MockUpResponse({"resp": [1020], "status": 200}, 200),
51+
),
52+
)
53+
]
54+
return super()._monkeypatch(patches=patches)

docs/source/Usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ The following is the list of the available analyzers you can run out-of-the-box.
263263
* `MalprobSearch`:[Malprob](https://malprob.io/) is a leading malware detection and identification service, powered by cutting-edge AI technology.
264264
* `OrklSearch`:[Orkl](https://orkl.eu/) is the Community Driven Cyber Threat Intelligence Library.
265265
* `Crt_sh`:[Crt_Sh](https://crt.sh/) lets you get certificates info about a domain.
266+
* `Spamhaus_WQS`:[Spamhaus_WQS](https://docs.spamhaus.com/datasets/docs/source/70-access-methods/web-query-service/000-intro.html) : The Spamhaus Web Query Service (WQS) is a method of accessing Spamhaus block lists using the HTTPS protocol.
266267

267268
##### Generic analyzers (email, phone number, etc.; anything really)
268269

0 commit comments

Comments
 (0)