Skip to content

Commit 08a3184

Browse files
committed
added hunting_abuse_api analyzer
1 parent f1a3ac5 commit 08a3184

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from django.db import migrations
2+
from django.db.models.fields.related_descriptors import (
3+
ForwardManyToOneDescriptor,
4+
ForwardOneToOneDescriptor,
5+
ManyToManyDescriptor,
6+
ReverseManyToOneDescriptor,
7+
ReverseOneToOneDescriptor,
8+
)
9+
10+
plugin = {
11+
"python_module": {
12+
"health_check_schedule": None,
13+
"update_schedule": None,
14+
"module": "hunting_abuse.HuntingAbuseAPI",
15+
"base_path": "api_app.analyzers_manager.observable_analyzers",
16+
},
17+
"name": "HuntingAbuseAPI",
18+
"description": "[HuntingAbuseAPI](https://hunting.abuse.ch/api/) provides an updated list of false positives from all it's services. This API can be queried to verify. if the provided observable is valid or false positive.",
19+
"disabled": False,
20+
"soft_time_limit": 60,
21+
"routing_key": "default",
22+
"health_check_status": True,
23+
"type": "observable",
24+
"docker_based": False,
25+
"maximum_tlp": "GREEN",
26+
"observable_supported": ["ip", "url", "domain", "hash", "generic"],
27+
"supported_filetypes": [],
28+
"run_hash": False,
29+
"run_hash_type": "",
30+
"not_supported_filetypes": [],
31+
"mapping_data_model": {},
32+
"model": "analyzers_manager.AnalyzerConfig",
33+
}
34+
35+
params = [
36+
{
37+
"python_module": {
38+
"module": "hunting_abuse.HuntingAbuseAPI",
39+
"base_path": "api_app.analyzers_manager.observable_analyzers",
40+
},
41+
"name": "auth_key",
42+
"type": "str",
43+
"description": "Please specify the Auth Key here which you got when registering with Abuse.ch api in order to access the API. \r\n\r\nRef: https://hunting.abuse.ch/api/",
44+
"is_secret": True,
45+
"required": True,
46+
}
47+
]
48+
49+
values = []
50+
51+
52+
def _get_real_obj(Model, field, value):
53+
def _get_obj(Model, other_model, value):
54+
if isinstance(value, dict):
55+
real_vals = {}
56+
for key, real_val in value.items():
57+
real_vals[key] = _get_real_obj(other_model, key, real_val)
58+
value = other_model.objects.get_or_create(**real_vals)[0]
59+
# it is just the primary key serialized
60+
else:
61+
if isinstance(value, int):
62+
if Model.__name__ == "PluginConfig":
63+
value = other_model.objects.get(name=plugin["name"])
64+
else:
65+
value = other_model.objects.get(pk=value)
66+
else:
67+
value = other_model.objects.get(name=value)
68+
return value
69+
70+
if (
71+
type(getattr(Model, field))
72+
in [
73+
ForwardManyToOneDescriptor,
74+
ReverseManyToOneDescriptor,
75+
ReverseOneToOneDescriptor,
76+
ForwardOneToOneDescriptor,
77+
]
78+
and value
79+
):
80+
other_model = getattr(Model, field).get_queryset().model
81+
value = _get_obj(Model, other_model, value)
82+
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value:
83+
other_model = getattr(Model, field).rel.model
84+
value = [_get_obj(Model, other_model, val) for val in value]
85+
return value
86+
87+
88+
def _create_object(Model, data):
89+
mtm, no_mtm = {}, {}
90+
for field, value in data.items():
91+
value = _get_real_obj(Model, field, value)
92+
if type(getattr(Model, field)) is ManyToManyDescriptor:
93+
mtm[field] = value
94+
else:
95+
no_mtm[field] = value
96+
try:
97+
o = Model.objects.get(**no_mtm)
98+
except Model.DoesNotExist:
99+
o = Model(**no_mtm)
100+
o.full_clean()
101+
o.save()
102+
for field, value in mtm.items():
103+
attribute = getattr(o, field)
104+
if value is not None:
105+
attribute.set(value)
106+
return False
107+
return True
108+
109+
110+
def migrate(apps, schema_editor):
111+
Parameter = apps.get_model("api_app", "Parameter")
112+
PluginConfig = apps.get_model("api_app", "PluginConfig")
113+
python_path = plugin.pop("model")
114+
Model = apps.get_model(*python_path.split("."))
115+
if not Model.objects.filter(name=plugin["name"]).exists():
116+
exists = _create_object(Model, plugin)
117+
if not exists:
118+
for param in params:
119+
_create_object(Parameter, param)
120+
for value in values:
121+
_create_object(PluginConfig, value)
122+
123+
124+
def reverse_migrate(apps, schema_editor):
125+
python_path = plugin.pop("model")
126+
Model = apps.get_model(*python_path.split("."))
127+
Model.objects.get(name=plugin["name"]).delete()
128+
129+
130+
class Migration(migrations.Migration):
131+
atomic = False
132+
dependencies = [
133+
("api_app", "0071_delete_last_elastic_report"),
134+
("analyzers_manager", "0157_analyzer_config_phunter"),
135+
]
136+
137+
operations = [migrations.RunPython(migrate, reverse_migrate)]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl
2+
# See the file 'LICENSE' for copying permission.
3+
4+
5+
import logging
6+
7+
import requests
8+
9+
from api_app.analyzers_manager.classes import ObservableAnalyzer
10+
from tests.mock_utils import MockUpResponse, if_mock_connections, patch
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
class HuntingAbuseAPI(ObservableAnalyzer):
16+
url: str = "https://hunting-api.abuse.ch/api/v1/"
17+
_auth_key: str
18+
19+
@classmethod
20+
def update(cls):
21+
pass
22+
23+
def run(self):
24+
headers = {"Content-Type": "application/json", "Auth-Key": self._auth_key}
25+
26+
data = {"query": "get_fplist", "format": "json"}
27+
28+
response = requests.post(self.url, json=data, headers=headers)
29+
response.raise_for_status()
30+
fp_list = response.json()
31+
32+
for _key, value_dict in fp_list.items():
33+
logger.info(f"Fetching fp_status for {self.observable_name}")
34+
if value_dict["entry_value"] == self.observable_name:
35+
return {"fp_status": "true", "details": value_dict}
36+
return {"fp_status": "False"}
37+
38+
@classmethod
39+
def _monkeypatch(cls):
40+
mock_response = {
41+
"1": {
42+
"time_stamp": "2025-06-04 07:46:14 UTC",
43+
"platform": "MalwareBazaar",
44+
"entry_type": "sha1_hash",
45+
"entry_value": "ac4cb655a78a5634f6a87c82bec33a4391269a3f",
46+
"removed_by": "admin",
47+
"removal_notes": None,
48+
}
49+
}
50+
patches = [
51+
if_mock_connections(
52+
patch(
53+
"requests.post",
54+
return_value=MockUpResponse(mock_response, 200),
55+
),
56+
)
57+
]
58+
return super()._monkeypatch(patches)

0 commit comments

Comments
 (0)