secator 0.0.1__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 (114) hide show
  1. secator/__init__.py +0 -0
  2. secator/celery.py +482 -0
  3. secator/cli.py +617 -0
  4. secator/config.py +137 -0
  5. secator/configs/__init__.py +0 -0
  6. secator/configs/profiles/__init__.py +0 -0
  7. secator/configs/profiles/aggressive.yaml +7 -0
  8. secator/configs/profiles/default.yaml +9 -0
  9. secator/configs/profiles/stealth.yaml +7 -0
  10. secator/configs/scans/__init__.py +0 -0
  11. secator/configs/scans/domain.yaml +18 -0
  12. secator/configs/scans/host.yaml +14 -0
  13. secator/configs/scans/network.yaml +17 -0
  14. secator/configs/scans/subdomain.yaml +8 -0
  15. secator/configs/scans/url.yaml +12 -0
  16. secator/configs/workflows/__init__.py +0 -0
  17. secator/configs/workflows/cidr_recon.yaml +28 -0
  18. secator/configs/workflows/code_scan.yaml +11 -0
  19. secator/configs/workflows/host_recon.yaml +41 -0
  20. secator/configs/workflows/port_scan.yaml +34 -0
  21. secator/configs/workflows/subdomain_recon.yaml +33 -0
  22. secator/configs/workflows/url_crawl.yaml +29 -0
  23. secator/configs/workflows/url_dirsearch.yaml +29 -0
  24. secator/configs/workflows/url_fuzz.yaml +35 -0
  25. secator/configs/workflows/url_nuclei.yaml +11 -0
  26. secator/configs/workflows/url_vuln.yaml +55 -0
  27. secator/configs/workflows/user_hunt.yaml +10 -0
  28. secator/configs/workflows/wordpress.yaml +14 -0
  29. secator/decorators.py +309 -0
  30. secator/definitions.py +165 -0
  31. secator/exporters/__init__.py +12 -0
  32. secator/exporters/_base.py +3 -0
  33. secator/exporters/csv.py +30 -0
  34. secator/exporters/gdrive.py +118 -0
  35. secator/exporters/json.py +15 -0
  36. secator/exporters/table.py +7 -0
  37. secator/exporters/txt.py +25 -0
  38. secator/hooks/__init__.py +0 -0
  39. secator/hooks/mongodb.py +212 -0
  40. secator/output_types/__init__.py +24 -0
  41. secator/output_types/_base.py +95 -0
  42. secator/output_types/exploit.py +50 -0
  43. secator/output_types/ip.py +33 -0
  44. secator/output_types/port.py +45 -0
  45. secator/output_types/progress.py +35 -0
  46. secator/output_types/record.py +34 -0
  47. secator/output_types/subdomain.py +42 -0
  48. secator/output_types/tag.py +46 -0
  49. secator/output_types/target.py +30 -0
  50. secator/output_types/url.py +76 -0
  51. secator/output_types/user_account.py +41 -0
  52. secator/output_types/vulnerability.py +97 -0
  53. secator/report.py +107 -0
  54. secator/rich.py +124 -0
  55. secator/runners/__init__.py +12 -0
  56. secator/runners/_base.py +833 -0
  57. secator/runners/_helpers.py +153 -0
  58. secator/runners/command.py +638 -0
  59. secator/runners/scan.py +65 -0
  60. secator/runners/task.py +106 -0
  61. secator/runners/workflow.py +135 -0
  62. secator/serializers/__init__.py +8 -0
  63. secator/serializers/dataclass.py +33 -0
  64. secator/serializers/json.py +15 -0
  65. secator/serializers/regex.py +17 -0
  66. secator/tasks/__init__.py +10 -0
  67. secator/tasks/_categories.py +304 -0
  68. secator/tasks/cariddi.py +102 -0
  69. secator/tasks/dalfox.py +65 -0
  70. secator/tasks/dirsearch.py +90 -0
  71. secator/tasks/dnsx.py +56 -0
  72. secator/tasks/dnsxbrute.py +34 -0
  73. secator/tasks/feroxbuster.py +91 -0
  74. secator/tasks/ffuf.py +86 -0
  75. secator/tasks/fping.py +44 -0
  76. secator/tasks/gau.py +47 -0
  77. secator/tasks/gf.py +33 -0
  78. secator/tasks/gospider.py +71 -0
  79. secator/tasks/grype.py +79 -0
  80. secator/tasks/h8mail.py +81 -0
  81. secator/tasks/httpx.py +99 -0
  82. secator/tasks/katana.py +133 -0
  83. secator/tasks/maigret.py +78 -0
  84. secator/tasks/mapcidr.py +32 -0
  85. secator/tasks/msfconsole.py +174 -0
  86. secator/tasks/naabu.py +52 -0
  87. secator/tasks/nmap.py +344 -0
  88. secator/tasks/nuclei.py +97 -0
  89. secator/tasks/searchsploit.py +52 -0
  90. secator/tasks/subfinder.py +40 -0
  91. secator/tasks/wpscan.py +179 -0
  92. secator/utils.py +445 -0
  93. secator/utils_test.py +183 -0
  94. secator-0.0.1.dist-info/LICENSE +60 -0
  95. secator-0.0.1.dist-info/METADATA +199 -0
  96. secator-0.0.1.dist-info/RECORD +114 -0
  97. secator-0.0.1.dist-info/WHEEL +5 -0
  98. secator-0.0.1.dist-info/entry_points.txt +2 -0
  99. secator-0.0.1.dist-info/top_level.txt +2 -0
  100. tests/__init__.py +0 -0
  101. tests/integration/__init__.py +0 -0
  102. tests/integration/inputs.py +42 -0
  103. tests/integration/outputs.py +392 -0
  104. tests/integration/test_scans.py +82 -0
  105. tests/integration/test_tasks.py +103 -0
  106. tests/integration/test_workflows.py +163 -0
  107. tests/performance/__init__.py +0 -0
  108. tests/performance/loadtester.py +56 -0
  109. tests/unit/__init__.py +0 -0
  110. tests/unit/test_celery.py +39 -0
  111. tests/unit/test_scans.py +0 -0
  112. tests/unit/test_serializers.py +51 -0
  113. tests/unit/test_tasks.py +348 -0
  114. tests/unit/test_workflows.py +96 -0
