secator 0.1.0__py2.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.

Files changed (99) hide show
  1. secator/.gitignore +162 -0
  2. secator/__init__.py +0 -0
  3. secator/celery.py +421 -0
  4. secator/cli.py +927 -0
  5. secator/config.py +137 -0
  6. secator/configs/__init__.py +0 -0
  7. secator/configs/profiles/__init__.py +0 -0
  8. secator/configs/profiles/aggressive.yaml +7 -0
  9. secator/configs/profiles/default.yaml +9 -0
  10. secator/configs/profiles/stealth.yaml +7 -0
  11. secator/configs/scans/__init__.py +0 -0
  12. secator/configs/scans/domain.yaml +18 -0
  13. secator/configs/scans/host.yaml +14 -0
  14. secator/configs/scans/network.yaml +17 -0
  15. secator/configs/scans/subdomain.yaml +8 -0
  16. secator/configs/scans/url.yaml +12 -0
  17. secator/configs/workflows/__init__.py +0 -0
  18. secator/configs/workflows/cidr_recon.yaml +28 -0
  19. secator/configs/workflows/code_scan.yaml +11 -0
  20. secator/configs/workflows/host_recon.yaml +41 -0
  21. secator/configs/workflows/port_scan.yaml +34 -0
  22. secator/configs/workflows/subdomain_recon.yaml +33 -0
  23. secator/configs/workflows/url_crawl.yaml +29 -0
  24. secator/configs/workflows/url_dirsearch.yaml +29 -0
  25. secator/configs/workflows/url_fuzz.yaml +35 -0
  26. secator/configs/workflows/url_nuclei.yaml +11 -0
  27. secator/configs/workflows/url_vuln.yaml +55 -0
  28. secator/configs/workflows/user_hunt.yaml +10 -0
  29. secator/configs/workflows/wordpress.yaml +14 -0
  30. secator/decorators.py +346 -0
  31. secator/definitions.py +183 -0
  32. secator/exporters/__init__.py +12 -0
  33. secator/exporters/_base.py +3 -0
  34. secator/exporters/csv.py +29 -0
  35. secator/exporters/gdrive.py +118 -0
  36. secator/exporters/json.py +14 -0
  37. secator/exporters/table.py +7 -0
  38. secator/exporters/txt.py +24 -0
  39. secator/hooks/__init__.py +0 -0
  40. secator/hooks/mongodb.py +212 -0
  41. secator/output_types/__init__.py +24 -0
  42. secator/output_types/_base.py +95 -0
  43. secator/output_types/exploit.py +50 -0
  44. secator/output_types/ip.py +33 -0
  45. secator/output_types/port.py +45 -0
  46. secator/output_types/progress.py +35 -0
  47. secator/output_types/record.py +34 -0
  48. secator/output_types/subdomain.py +42 -0
  49. secator/output_types/tag.py +46 -0
  50. secator/output_types/target.py +30 -0
  51. secator/output_types/url.py +76 -0
  52. secator/output_types/user_account.py +41 -0
  53. secator/output_types/vulnerability.py +97 -0
  54. secator/report.py +95 -0
  55. secator/rich.py +123 -0
  56. secator/runners/__init__.py +12 -0
  57. secator/runners/_base.py +873 -0
  58. secator/runners/_helpers.py +154 -0
  59. secator/runners/command.py +674 -0
  60. secator/runners/scan.py +67 -0
  61. secator/runners/task.py +107 -0
  62. secator/runners/workflow.py +137 -0
  63. secator/serializers/__init__.py +8 -0
  64. secator/serializers/dataclass.py +33 -0
  65. secator/serializers/json.py +15 -0
  66. secator/serializers/regex.py +17 -0
  67. secator/tasks/__init__.py +10 -0
  68. secator/tasks/_categories.py +304 -0
  69. secator/tasks/cariddi.py +102 -0
  70. secator/tasks/dalfox.py +66 -0
  71. secator/tasks/dirsearch.py +88 -0
  72. secator/tasks/dnsx.py +56 -0
  73. secator/tasks/dnsxbrute.py +34 -0
  74. secator/tasks/feroxbuster.py +89 -0
  75. secator/tasks/ffuf.py +85 -0
  76. secator/tasks/fping.py +44 -0
  77. secator/tasks/gau.py +43 -0
  78. secator/tasks/gf.py +34 -0
  79. secator/tasks/gospider.py +71 -0
  80. secator/tasks/grype.py +78 -0
  81. secator/tasks/h8mail.py +80 -0
  82. secator/tasks/httpx.py +104 -0
  83. secator/tasks/katana.py +128 -0
  84. secator/tasks/maigret.py +78 -0
  85. secator/tasks/mapcidr.py +32 -0
  86. secator/tasks/msfconsole.py +176 -0
  87. secator/tasks/naabu.py +52 -0
  88. secator/tasks/nmap.py +341 -0
  89. secator/tasks/nuclei.py +97 -0
  90. secator/tasks/searchsploit.py +53 -0
  91. secator/tasks/subfinder.py +40 -0
  92. secator/tasks/wpscan.py +177 -0
  93. secator/utils.py +404 -0
  94. secator/utils_test.py +183 -0
  95. secator-0.1.0.dist-info/METADATA +379 -0
  96. secator-0.1.0.dist-info/RECORD +99 -0
  97. secator-0.1.0.dist-info/WHEEL +5 -0
  98. secator-0.1.0.dist-info/entry_points.txt +2 -0
  99. secator-0.1.0.dist-info/licenses/LICENSE +60 -0
