Skip to content

Commit 7cebbd4

Browse files
committed
adding commandsequence_api to greedybear
1 parent 08a3184 commit 7cebbd4

File tree

2 files changed

+286
-7
lines changed

2 files changed

+286
-7
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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": "greedybear.GreedyBear",
15+
"base_path": "api_app.analyzers_manager.observable_analyzers",
16+
},
17+
"name": "GreedyBear",
18+
"description": "scan an IP or a domain against the [GreedyBear](https://www.honeynet.org/2021/12/27/new-project-available-greedybear/) service",
19+
"disabled": False,
20+
"soft_time_limit": 30,
21+
"routing_key": "default",
22+
"health_check_status": True,
23+
"type": "observable",
24+
"docker_based": False,
25+
"maximum_tlp": "AMBER",
26+
"observable_supported": ["ip", "domain", "hash"],
27+
"supported_filetypes": [],
28+
"run_hash": False,
29+
"run_hash_type": "sha256",
30+
"not_supported_filetypes": [],
31+
"mapping_data_model": {},
32+
"model": "analyzers_manager.AnalyzerConfig",
33+
}
34+
35+
params = [
36+
{
37+
"python_module": {
38+
"module": "greedybear.GreedyBear",
39+
"base_path": "api_app.analyzers_manager.observable_analyzers",
40+
},
41+
"name": "url",
42+
"type": "str",
43+
"description": "URL of the GreedyBear instance you want to connect to",
44+
"is_secret": False,
45+
"required": False,
46+
},
47+
{
48+
"python_module": {
49+
"module": "greedybear.GreedyBear",
50+
"base_path": "api_app.analyzers_manager.observable_analyzers",
51+
},
52+
"name": "api_key_name",
53+
"type": "str",
54+
"description": "API key required for authentication",
55+
"is_secret": True,
56+
"required": True,
57+
},
58+
{
59+
"python_module": {
60+
"module": "greedybear.GreedyBear",
61+
"base_path": "api_app.analyzers_manager.observable_analyzers",
62+
},
63+
"name": "command_sequence_toggle",
64+
"type": "bool",
65+
"description": "Enable fetching the details from CommandSequenceAPI",
66+
"is_secret": False,
67+
"required": False,
68+
},
69+
{
70+
"python_module": {
71+
"module": "greedybear.GreedyBear",
72+
"base_path": "api_app.analyzers_manager.observable_analyzers",
73+
},
74+
"name": "same_cluster_commands",
75+
"type": "bool",
76+
"description": "Fetch results for command sequences from same cluster",
77+
"is_secret": False,
78+
"required": False,
79+
},
80+
]
81+
82+
values = [
83+
{
84+
"parameter": {
85+
"python_module": {
86+
"module": "greedybear.GreedyBear",
87+
"base_path": "api_app.analyzers_manager.observable_analyzers",
88+
},
89+
"name": "url",
90+
"type": "str",
91+
"description": "URL of the GreedyBear instance you want to connect to",
92+
"is_secret": False,
93+
"required": False,
94+
},
95+
"analyzer_config": "GreedyBear",
96+
"connector_config": None,
97+
"visualizer_config": None,
98+
"ingestor_config": None,
99+
"pivot_config": None,
100+
"for_organization": False,
101+
"value": "https://greedybear.honeynet.org",
102+
"updated_at": "2025-02-19T14:53:18.159755Z",
103+
"owner": None,
104+
},
105+
{
106+
"parameter": {
107+
"python_module": {
108+
"module": "greedybear.GreedyBear",
109+
"base_path": "api_app.analyzers_manager.observable_analyzers",
110+
},
111+
"name": "command_sequence_toggle",
112+
"type": "bool",
113+
"description": "Enable fetching the details from CommandSequenceAPI",
114+
"is_secret": False,
115+
"required": False,
116+
},
117+
"analyzer_config": "GreedyBear",
118+
"connector_config": None,
119+
"visualizer_config": None,
120+
"ingestor_config": None,
121+
"pivot_config": None,
122+
"for_organization": False,
123+
"value": True,
124+
"updated_at": "2025-06-19T03:34:12.680076Z",
125+
"owner": None,
126+
},
127+
{
128+
"parameter": {
129+
"python_module": {
130+
"module": "greedybear.GreedyBear",
131+
"base_path": "api_app.analyzers_manager.observable_analyzers",
132+
},
133+
"name": "same_cluster_commands",
134+
"type": "bool",
135+
"description": "Fetch results for command sequences from same cluster",
136+
"is_secret": False,
137+
"required": False,
138+
},
139+
"analyzer_config": "GreedyBear",
140+
"connector_config": None,
141+
"visualizer_config": None,
142+
"ingestor_config": None,
143+
"pivot_config": None,
144+
"for_organization": False,
145+
"value": False,
146+
"updated_at": "2025-06-19T03:34:12.688262Z",
147+
"owner": None,
148+
},
149+
]
150+
151+
152+
def _get_real_obj(Model, field, value):
153+
def _get_obj(Model, other_model, value):
154+
if isinstance(value, dict):
155+
real_vals = {}
156+
for key, real_val in value.items():
157+
real_vals[key] = _get_real_obj(other_model, key, real_val)
158+
value = other_model.objects.get_or_create(**real_vals)[0]
159+
# it is just the primary key serialized
160+
else:
161+
if isinstance(value, int):
162+
if Model.__name__ == "PluginConfig":
163+
value = other_model.objects.get(name=plugin["name"])
164+
else:
165+
value = other_model.objects.get(pk=value)
166+
else:
167+
value = other_model.objects.get(name=value)
168+
return value
169+
170+
if (
171+
type(getattr(Model, field))
172+
in [
173+
ForwardManyToOneDescriptor,
174+
ReverseManyToOneDescriptor,
175+
ReverseOneToOneDescriptor,
176+
ForwardOneToOneDescriptor,
177+
]
178+
and value
179+
):
180+
other_model = getattr(Model, field).get_queryset().model
181+
value = _get_obj(Model, other_model, value)
182+
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value:
183+
other_model = getattr(Model, field).rel.model
184+
value = [_get_obj(Model, other_model, val) for val in value]
185+
return value
186+
187+
188+
def _create_object(Model, data):
189+
mtm, no_mtm = {}, {}
190+
for field, value in data.items():
191+
value = _get_real_obj(Model, field, value)
192+
if type(getattr(Model, field)) is ManyToManyDescriptor:
193+
mtm[field] = value
194+
else:
195+
no_mtm[field] = value
196+
try:
197+
o = Model.objects.get(**no_mtm)
198+
except Model.DoesNotExist:
199+
o = Model(**no_mtm)
200+
o.full_clean()
201+
o.save()
202+
for field, value in mtm.items():
203+
attribute = getattr(o, field)
204+
if value is not None:
205+
attribute.set(value)
206+
return False
207+
return True
208+
209+
210+
def migrate(apps, schema_editor):
211+
Parameter = apps.get_model("api_app", "Parameter")
212+
PluginConfig = apps.get_model("api_app", "PluginConfig")
213+
python_path = plugin.pop("model")
214+
Model = apps.get_model(*python_path.split("."))
215+
if not Model.objects.filter(name=plugin["name"]).exists():
216+
exists = _create_object(Model, plugin)
217+
if not exists:
218+
for param in params:
219+
_create_object(Parameter, param)
220+
for value in values:
221+
_create_object(PluginConfig, value)
222+
223+
224+
def reverse_migrate(apps, schema_editor):
225+
python_path = plugin.pop("model")
226+
Model = apps.get_model(*python_path.split("."))
227+
Model.objects.get(name=plugin["name"]).delete()
228+
229+
230+
class Migration(migrations.Migration):
231+
atomic = False
232+
dependencies = [
233+
("api_app", "0071_delete_last_elastic_report"),
234+
("analyzers_manager", "0158_analyzer_config_huntingabuseapi"),
235+
]
236+
237+
operations = [migrations.RunPython(migrate, reverse_migrate)]