@@ -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,107 @@
1
+ import operator
2
+ import os
3
+ from pathlib import Path
4
+
5
+ from secator.definitions import REPORTS_FOLDER
6
+ from secator.output_types import OUTPUT_TYPES, OutputType
7
+ from secator.utils import merge_opts, pluralize, get_file_timestamp, print_results_table
8
+ from secator.rich import console
9
+
10
+
11
+ # TODO: initialize from data, not from runner
12
+ class Report:
13
+ """Report class.
14
+
15
+ Args:
16
+ runner (secator.runners.Runner): Runner instance.
17
+ title (str): Report title.
18
+ exporters (list): List of exporter classes.
19
+ """
20
+ def __init__(self, runner, title=None, exporters=[]):
21
+ self.title = title or f'{runner.__class__.__name__.lower()}_{runner.config.name}'
22
+ self.runner = runner
23
+ self.timestamp = get_file_timestamp()
24
+ self.exporters = exporters
25
+ self.workspace_name = runner.workspace_name
26
+ self.create_local_folders()
27
+
28
+ def as_table(self):
29
+ print_results_table(self.results, self.title)
30
+
31
+ def send(self):
32
+ for report_cls in self.exporters:
33
+ try:
34
+ report_cls(self).send()
35
+ except Exception as e:
36
+ console.print(
37
+ f'Could not create exporter {report_cls.__name__} for {self.__class__.__name__}: {str(e)}',
38
+ style='bold red')
39
+
40
+ def build(self):
41
+ # Trim options
42
+ from secator.decorators import DEFAULT_CLI_OPTIONS
43
+ opts = merge_opts(self.runner.config.options, self.runner.run_opts)
44
+ opts = {
45
+ k: v for k, v in opts.items()
46
+ if k not in DEFAULT_CLI_OPTIONS
47
+ and not k.startswith('print_')
48
+ and v is not None
49
+ }
50
+
51
+ # Prepare report structure
52
+ data = {
53
+ 'info': {
54
+ 'title': self.title,
55
+ 'runner': self.runner.__class__.__name__,
56
+ 'name': self.runner.config.name,
57
+ 'targets': self.runner.targets,
58
+ 'total_time': str(self.runner.elapsed),
59
+ 'total_human': self.runner.elapsed_human,
60
+ 'opts': opts,
61
+ },
62
+ 'results': {},
63
+ }
64
+
65
+ # Fill report
66
+ for output_type in OUTPUT_TYPES:
67
+ if output_type.__name__ == 'Progress':
68
+ continue
69
+ output_name = output_type.get_name()
70
+ sort_by, _ = get_table_fields(output_type)
71
+ items = [
72
+ item for item in self.runner.results
73
+ if isinstance(item, OutputType) and item._type == output_name and not item._duplicate
74
+ ]
75
+ if items:
76
+ if sort_by and all(sort_by):
77
+ items = sorted(items, key=operator.attrgetter(*sort_by))
78
+ data['results'][output_name] = items
79
+
80
+ # Save data
81
+ self.data = data
82
+
83
+ def create_local_folders(self):
84
+ output_folder = Path(REPORTS_FOLDER)
85
+ if self.runner.workspace_name:
86
+ output_folder = output_folder / Path(self.runner.workspace_name)
87
+ output_folder = output_folder / Path(pluralize(self.runner.__class__.__name__).lower())
88
+ output_folder = str(output_folder)
89
+ os.makedirs(output_folder, exist_ok=True)
90
+ self.output_folder = output_folder
91
+
92
+
93
+ def get_table_fields(output_type):
94
+ """Get output fields and sort fields based on output type.
95
+
96
+ Args:
97
+ output_type (str): Output type.
98
+
99
+ Returns:
100
+ tuple: Tuple of sort_by (tuple), output_fields (list).
101
+ """
102
+ sort_by = ()
103
+ output_fields = []
104
+ if output_type in OUTPUT_TYPES:
105
+ sort_by = output_type._sort_by
106
+ output_fields = output_type._table_fields
107
+ return sort_by, output_fields
secator/rich.py ADDED
@@ -0,0 +1,124 @@
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.logging import RichHandler
9
+ from rich.table import Table
10
+ from rich.traceback import install
11
+
12
+ from secator.definitions import DEBUG, RECORD
13
+
14
+ console = Console(stderr=True, record=RECORD, color_system='truecolor')
15
+ console_stdout = Console(record=True)
16
+ handler = RichHandler(rich_tracebacks=True)
17
+ install(show_locals=DEBUG > 2, suppress=[click, rich_click])
18
+
19
+
20
+ def criticity_to_color(value):
21
+ if value == 'critical':
22
+ value = f'[bold red3]{value.upper()}[/]'
23
+ elif value == 'high':
24
+ value = f'[bold orange_red1]{value.upper()}[/]'
25
+ elif value == 'medium':
26
+ value = f'[bold dark_orange]{value.upper()}[/]'
27
+ elif value == 'low':
28
+ value = f'[bold yellow1]{value.upper()}[/]'
29
+ elif value == 'info':
30
+ value = f'[bold green]{value.upper()}[/]'
31
+ return value
32
+
33
+
34
+ def status_to_color(value):
35
+ value = int(value) if value else None
36
+ if value is None:
37
+ return value
38
+ if value < 400:
39
+ value = f'[bold green]{value}[/]'
40
+ elif value in [400, 499]:
41
+ value = f'[bold dark_orange]{value}[/]'
42
+ elif value >= 500:
43
+ value = f'[bold red3]{value}[/]'
44
+ return value
45
+
46
+
47
+ FORMATTERS = {
48
+ 'confidence': criticity_to_color,
49
+ 'severity': criticity_to_color,
50
+ 'cvss_score': lambda score: '' if score == -1 else f'[bold cyan]{score}[/]',
51
+ 'port': lambda port: f'[bold cyan]{port}[/]',
52
+ 'url': lambda host: f'[bold underline blue]{host}[/]',
53
+ 'ip': lambda ip: f'[bold yellow]{ip}[/]',
54
+ 'status_code': status_to_color,
55
+ 'reference': lambda reference: f'[link={reference}]🡕[/]',
56
+ '_source': lambda source: f'[bold gold3]{source}[/]'
57
+ }
58
+
59
+
60
+ def build_table(items, output_fields=[], exclude_fields=[], sort_by=None):
61
+ """Build rich table.
62
+
63
+ Args:
64
+ items (list): List of items.
65
+ output_fields (list, Optional): List of fields to add.
66
+ exclude_fields (list, Optional): List of fields to exclude.
67
+ sort_by (tuple, Optional): Tuple of sort_by keys.
68
+
69
+ Returns:
70
+ rich.table.Table: rich table.
71
+ """
72
+ # Sort items by one or multiple fields
73
+ if sort_by and all(sort_by):
74
+ items = sorted(items, key=operator.attrgetter(*sort_by))
75
+
76
+ # Create rich table
77
+ box_style = box.DOUBLE if RECORD else box.ROUNDED
78
+ table = Table(show_lines=True, box=box_style)
79
+
80
+ # Get table schema if any, default to first item keys
81
+ keys = output_fields
82
+
83
+ # List of fields to exclude
84
+ keys = [k for k in keys if k not in exclude_fields]
85
+
86
+ # Remove meta fields not needed in output
87
+ if '_cls' in keys:
88
+ keys.remove('_cls')
89
+ if '_type' in keys:
90
+ keys.remove('_type')
91
+ if '_uuid' in keys:
92
+ keys.remove('_uuid')
93
+
94
+ # Add _source field
95
+ if '_source' not in keys:
96
+ keys.append('_source')
97
+
98
+ # Create table columns
99
+ for key in keys:
100
+ key_str = key
101
+ if not key.startswith('_'):
102
+ key_str = ' '.join(key.split('_')).upper()
103
+ no_wrap = key in ['url', 'reference', 'references', 'matched_at']
104
+ overflow = None if no_wrap else 'fold'
105
+ table.add_column(
106
+ key_str,
107
+ overflow=overflow,
108
+ min_width=10,
109
+ no_wrap=no_wrap,
110
+ header_style='bold blue')
111
+
112
+ # Create table rows
113
+ for item in items:
114
+ values = []
115
+ for key in keys:
116
+ value = getattr(item, key)
117
+ value = FORMATTERS.get(key, lambda x: x)(value)
118
+ if isinstance(value, dict) or isinstance(value, list):
119
+ value = yaml.dump(value)
120
+ elif isinstance(value, int) or isinstance(value, float):
121
+ value = str(value)
122
+ values.append(value)
123
+ table.add_row(*values)
124
+ 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