@@ -0,0 +1,30 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+
4
+ from secator.output_types import OutputType
5
+ from secator.utils import rich_to_ansi
6
+
7
+
8
+ @dataclass
9
+ class Target(OutputType):
10
+ name: str
11
+ _source: str = field(default='', repr=True)
12
+ _type: str = field(default='target', repr=True)
13
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
14
+ _uuid: str = field(default='', repr=True, compare=False)
15
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
16
+ _tagged: bool = field(default=False, repr=True, compare=False)
17
+ _duplicate: bool = field(default=False, repr=True, compare=False)
18
+ _related: list = field(default_factory=list, compare=False)
19
+
20
+ _table_fields = [
21
+ 'name',
22
+ ]
23
+ _sort_by = ('name',)
24
+
25
+ def __str__(self):
26
+ return self.name
27
+
28
+ def __repr__(self):
29
+ s = f'🎯 {self.name}'
30
+ return rich_to_ansi(s)
@@ -0,0 +1,76 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+
4
+ from secator.definitions import (CONTENT_LENGTH, CONTENT_TYPE, STATUS_CODE,
5
+ TECH, TIME, TITLE, URL, WEBSERVER)
6
+ from secator.output_types import OutputType
7
+ from secator.utils import rich_to_ansi
8
+
9
+
10
+ @dataclass
11
+ class Url(OutputType):
12
+ url: str
13
+ host: str = field(default='', compare=False)
14
+ status_code: int = field(default=0, compare=False)
15
+ title: str = field(default='', compare=False)
16
+ webserver: str = field(default='', compare=False)
17
+ tech: list = field(default_factory=list, compare=False)
18
+ content_type: str = field(default='', compare=False)
19
+ content_length: int = field(default=0, compare=False)
20
+ time: str = field(default='', compare=False)
21
+ method: str = field(default='', compare=False)
22
+ words: int = field(default=0, compare=False)
23
+ lines: int = field(default=0, compare=False)
24
+ screenshot_path: str = field(default='', compare=False)
25
+ stored_response_path: str = field(default='', compare=False)
26
+ _source: str = field(default='', repr=True, compare=False)
27
+ _type: str = field(default='url', repr=True)
28
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
29
+ _uuid: str = field(default='', repr=True, compare=False)
30
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
31
+ _tagged: bool = field(default=False, repr=True, compare=False)
32
+ _duplicate: bool = field(default=False, repr=True, compare=False)
33
+ _related: list = field(default_factory=list, compare=False)
34
+
35
+ _table_fields = [
36
+ URL,
37
+ STATUS_CODE,
38
+ TITLE,
39
+ WEBSERVER,
40
+ TECH,
41
+ CONTENT_TYPE,
42
+ CONTENT_LENGTH,
43
+ TIME
44
+ ]
45
+ _sort_by = (URL,)
46
+
47
+ def __gt__(self, other):
48
+ # favor httpx over other url info tools
49
+ if self._source == 'httpx' and other._source != 'httpx':
50
+ return True
51
+ return super().__gt__(other)
52
+
53
+ def __str__(self):
54
+ return self.url
55
+
56
+ def __repr__(self):
57
+ s = f'🔗 [white]{self.url}'
58
+ if self.status_code and self.status_code != 0:
59
+ if self.status_code < 400:
60
+ s += f' \[[green]{self.status_code}[/]]'
61
+ else:
62
+ s += f' \[[red]{self.status_code}[/]]'
63
+ if self.title:
64
+ s += f' \[[green]{self.title}[/]]'
65
+ if self.webserver:
66
+ s += f' \[[magenta]{self.webserver}[/]]'
67
+ if self.tech:
68
+ techs_str = ', '.join([f'[magenta]{tech}[/]' for tech in self.tech])
69
+ s += f' [{techs_str}]'
70
+ if self.content_type:
71
+ s += f' \[[magenta]{self.content_type}[/]]'
72
+ if self.content_length:
73
+ s += f' \[[magenta]{self.content_length}[/]]'
74
+ if self.screenshot_path:
75
+ s += f' \[[magenta]{self.screenshot_path}[/]]'
76
+ return rich_to_ansi(s)
@@ -0,0 +1,41 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+
4
+ from secator.definitions import SITE_NAME, URL, USERNAME
5
+ from secator.output_types import OutputType
6
+ from secator.utils import rich_to_ansi
7
+
8
+
9
+ @dataclass
10
+ class UserAccount(OutputType):
11
+ username: str
12
+ url: str = ''
13
+ email: str = ''
14
+ site_name: str = ''
15
+ extra_data: dict = field(default_factory=dict, compare=False)
16
+ _source: str = field(default='', repr=True)
17
+ _type: str = field(default='user_account', repr=True)
18
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
19
+ _uuid: str = field(default='', repr=True, compare=False)
20
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
21
+ _tagged: bool = field(default=False, repr=True, compare=False)
22
+ _duplicate: bool = field(default=False, repr=True, compare=False)
23
+ _related: list = field(default_factory=list, compare=False)
24
+
25
+ _table_fields = [SITE_NAME, USERNAME, URL]
26
+ _sort_by = (URL, USERNAME)
27
+
28
+ def __str__(self) -> str:
29
+ return self.url
30
+
31
+ def __repr__(self) -> str:
32
+ s = f'👤 [green]{self.username}[/]'
33
+ if self.email:
34
+ s += f' \[[bold yellow]{self.email}[/]]'
35
+ if self.site_name:
36
+ s += f' \[[bold blue]{self.site_name}[/]]'
37
+ if self.url:
38
+ s += f' \[[white]{self.url}[/]]'
39
+ if self.extra_data:
40
+ s += ' \[[bold yellow]' + ', '.join(f'{k}:{v}' for k, v in self.extra_data.items()) + '[/]]'
41
+ return rich_to_ansi(s)
@@ -0,0 +1,97 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from typing import List
4
+
5
+ from secator.definitions import (CONFIDENCE, CVSS_SCORE, EXTRA_DATA, ID,
6
+ MATCHED_AT, NAME, REFERENCE, SEVERITY, TAGS)
7
+ from secator.output_types import OutputType
8
+ from secator.utils import rich_to_ansi
9
+
10
+
11
+ @dataclass
12
+ class Vulnerability(OutputType):
13
+ name: str
14
+ provider: str = ''
15
+ id: str = ''
16
+ matched_at: str = ''
17
+ ip: str = field(default='', compare=False)
18
+ confidence: int = 'low'
19
+ severity: str = 'unknown'
20
+ cvss_score: float = 0
21
+ tags: List[str] = field(default_factory=list)
22
+ extra_data: dict = field(default_factory=dict, compare=False)
23
+ description: str = field(default='', compare=False)
24
+ references: List[str] = field(default_factory=list, compare=False)
25
+ reference: str = field(default='', compare=False)
26
+ confidence_nb: int = 0
27
+ severity_nb: int = 0
28
+ _source: str = field(default='', repr=True)
29
+ _type: str = field(default='vulnerability', repr=True)
30
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
31
+ _uuid: str = field(default='', repr=True, compare=False)
32
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
33
+ _tagged: bool = field(default=False, repr=True, compare=False)
34
+ _duplicate: bool = field(default=False, repr=True, compare=False)
35
+ _related: list = field(default_factory=list, compare=False)
36
+
37
+ _table_fields = [
38
+ MATCHED_AT,
39
+ SEVERITY,
40
+ CONFIDENCE,
41
+ NAME,
42
+ ID,
43
+ CVSS_SCORE,
44
+ TAGS,
45
+ EXTRA_DATA,
46
+ REFERENCE
47
+ ]
48
+ _sort_by = ('confidence_nb', 'severity_nb', 'matched_at', 'cvss_score')
49
+
50
+ def __post_init__(self):
51
+ super().__post_init__()
52
+ severity_map = {
53
+ 'critical': 0,
54
+ 'high': 1,
55
+ 'medium': 2,
56
+ 'low': 3,
57
+ 'info': 4,
58
+ 'unknown': 5,
59
+ None: 6
60
+ }
61
+ self.severity_nb = severity_map[self.severity]
62
+ self.confidence_nb = severity_map[self.confidence]
63
+ if len(self.references) > 0:
64
+ self.reference = self.references[0]
65
+
66
+ def __repr__(self):
67
+ data = self.extra_data
68
+ if 'data' in data and isinstance(data['data'], list):
69
+ data = ','.join(data['data'])
70
+ elif isinstance(data, dict):
71
+ data = ', '.join([f'{k}:{v}' for k, v in data.items()])
72
+ tags = self.tags
73
+ colors = {
74
+ 'critical': 'bold red',
75
+ 'high': 'red',
76
+ 'medium': 'yellow',
77
+ 'low': 'green',
78
+ 'info': 'magenta',
79
+ 'unknown': 'dim magenta'
80
+ }
81
+ c = colors[self.severity]
82
+ s = f'🚨 \[[green]{self.name} [link={self.reference}]🡕[/link][/]] \[[{c}]{self.severity}[/]] {self.matched_at}'
83
+ if tags:
84
+ tags_str = ','.join(tags)
85
+ s += f' \[[cyan]{tags_str}[/]]'
86
+ if data:
87
+ s += f' \[[yellow]{str(data)}[/]]'
88
+ return rich_to_ansi(s)
89
+
90
+ # def __gt__(self, other):
91
+ # # favor httpx over other url info tools
92
+ # if self._source == 'httpx' and other._source != 'httpx':
93
+ # return True
94
+ # return super().__gt__(other)
95
+
96
+ def __str__(self):
97
+ return self.matched_at + ' -> ' + self.name
secator/report.py ADDED
@@ -0,0 +1,95 @@
1
+ import operator
2
+
3
+ from secator.output_types import OUTPUT_TYPES, OutputType
4
+ from secator.utils import merge_opts, get_file_timestamp, print_results_table
5
+ from secator.rich import console
6
+
7
+
8
+ # TODO: initialize from data, not from runner
9
+ class Report:
10
+ """Report class.
11
+
12
+ Args:
13
+ runner (secator.runners.Runner): Runner instance.
14
+ title (str): Report title.
15
+ exporters (list): List of exporter classes.
16
+ """
17
+ def __init__(self, runner, title=None, exporters=[]):
18
+ self.title = title or f'{runner.__class__.__name__.lower()}_{runner.config.name}'
19
+ self.runner = runner
20
+ self.timestamp = get_file_timestamp()
21
+ self.exporters = exporters
22
+ self.workspace_name = runner.workspace_name
23
+ self.output_folder = runner.reports_folder
24
+
25
+ def as_table(self):
26
+ print_results_table(self.results, self.title)
27
+
28
+ def send(self):
29
+ for report_cls in self.exporters:
30
+ try:
31
+ report_cls(self).send()
32
+ except Exception as e:
33
+ console.print(
34
+ f'Could not create exporter {report_cls.__name__} for {self.__class__.__name__}: {str(e)}',
35
+ style='bold red')
36
+
37
+ def build(self):
38
+ # Trim options
39
+ from secator.decorators import DEFAULT_CLI_OPTIONS
40
+ opts = merge_opts(self.runner.config.options, self.runner.run_opts)
41
+ opts = {
42
+ k: v for k, v in opts.items()
43
+ if k not in DEFAULT_CLI_OPTIONS
44
+ and not k.startswith('print_')
45
+ and v is not None
46
+ }
47
+
48
+ # Prepare report structure
49
+ data = {
50
+ 'info': {
51
+ 'title': self.title,
52
+ 'runner': self.runner.__class__.__name__,
53
+ 'name': self.runner.config.name,
54
+ 'targets': self.runner.targets,
55
+ 'total_time': str(self.runner.elapsed),
56
+ 'total_human': self.runner.elapsed_human,
57
+ 'opts': opts,
58
+ },
59
+ 'results': {},
60
+ }
61
+
62
+ # Fill report
63
+ for output_type in OUTPUT_TYPES:
64
+ if output_type.__name__ == 'Progress':
65
+ continue
66
+ output_name = output_type.get_name()
67
+ sort_by, _ = get_table_fields(output_type)
68
+ items = [
69
+ item for item in self.runner.results
70
+ if isinstance(item, OutputType) and item._type == output_name and not item._duplicate
71
+ ]
72
+ if items:
73
+ if sort_by and all(sort_by):
74
+ items = sorted(items, key=operator.attrgetter(*sort_by))
75
+ data['results'][output_name] = items
76
+
77
+ # Save data
78
+ self.data = data
79
+
80
+
81
+ def get_table_fields(output_type):
82
+ """Get output fields and sort fields based on output type.
83
+
84
+ Args:
85
+ output_type (str): Output type.
86
+
87
+ Returns:
88
+ tuple: Tuple of sort_by (tuple), output_fields (list).
89
+ """
90
+ sort_by = ()
91
+ output_fields = []
92
+ if output_type in OUTPUT_TYPES:
93
+ sort_by = output_type._sort_by
94
+ output_fields = output_type._table_fields
95
+ return sort_by, output_fields
secator/rich.py ADDED
@@ -0,0 +1,123 @@
1
+ import operator
2
+
3
+ import click
4
+ import rich_click
5
+ import yaml
6
+ from rich import box
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ from rich.traceback import install
10
+
11
+ from secator.definitions import DEBUG, RECORD
12
+
13
+ console = Console(stderr=True, record=RECORD, color_system='truecolor')
14
+ console_stdout = Console(record=True)
15
+ # handler = RichHandler(rich_tracebacks=True) # TODO: add logging handler
16
+ install(show_locals=DEBUG > 2, suppress=[click, rich_click])
17
+
18
+
19
+ def criticity_to_color(value):
20
+ if value == 'critical':
21
+ value = f'[bold red3]{value.upper()}[/]'
22
+ elif value == 'high':
23
+ value = f'[bold orange_red1]{value.upper()}[/]'
24
+ elif value == 'medium':
25
+ value = f'[bold dark_orange]{value.upper()}[/]'
26
+ elif value == 'low':
27
+ value = f'[bold yellow1]{value.upper()}[/]'
28
+ elif value == 'info':
29
+ value = f'[bold green]{value.upper()}[/]'
30
+ return value
31
+
32
+
33
+ def status_to_color(value):
34
+ value = int(value) if value else None
35
+ if value is None:
36
+ return value
37
+ if value < 400:
38
+ value = f'[bold green]{value}[/]'
39
+ elif value in [400, 499]:
40
+ value = f'[bold dark_orange]{value}[/]'
41
+ elif value >= 500:
42
+ value = f'[bold red3]{value}[/]'
43
+ return value
44
+
45
+
46
+ FORMATTERS = {
47
+ 'confidence': criticity_to_color,
48
+ 'severity': criticity_to_color,
49
+ 'cvss_score': lambda score: '' if score == -1 else f'[bold cyan]{score}[/]',
50
+ 'port': lambda port: f'[bold cyan]{port}[/]',
51
+ 'url': lambda host: f'[bold underline blue]{host}[/]',
52
+ 'ip': lambda ip: f'[bold yellow]{ip}[/]',
53
+ 'status_code': status_to_color,
54
+ 'reference': lambda reference: f'[link={reference}]🡕[/]',
55
+ '_source': lambda source: f'[bold gold3]{source}[/]'
56
+ }
57
+
58
+
59
+ def build_table(items, output_fields=[], exclude_fields=[], sort_by=None):
60
+ """Build rich table.
61
+
62
+ Args:
63
+ items (list): List of items.
64
+ output_fields (list, Optional): List of fields to add.
65
+ exclude_fields (list, Optional): List of fields to exclude.
66
+ sort_by (tuple, Optional): Tuple of sort_by keys.
67
+
68
+ Returns:
69
+ rich.table.Table: rich table.
70
+ """
71
+ # Sort items by one or multiple fields
72
+ if sort_by and all(sort_by):
73
+ items = sorted(items, key=operator.attrgetter(*sort_by))
74
+
75
+ # Create rich table
76
+ box_style = box.DOUBLE if RECORD else box.ROUNDED
77
+ table = Table(show_lines=True, box=box_style)
78
+
79
+ # Get table schema if any, default to first item keys
80
+ keys = output_fields
81
+
82
+ # List of fields to exclude
83
+ keys = [k for k in keys if k not in exclude_fields]
84
+
85
+ # Remove meta fields not needed in output
86
+ if '_cls' in keys:
87
+ keys.remove('_cls')
88
+ if '_type' in keys:
89
+ keys.remove('_type')
90
+ if '_uuid' in keys:
91
+ keys.remove('_uuid')
92
+
93
+ # Add _source field
94
+ if '_source' not in keys:
95
+ keys.append('_source')
96
+
97
+ # Create table columns
98
+ for key in keys:
99
+ key_str = key
100
+ if not key.startswith('_'):
101
+ key_str = ' '.join(key.split('_')).upper()
102
+ no_wrap = key in ['url', 'reference', 'references', 'matched_at']
103
+ overflow = None if no_wrap else 'fold'
104
+ table.add_column(
105
+ key_str,
106
+ overflow=overflow,
107
+ min_width=10,
108
+ no_wrap=no_wrap,
109
+ header_style='bold blue')
110
+
111
+ # Create table rows
112
+ for item in items:
113
+ values = []
114
+ for key in keys:
115
+ value = getattr(item, key)
116
+ value = FORMATTERS.get(key, lambda x: x)(value)
117
+ if isinstance(value, dict) or isinstance(value, list):
118
+ value = yaml.dump(value)
119
+ elif isinstance(value, int) or isinstance(value, float):
120
+ value = str(value)
121
+ values.append(value)
122
+ table.add_row(*values)
123
+ return table
@@ -0,0 +1,12 @@
1
+ __all__ = [
2
+ 'Runner',
3
+ 'Command',
4
+ 'Task',
5
+ 'Workflow',
6
+ 'Scan',
7
+ ]
8
+ from secator.runners._base import Runner
9
+ from secator.runners.command import Command
10
+ from secator.runners.task import Task
11
+ from secator.runners.scan import Scan
12
+ from secator.runners.workflow import Workflow