api_app/analyzers_manager/observable_analyzers/greedybear.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl
22
# See the file 'LICENSE' for copying permission.
33

4+
from ipaddress import ip_address
5+
46
import requests
57

68
from api_app.analyzers_manager.classes import ObservableAnalyzer
@@ -10,20 +12,60 @@
1012
class GreedyBear(ObservableAnalyzer):
1113
_api_key_name: str
1214
url: str
15+
command_sequence_toggle: bool = True
16+
same_cluster_commands: bool = False
17+
18+
def is_ip_address(self, observable: str) -> bool:
19+
try:
20+
ip_address(observable)
21+
return True
22+
except ValueError:
23+
return False
24+
25+
def is_sha256_hash(self, observable: str) -> bool:
26+
return len(observable) == 64 and all(
27+
c in "0123456789abcdefABCDEF" for c in self.observable_name
28+
)
29+
30+
@classmethod
31+
def update(cls):
32+
pass
1333

1434
def run(self):
1535
headers = {
1636
"Authorization": "Token " + self._api_key_name,
1737
"Accept": "application/json",
1838
}
19-
params_ = {
20-
"query": self.observable_name,
21-
}
22-
uri = "/api/enrichment"
23-
response = requests.get(self.url + uri, params=params_, headers=headers)
24-
response.raise_for_status()
2539

26-
result = response.json()
40+
params_ = {"query": self.observable_name, "include_similar": False}
41+
42+
enrichment_uri = "/api/enrichment"
43+
command_sequence_uri = "/api/command_sequence"
44+
45+
result = {}
46+
47+
if self.is_sha256_hash(self.observable_name):
48+
if self.same_cluster_commands:
49+
params_["include_similar"] = True
50+
51+
command_sequence_response = requests.get(
52+
self.url + command_sequence_uri, params=params_, headers=headers
53+
)
54+
result = {"command_sequence_results": command_sequence_response.json()}
55+
56+
else:
57+
if self.command_sequence_toggle:
58+
if self.same_cluster_commands:
59+
params_["include_similar"] = True
60+
command_sequence_response = requests.get(
61+
self.url + command_sequence_uri, params=params_, headers=headers
62+
)
63+
result["command_sequence_results"] = command_sequence_response.json()
64+
65+
enrichment_response = requests.get(
66+
self.url + enrichment_uri, params=params_, headers=headers
67+
)
68+
result["enrichment_results"] = enrichment_response.json()
2769

2870
return result
2971

0 commit comments

Comments
 (0)