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

Commit a022146

Browse files
g4zeg4ze
authored andcommitted
Knock analyzer (intelowlproject#2448)
* knock * migration * knock but no deletion reqed * t/o test * rmv log * timeout tests * t/o * mock * mock * tests * tests * t/o * typo * tlp * pypi * works now * log * mign --------- Co-authored-by: g4ze <[email protected]>
1 parent 37b7ef7 commit a022146

File tree

5 files changed

+280
-3
lines changed

5 files changed

+280
-3
lines changed

api_app/analyzers_manager/file_analyzers/polyswarm.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,6 @@ def run(self):
6767

6868
return result
6969

70-
def update(self):
71-
pass
72-
7370
@classmethod
7471
def _monkeypatch(cls):
7572
patches = [
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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": None,
11+
"update_schedule": None,
12+
"module": "knockanalyzer.KnockAnalyzer",
13+
"base_path": "api_app.analyzers_manager.observable_analyzers",
14+
},
15+
"name": "Knock",
16+
"description": "[Knock](https://github.com/guelfoweb/knock) is a portable and modular python3 tool designed to quickly enumerate subdomains on a target domain through passive reconnaissance and dictionary scan.",
17+
"disabled": False,
18+
"soft_time_limit": 600,
19+
"routing_key": "default",
20+
"health_check_status": True,
21+
"type": "observable",
22+
"docker_based": False,
23+
"maximum_tlp": "CLEAR",
24+
"observable_supported": ["domain"],
25+
"supported_filetypes": [],
26+
"run_hash": False,
27+
"run_hash_type": "",
28+
"not_supported_filetypes": [],
29+
"model": "analyzers_manager.AnalyzerConfig",
30+
}
31+
32+
params = [
33+
{
34+
"python_module": {
35+
"module": "knockanalyzer.KnockAnalyzer",
36+
"base_path": "api_app.analyzers_manager.observable_analyzers",
37+
},
38+
"name": "useragent",
39+
"type": "str",
40+
"description": "custom useragent",
41+
"is_secret": False,
42+
"required": False,
43+
},
44+
{
45+
"python_module": {
46+
"module": "knockanalyzer.KnockAnalyzer",
47+
"base_path": "api_app.analyzers_manager.observable_analyzers",
48+
},
49+
"name": "timeout",
50+
"type": "int",
51+
"description": "custom timeout(s)",
52+
"is_secret": False,
53+
"required": False,
54+
},
55+
{
56+
"python_module": {
57+
"module": "knockanalyzer.KnockAnalyzer",
58+
"base_path": "api_app.analyzers_manager.observable_analyzers",
59+
},
60+
"name": "threads",
61+
"type": "int",
62+
"description": "custom threads",
63+
"is_secret": False,
64+
"required": False,
65+
},
66+
{
67+
"python_module": {
68+
"module": "knockanalyzer.KnockAnalyzer",
69+
"base_path": "api_app.analyzers_manager.observable_analyzers",
70+
},
71+
"name": "recon",
72+
"type": "bool",
73+
"description": "subdomain reconnaissance",
74+
"is_secret": False,
75+
"required": False,
76+
},
77+
{
78+
"python_module": {
79+
"module": "knockanalyzer.KnockAnalyzer",
80+
"base_path": "api_app.analyzers_manager.observable_analyzers",
81+
},
82+
"name": "bruteforce",
83+
"type": "bool",
84+
"description": "subdomain bruteforce",
85+
"is_secret": False,
86+
"required": False,
87+
},
88+
{
89+
"python_module": {
90+
"module": "knockanalyzer.KnockAnalyzer",
91+
"base_path": "api_app.analyzers_manager.observable_analyzers",
92+
},
93+
"name": "dns",
94+
"type": "str",
95+
"description": "dns for knockpy",
96+
"is_secret": False,
97+
"required": False,
98+
},
99+
]
100+
101+
values = [
102+
{
103+
"parameter": {
104+
"python_module": {
105+
"module": "knockanalyzer.KnockAnalyzer",
106+
"base_path": "api_app.analyzers_manager.observable_analyzers",
107+
},
108+
"name": "recon",
109+
"type": "bool",
110+
"description": "subdomain reconnaissance",
111+
"is_secret": False,
112+
"required": False,
113+
},
114+
"analyzer_config": "Knock",
115+
"connector_config": None,
116+
"visualizer_config": None,
117+
"ingestor_config": None,
118+
"pivot_config": None,
119+
"for_organization": False,
120+
"value": True,
121+
"updated_at": "2024-05-22T12:25:43.887022Z",
122+
"owner": None,
123+
},
124+
{
125+
"parameter": {
126+
"python_module": {
127+
"module": "knockanalyzer.KnockAnalyzer",
128+
"base_path": "api_app.analyzers_manager.observable_analyzers",
129+
},
130+
"name": "bruteforce",
131+
"type": "bool",
132+
"description": "subdomain bruteforce",
133+
"is_secret": False,
134+
"required": False,
135+
},
136+
"analyzer_config": "Knock",
137+
"connector_config": None,
138+
"visualizer_config": None,
139+
"ingestor_config": None,
140+
"pivot_config": None,
141+
"for_organization": False,
142+
"value": True,
143+
"updated_at": "2024-05-22T12:25:45.001333Z",
144+
"owner": None,
145+
},
146+
]
147+
148+
149+
def _get_real_obj(Model, field, value):
150+
def _get_obj(Model, other_model, value):
151+
if isinstance(value, dict):
152+
real_vals = {}
153+
for key, real_val in value.items():
154+
real_vals[key] = _get_real_obj(other_model, key, real_val)
155+
value = other_model.objects.get_or_create(**real_vals)[0]
156+
# it is just the primary key serialized
157+
else:
158+
if isinstance(value, int):
159+
if Model.__name__ == "PluginConfig":
160+
value = other_model.objects.get(name=plugin["name"])
161+
else:
162+
value = other_model.objects.get(pk=value)
163+
else:
164+
value = other_model.objects.get(name=value)
165+
return value
166+
167+
if (
168+
type(getattr(Model, field))
169+
in [ForwardManyToOneDescriptor, ForwardOneToOneDescriptor]
170+
and value
171+
):
172+
other_model = getattr(Model, field).get_queryset().model
173+
value = _get_obj(Model, other_model, value)
174+
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value:
175+
other_model = getattr(Model, field).rel.model
176+
value = [_get_obj(Model, other_model, val) for val in value]
177+
return value
178+
179+
180+
def _create_object(Model, data):
181+
mtm, no_mtm = {}, {}
182+
for field, value in data.items():
183+
value = _get_real_obj(Model, field, value)
184+
if type(getattr(Model, field)) is ManyToManyDescriptor:
185+
mtm[field] = value
186+
else:
187+
no_mtm[field] = value
188+
try:
189+
o = Model.objects.get(**no_mtm)
190+
except Model.DoesNotExist:
191+
o = Model(**no_mtm)
192+
o.full_clean()
193+
o.save()
194+
for field, value in mtm.items():
195+
attribute = getattr(o, field)
196+
if value is not None:
197+
attribute.set(value)
198+
return False
199+
return True
200+
201+
202+
def migrate(apps, schema_editor):
203+
Parameter = apps.get_model("api_app", "Parameter")
204+
PluginConfig = apps.get_model("api_app", "PluginConfig")
205+
python_path = plugin.pop("model")
206+
Model = apps.get_model(*python_path.split("."))
207+
if not Model.objects.filter(name=plugin["name"]).exists():
208+
exists = _create_object(Model, plugin)
209+
if not exists:
210+
for param in params:
211+
_create_object(Parameter, param)
212+
for value in values:
213+
_create_object(PluginConfig, value)
214+
215+
216+
def reverse_migrate(apps, schema_editor):
217+
python_path = plugin.pop("model")
218+
Model = apps.get_model(*python_path.split("."))
219+
Model.objects.get(name=plugin["name"]).delete()
220+
221+
222+
class Migration(migrations.Migration):
223+
atomic = False
224+
dependencies = [
225+
("api_app", "0062_alter_parameter_python_module"),
226+
("analyzers_manager", "0114_analyzer_config_polyswarmobs"),
227+
]
228+
229+
operations = [migrations.RunPython(migrate, reverse_migrate)]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import json
2+
import logging
3+
4+
from knock import knockpy
5+
6+
from api_app.analyzers_manager import classes
7+
from tests.mock_utils import if_mock_connections, patch
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class KnockAnalyzer(classes.ObservableAnalyzer):
13+
"""
14+
This analyzer is a wrapper for the knockpy project.
15+
"""
16+
17+
dns: str = None
18+
useragent: str = None
19+
timeout: int = None
20+
threads: int = None
21+
recon: bool = True
22+
bruteforce: bool = True
23+
24+
def update(self) -> bool:
25+
pass
26+
27+
def run(self):
28+
logger.info(f"Running KnockAnalyzer for {self.observable_name}")
29+
results = knockpy.KNOCKPY(
30+
domain=self.observable_name,
31+
dns=self.dns,
32+
useragent=self.useragent,
33+
timeout=self.timeout,
34+
threads=self.threads,
35+
recon=self.recon,
36+
bruteforce=self.bruteforce,
37+
)
38+
39+
results = json.dumps(results)
40+
return results
41+
42+
@classmethod
43+
def _monkeypatch(cls):
44+
patches = [
45+
if_mock_connections(
46+
patch.object(knockpy, "KNOCKPY", return_value=None),
47+
)
48+
]
49+
return super()._monkeypatch(patches=patches)

