secator 0.15.0__py3-none-any.whl → 0.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secator might be problematic. Click here for more details.
- secator/celery.py +40 -24
- secator/celery_signals.py +71 -68
- secator/celery_utils.py +43 -27
- secator/cli.py +520 -280
- secator/cli_helper.py +394 -0
- secator/click.py +87 -0
- secator/config.py +67 -39
- secator/configs/profiles/http_headless.yaml +6 -0
- secator/configs/profiles/http_record.yaml +6 -0
- secator/configs/profiles/tor.yaml +1 -1
- secator/configs/scans/domain.yaml +4 -2
- secator/configs/scans/host.yaml +1 -1
- secator/configs/scans/network.yaml +1 -4
- secator/configs/scans/subdomain.yaml +13 -1
- secator/configs/scans/url.yaml +1 -2
- secator/configs/workflows/cidr_recon.yaml +6 -4
- secator/configs/workflows/code_scan.yaml +1 -1
- secator/configs/workflows/host_recon.yaml +29 -3
- secator/configs/workflows/subdomain_recon.yaml +67 -16
- secator/configs/workflows/url_crawl.yaml +44 -15
- secator/configs/workflows/url_dirsearch.yaml +4 -4
- secator/configs/workflows/url_fuzz.yaml +25 -17
- secator/configs/workflows/url_params_fuzz.yaml +7 -0
- secator/configs/workflows/url_vuln.yaml +33 -8
- secator/configs/workflows/user_hunt.yaml +2 -1
- secator/configs/workflows/wordpress.yaml +5 -3
- secator/cve.py +718 -0
- secator/decorators.py +0 -454
- secator/definitions.py +49 -30
- secator/exporters/_base.py +2 -2
- secator/exporters/console.py +2 -2
- secator/exporters/table.py +4 -3
- secator/exporters/txt.py +1 -1
- secator/hooks/mongodb.py +2 -4
- secator/installer.py +77 -49
- secator/loader.py +116 -0
- secator/output_types/_base.py +3 -0
- secator/output_types/certificate.py +63 -63
- secator/output_types/error.py +4 -5
- secator/output_types/info.py +2 -2
- secator/output_types/ip.py +3 -1
- secator/output_types/progress.py +5 -9
- secator/output_types/state.py +17 -17
- secator/output_types/tag.py +3 -0
- secator/output_types/target.py +10 -2
- secator/output_types/url.py +19 -7
- secator/output_types/vulnerability.py +11 -7
- secator/output_types/warning.py +2 -2
- secator/report.py +27 -15
- secator/rich.py +18 -10
- secator/runners/_base.py +447 -234
- secator/runners/_helpers.py +133 -24
- secator/runners/command.py +182 -102
- secator/runners/scan.py +33 -5
- secator/runners/task.py +13 -7
- secator/runners/workflow.py +105 -72
- secator/scans/__init__.py +2 -2
- secator/serializers/dataclass.py +20 -20
- secator/tasks/__init__.py +4 -4
- secator/tasks/_categories.py +39 -27
- secator/tasks/arjun.py +9 -5
- secator/tasks/bbot.py +53 -21
- secator/tasks/bup.py +19 -5
- secator/tasks/cariddi.py +24 -3
- secator/tasks/dalfox.py +26 -7
- secator/tasks/dirsearch.py +10 -4
- secator/tasks/dnsx.py +70 -25
- secator/tasks/feroxbuster.py +11 -3
- secator/tasks/ffuf.py +42 -6
- secator/tasks/fping.py +20 -8
- secator/tasks/gau.py +3 -1
- secator/tasks/gf.py +5 -4
- secator/tasks/gitleaks.py +2 -2
- secator/tasks/gospider.py +7 -1
- secator/tasks/grype.py +5 -4
- secator/tasks/h8mail.py +2 -1
- secator/tasks/httpx.py +18 -5
- secator/tasks/katana.py +35 -15
- secator/tasks/maigret.py +4 -4
- secator/tasks/mapcidr.py +3 -3
- secator/tasks/msfconsole.py +4 -4
- secator/tasks/naabu.py +5 -4
- secator/tasks/nmap.py +12 -14
- secator/tasks/nuclei.py +3 -3
- secator/tasks/searchsploit.py +6 -5
- secator/tasks/subfinder.py +2 -2
- secator/tasks/testssl.py +264 -263
- secator/tasks/trivy.py +5 -5
- secator/tasks/wafw00f.py +21 -3
- secator/tasks/wpprobe.py +90 -83
- secator/tasks/wpscan.py +6 -5
- secator/template.py +218 -104
- secator/thread.py +15 -15
- secator/tree.py +196 -0
- secator/utils.py +131 -123
- secator/utils_test.py +60 -19
- secator/workflows/__init__.py +2 -2
- {secator-0.15.0.dist-info → secator-0.16.0.dist-info}/METADATA +37 -36
- secator-0.16.0.dist-info/RECORD +132 -0
- secator/configs/profiles/default.yaml +0 -8
- secator/configs/workflows/url_nuclei.yaml +0 -11
- secator/tasks/dnsxbrute.py +0 -42
- secator-0.15.0.dist-info/RECORD +0 -128
- {secator-0.15.0.dist-info → secator-0.16.0.dist-info}/WHEEL +0 -0
- {secator-0.15.0.dist-info → secator-0.16.0.dist-info}/entry_points.txt +0 -0
- {secator-0.15.0.dist-info → secator-0.16.0.dist-info}/licenses/LICENSE +0 -0
secator/output_types/target.py
CHANGED
|
@@ -2,12 +2,13 @@ import time
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
|
|
4
4
|
from secator.output_types import OutputType
|
|
5
|
-
from secator.utils import rich_to_ansi, rich_escape as _s
|
|
5
|
+
from secator.utils import autodetect_type, rich_to_ansi, rich_escape as _s
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
9
|
class Target(OutputType):
|
|
10
10
|
name: str
|
|
11
|
+
type: str = ''
|
|
11
12
|
_source: str = field(default='', repr=True)
|
|
12
13
|
_type: str = field(default='target', repr=True)
|
|
13
14
|
_timestamp: int = field(default_factory=lambda: time.time(), compare=False)
|
|
@@ -19,12 +20,19 @@ class Target(OutputType):
|
|
|
19
20
|
|
|
20
21
|
_table_fields = [
|
|
21
22
|
'name',
|
|
23
|
+
'type',
|
|
22
24
|
]
|
|
23
|
-
_sort_by = ('name'
|
|
25
|
+
_sort_by = ('type', 'name')
|
|
26
|
+
|
|
27
|
+
def __post_init__(self):
|
|
28
|
+
if not self.type:
|
|
29
|
+
self.type = autodetect_type(self.name)
|
|
24
30
|
|
|
25
31
|
def __str__(self):
|
|
26
32
|
return self.name
|
|
27
33
|
|
|
28
34
|
def __repr__(self):
|
|
29
35
|
s = f'🎯 {_s(self.name)}'
|
|
36
|
+
if self.type:
|
|
37
|
+
s += f' ({self.type})'
|
|
30
38
|
return rich_to_ansi(s)
|
secator/output_types/url.py
CHANGED
|
@@ -2,9 +2,9 @@ import time
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
|
|
4
4
|
from secator.definitions import (CONTENT_LENGTH, CONTENT_TYPE, STATUS_CODE,
|
|
5
|
-
TECH,
|
|
5
|
+
TECH, TITLE, URL, WEBSERVER, METHOD)
|
|
6
6
|
from secator.output_types import OutputType
|
|
7
|
-
from secator.utils import rich_to_ansi, trim_string, rich_escape as _s
|
|
7
|
+
from secator.utils import rich_to_ansi, trim_string, format_object, rich_escape as _s
|
|
8
8
|
from secator.config import CONFIG
|
|
9
9
|
|
|
10
10
|
|
|
@@ -24,7 +24,9 @@ class Url(OutputType):
|
|
|
24
24
|
lines: int = field(default=0, compare=False)
|
|
25
25
|
screenshot_path: str = field(default='', compare=False)
|
|
26
26
|
stored_response_path: str = field(default='', compare=False)
|
|
27
|
-
|
|
27
|
+
response_headers: dict = field(default_factory=dict, repr=True, compare=False)
|
|
28
|
+
request_headers: dict = field(default_factory=dict, repr=True, compare=False)
|
|
29
|
+
extra_data: dict = field(default_factory=dict, compare=False)
|
|
28
30
|
_source: str = field(default='', repr=True, compare=False)
|
|
29
31
|
_type: str = field(default='url', repr=True)
|
|
30
32
|
_timestamp: int = field(default_factory=lambda: time.time(), compare=False)
|
|
@@ -36,13 +38,15 @@ class Url(OutputType):
|
|
|
36
38
|
|
|
37
39
|
_table_fields = [
|
|
38
40
|
URL,
|
|
41
|
+
METHOD,
|
|
39
42
|
STATUS_CODE,
|
|
40
43
|
TITLE,
|
|
41
44
|
WEBSERVER,
|
|
42
45
|
TECH,
|
|
43
46
|
CONTENT_TYPE,
|
|
44
47
|
CONTENT_LENGTH,
|
|
45
|
-
|
|
48
|
+
'stored_response_path',
|
|
49
|
+
'screenshot_path',
|
|
46
50
|
]
|
|
47
51
|
_sort_by = (URL,)
|
|
48
52
|
|
|
@@ -59,15 +63,17 @@ class Url(OutputType):
|
|
|
59
63
|
s = f'🔗 [white]{_s(self.url)}'
|
|
60
64
|
if self.method and self.method != 'GET':
|
|
61
65
|
s += rf' \[[turquoise4]{self.method}[/]]'
|
|
66
|
+
if self.request_headers:
|
|
67
|
+
s += rf'{format_object(self.request_headers, "gold3", skip_keys=["user_agent"])}'
|
|
62
68
|
if self.status_code and self.status_code != 0:
|
|
63
69
|
if self.status_code < 400:
|
|
64
70
|
s += rf' \[[green]{self.status_code}[/]]'
|
|
65
71
|
else:
|
|
66
72
|
s += rf' \[[red]{self.status_code}[/]]'
|
|
67
73
|
if self.title:
|
|
68
|
-
s += rf' \[[
|
|
74
|
+
s += rf' \[[spring_green3]{trim_string(self.title)}[/]]'
|
|
69
75
|
if self.webserver:
|
|
70
|
-
s += rf' \[[magenta]{_s(self.webserver)}[/]]'
|
|
76
|
+
s += rf' \[[bold magenta]{_s(self.webserver)}[/]]'
|
|
71
77
|
if self.tech:
|
|
72
78
|
techs_str = ', '.join([f'[magenta]{_s(tech)}[/]' for tech in self.tech])
|
|
73
79
|
s += f' [{techs_str}]'
|
|
@@ -77,6 +83,12 @@ class Url(OutputType):
|
|
|
77
83
|
cl = str(self.content_length)
|
|
78
84
|
cl += '[bold red]+[/]' if self.content_length == CONFIG.http.response_max_size_bytes else ''
|
|
79
85
|
s += rf' \[[magenta]{cl}[/]]'
|
|
86
|
+
if self.response_headers and CONFIG.cli.show_http_response_headers:
|
|
87
|
+
s += rf'{format_object(self.response_headers, "magenta", skip_keys=CONFIG.cli.exclude_http_response_headers)}' # noqa: E501
|
|
88
|
+
if self.extra_data:
|
|
89
|
+
s += format_object(self.extra_data, 'yellow')
|
|
80
90
|
if self.screenshot_path:
|
|
81
|
-
s += rf'
|
|
91
|
+
s += rf' [link=file://{self.screenshot_path}]:camera:[/]'
|
|
92
|
+
if self.stored_response_path:
|
|
93
|
+
s += rf' [link=file://{self.stored_response_path}]:pencil:[/]'
|
|
82
94
|
return rich_to_ansi(s)
|
|
@@ -5,7 +5,7 @@ from typing import List
|
|
|
5
5
|
from secator.definitions import (CONFIDENCE, CVSS_SCORE, EXTRA_DATA, ID,
|
|
6
6
|
MATCHED_AT, NAME, REFERENCE, SEVERITY, TAGS)
|
|
7
7
|
from secator.output_types import OutputType
|
|
8
|
-
from secator.utils import rich_to_ansi, rich_escape as _s
|
|
8
|
+
from secator.utils import rich_to_ansi, rich_escape as _s, format_object
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -66,11 +66,11 @@ class Vulnerability(OutputType):
|
|
|
66
66
|
|
|
67
67
|
def __repr__(self):
|
|
68
68
|
data = self.extra_data
|
|
69
|
+
|
|
70
|
+
# TODO: review this
|
|
69
71
|
if 'data' in data and isinstance(data['data'], list):
|
|
70
|
-
data =
|
|
71
|
-
|
|
72
|
-
data = ', '.join([f'{k}:{v}' for k, v in data.items()])
|
|
73
|
-
data = _s(data)
|
|
72
|
+
data = data['data']
|
|
73
|
+
|
|
74
74
|
tags = self.tags
|
|
75
75
|
colors = {
|
|
76
76
|
'critical': 'bold red',
|
|
@@ -81,12 +81,16 @@ class Vulnerability(OutputType):
|
|
|
81
81
|
'unknown': 'dim magenta'
|
|
82
82
|
}
|
|
83
83
|
c = colors.get(self.severity, 'dim magenta')
|
|
84
|
-
|
|
84
|
+
name = self.name
|
|
85
|
+
if self.reference:
|
|
86
|
+
name += rf' [link={_s(self.reference)}]🡕[/link]'
|
|
87
|
+
s = rf'🚨 \[[green]{name}[/]]'
|
|
88
|
+
s += rf' \[[{c}]{self.severity}[/]] {_s(self.matched_at)}' # noqa: E501
|
|
85
89
|
if tags:
|
|
86
90
|
tags_str = ','.join(tags)
|
|
87
91
|
s += rf' \[[cyan]{_s(tags_str)}[/]]'
|
|
88
92
|
if data:
|
|
89
|
-
s +=
|
|
93
|
+
s += format_object(data, 'yellow')
|
|
90
94
|
if self.confidence == 'low':
|
|
91
95
|
s = f'[dim]{s}[/]'
|
|
92
96
|
return rich_to_ansi(s)
|
secator/output_types/warning.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
import time
|
|
3
3
|
from secator.output_types import OutputType
|
|
4
|
-
from secator.utils import rich_to_ansi
|
|
4
|
+
from secator.utils import rich_to_ansi
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
@dataclass
|
|
@@ -20,5 +20,5 @@ class Warning(OutputType):
|
|
|
20
20
|
_sort_by = ('_timestamp',)
|
|
21
21
|
|
|
22
22
|
def __repr__(self):
|
|
23
|
-
s = rf"\[[yellow]WRN[/]] {
|
|
23
|
+
s = rf"\[[yellow]WRN[/]] {self.message}"
|
|
24
24
|
return rich_to_ansi(s)
|
secator/report.py
CHANGED
|
@@ -2,7 +2,7 @@ import operator
|
|
|
2
2
|
|
|
3
3
|
from secator.config import CONFIG
|
|
4
4
|
from secator.output_types import FINDING_TYPES, OutputType
|
|
5
|
-
from secator.utils import
|
|
5
|
+
from secator.utils import get_file_timestamp, traceback_as_string
|
|
6
6
|
from secator.rich import console
|
|
7
7
|
from secator.runners._helpers import extract_from_results
|
|
8
8
|
|
|
@@ -56,14 +56,7 @@ class Report:
|
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
def build(self, extractors=[], dedupe=CONFIG.runners.remove_duplicates):
|
|
59
|
-
#
|
|
60
|
-
from secator.decorators import DEFAULT_CLI_OPTIONS
|
|
61
|
-
opts = merge_opts(self.runner.config.options, self.runner.run_opts)
|
|
62
|
-
opts = {
|
|
63
|
-
k: v for k, v in opts.items()
|
|
64
|
-
if k not in DEFAULT_CLI_OPTIONS and k not in self.runner.print_opts
|
|
65
|
-
and v is not None
|
|
66
|
-
}
|
|
59
|
+
# Prepare report structure
|
|
67
60
|
runner_fields = {
|
|
68
61
|
'name',
|
|
69
62
|
'status',
|
|
@@ -75,8 +68,6 @@ class Report:
|
|
|
75
68
|
'run_opts',
|
|
76
69
|
'results_count'
|
|
77
70
|
}
|
|
78
|
-
|
|
79
|
-
# Prepare report structure
|
|
80
71
|
data = {
|
|
81
72
|
'info': {k: v for k, v in self.runner.toDict().items() if k in runner_fields},
|
|
82
73
|
'results': {}
|
|
@@ -96,12 +87,33 @@ class Report:
|
|
|
96
87
|
]
|
|
97
88
|
if items:
|
|
98
89
|
if sort_by and all(sort_by):
|
|
99
|
-
|
|
90
|
+
try:
|
|
91
|
+
items = sorted(items, key=operator.attrgetter(*sort_by))
|
|
92
|
+
except TypeError as e:
|
|
93
|
+
console.print(f'[bold red]Could not sort {output_name} by {sort_by}: {str(e)}[/]')
|
|
94
|
+
console.print(f'[dim]{traceback_as_string(e)}[/]')
|
|
100
95
|
if dedupe:
|
|
101
96
|
items = remove_duplicates(items)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
if extractors:
|
|
98
|
+
all_res = []
|
|
99
|
+
extractors_type = [extractor for extractor in extractors if extractor.get('type') == output_name]
|
|
100
|
+
for extractor in extractors_type:
|
|
101
|
+
op = extractor.get('op', 'or')
|
|
102
|
+
res, errors = extract_from_results(items, extractors=[extractor])
|
|
103
|
+
# console.print(f'{extractor} --> {len(res)} results')
|
|
104
|
+
if not res:
|
|
105
|
+
continue
|
|
106
|
+
if errors:
|
|
107
|
+
data['info']['errors'] = errors
|
|
108
|
+
if res:
|
|
109
|
+
if op == 'or':
|
|
110
|
+
all_res = all_res + res
|
|
111
|
+
else:
|
|
112
|
+
if not all_res:
|
|
113
|
+
all_res = res
|
|
114
|
+
else:
|
|
115
|
+
all_res = [item for item in res if item in all_res]
|
|
116
|
+
items = remove_duplicates(all_res) if dedupe else all_res
|
|
105
117
|
data['results'][output_name] = items
|
|
106
118
|
|
|
107
119
|
# Save data
|
secator/rich.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import operator
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import yaml
|
|
4
5
|
from rich.console import Console
|
|
@@ -37,14 +38,18 @@ def status_to_color(value):
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
FORMATTERS = {
|
|
40
|
-
'confidence':
|
|
41
|
+
'confidence': lambda x: f'[dim]{x.upper()}[/]',
|
|
41
42
|
'severity': criticity_to_color,
|
|
42
43
|
'cvss_score': lambda score: '' if score == -1 else f'[bold cyan]{score}[/]',
|
|
43
44
|
'port': lambda port: f'[bold cyan]{port}[/]',
|
|
44
|
-
'url': lambda host: f'[bold underline blue]{host}[/]',
|
|
45
|
+
'url': lambda host: f'[bold underline blue link={host}]{host}[/]',
|
|
46
|
+
'stored_response_path': lambda path: f'[link=file://{path}]:pencil:[/]' if path and Path(path).exists() else '',
|
|
47
|
+
'screenshot_path': lambda path: f'[link=file://{path}]:camera:[/]' if path and Path(path).exists() else '',
|
|
45
48
|
'ip': lambda ip: f'[bold yellow]{ip}[/]',
|
|
46
49
|
'status_code': status_to_color,
|
|
47
|
-
'reference': lambda reference: f'[link={reference}]
|
|
50
|
+
'reference': lambda reference: f'[link={reference}]{reference}[/]' if reference else '',
|
|
51
|
+
'matched_at': lambda matched_at: f'[link={matched_at}]{matched_at}[/]' if matched_at and matched_at.startswith('http') else '', # noqa: E501
|
|
52
|
+
'match': lambda match: f'[link={match}]{match}[/]' if match else '',
|
|
48
53
|
'_source': lambda source: f'[bold gold3]{source}[/]'
|
|
49
54
|
}
|
|
50
55
|
|
|
@@ -89,13 +94,16 @@ def build_table(items, output_fields=[], exclude_fields=[], sort_by=None):
|
|
|
89
94
|
key_str = key
|
|
90
95
|
if not key.startswith('_'):
|
|
91
96
|
key_str = ' '.join(key.split('_')).title()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
# TODO: remove this as it's not needed anymore
|
|
98
|
+
# no_wrap = key in ['url', 'reference', 'references', 'matched_at']
|
|
99
|
+
# overflow = None if no_wrap else 'fold'
|
|
100
|
+
# print('key: ', key_str, 'overflow: ', overflow, 'no_wrap: ', no_wrap)
|
|
101
|
+
# table.add_column(
|
|
102
|
+
# key_str,
|
|
103
|
+
# overflow=overflow,
|
|
104
|
+
# min_width=10,
|
|
105
|
+
# no_wrap=no_wrap)
|
|
106
|
+
table.add_column(key_str)
|
|
99
107
|
|
|
100
108
|
if not keys:
|
|
101
109
|
table.add_column(
|