docs/source/Usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ threat prevention, reducing and automating the manual work of security analysts.
276276
* `CriminalIp`: [Criminal IP](https://www.criminalip.io/) is an OSINT search engine specialized in attack surface assessment and threat hunting. It offers extensive cyber threat intelligence, including device reputation, geolocation, IP reputation for C2 or scanners, domain safety, malicious link detection, and APT attack vectors via search and API.
277277
* `CriminalIp_Scan`:CriminalIp_Scan is an implementation of scan APIs provided by [CriminalIp](https://www.criminalip.io/) specifically for domains. Criminal IP is an OSINT search engine specialized in attack surface assessment and threat hunting. It offers extensive cyber threat intelligence, including device reputation, geolocation, IP reputation for C2 or scanners, domain safety, malicious link detection, and APT attack vectors via search and API.
278278
* `PolyswarmObs`: Scan an observable using [Polyswarm](https://docs.polyswarm.io/) API. Paid plan is required for IP and Domain scans. Hash scan is free.
279+
* `Knock`:[Knock](https://github.com/guelfoweb/knock) or Knockpy is a portable and modular python3 tool designed to quickly enumerate subdomains on a target domain through passive reconnaissance and dictionary scan.
279280

280281
##### Generic analyzers (email, phone number, etc.; anything really)
281282

requirements/project-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ ail_typo_squatting==2.7.4
8282
iocextract==1.16.1
8383
ioc-finder==7.0.0
8484
polyswarm-api==3.8.0
85+
knock-subdomains==7.0.1
8586

8687
# this is required because XLMMacroDeobfuscator does not pin the following packages
8788
pyxlsb2==0.0.8

0 commit comments

Comments
 (0)