secator 0.22.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.
Files changed (150) hide show
  1. secator/.gitignore +162 -0
  2. secator/__init__.py +0 -0
  3. secator/celery.py +453 -0
  4. secator/celery_signals.py +138 -0
  5. secator/celery_utils.py +320 -0
  6. secator/cli.py +2035 -0
  7. secator/cli_helper.py +395 -0
  8. secator/click.py +87 -0
  9. secator/config.py +670 -0
  10. secator/configs/__init__.py +0 -0
  11. secator/configs/profiles/__init__.py +0 -0
  12. secator/configs/profiles/aggressive.yaml +8 -0
  13. secator/configs/profiles/all_ports.yaml +7 -0
  14. secator/configs/profiles/full.yaml +31 -0
  15. secator/configs/profiles/http_headless.yaml +7 -0
  16. secator/configs/profiles/http_record.yaml +8 -0
  17. secator/configs/profiles/insane.yaml +8 -0
  18. secator/configs/profiles/paranoid.yaml +8 -0
  19. secator/configs/profiles/passive.yaml +11 -0
  20. secator/configs/profiles/polite.yaml +8 -0
  21. secator/configs/profiles/sneaky.yaml +8 -0
  22. secator/configs/profiles/tor.yaml +5 -0
  23. secator/configs/scans/__init__.py +0 -0
  24. secator/configs/scans/domain.yaml +31 -0
  25. secator/configs/scans/host.yaml +23 -0
  26. secator/configs/scans/network.yaml +30 -0
  27. secator/configs/scans/subdomain.yaml +27 -0
  28. secator/configs/scans/url.yaml +19 -0
  29. secator/configs/workflows/__init__.py +0 -0
  30. secator/configs/workflows/cidr_recon.yaml +48 -0
  31. secator/configs/workflows/code_scan.yaml +29 -0
  32. secator/configs/workflows/domain_recon.yaml +46 -0
  33. secator/configs/workflows/host_recon.yaml +95 -0
  34. secator/configs/workflows/subdomain_recon.yaml +120 -0
  35. secator/configs/workflows/url_bypass.yaml +15 -0
  36. secator/configs/workflows/url_crawl.yaml +98 -0
  37. secator/configs/workflows/url_dirsearch.yaml +62 -0
  38. secator/configs/workflows/url_fuzz.yaml +68 -0
  39. secator/configs/workflows/url_params_fuzz.yaml +66 -0
  40. secator/configs/workflows/url_secrets_hunt.yaml +23 -0
  41. secator/configs/workflows/url_vuln.yaml +91 -0
  42. secator/configs/workflows/user_hunt.yaml +29 -0
  43. secator/configs/workflows/wordpress.yaml +38 -0
  44. secator/cve.py +718 -0
  45. secator/decorators.py +7 -0
  46. secator/definitions.py +168 -0
  47. secator/exporters/__init__.py +14 -0
  48. secator/exporters/_base.py +3 -0
  49. secator/exporters/console.py +10 -0
  50. secator/exporters/csv.py +37 -0
  51. secator/exporters/gdrive.py +123 -0
  52. secator/exporters/json.py +16 -0
  53. secator/exporters/table.py +36 -0
  54. secator/exporters/txt.py +28 -0
  55. secator/hooks/__init__.py +0 -0
  56. secator/hooks/gcs.py +80 -0
  57. secator/hooks/mongodb.py +281 -0
  58. secator/installer.py +694 -0
  59. secator/loader.py +128 -0
  60. secator/output_types/__init__.py +49 -0
  61. secator/output_types/_base.py +108 -0
  62. secator/output_types/certificate.py +78 -0
  63. secator/output_types/domain.py +50 -0
  64. secator/output_types/error.py +42 -0
  65. secator/output_types/exploit.py +58 -0
  66. secator/output_types/info.py +24 -0
  67. secator/output_types/ip.py +47 -0
  68. secator/output_types/port.py +55 -0
  69. secator/output_types/progress.py +36 -0
  70. secator/output_types/record.py +36 -0
  71. secator/output_types/stat.py +41 -0
  72. secator/output_types/state.py +29 -0
  73. secator/output_types/subdomain.py +45 -0
  74. secator/output_types/tag.py +69 -0
  75. secator/output_types/target.py +38 -0
  76. secator/output_types/url.py +112 -0
  77. secator/output_types/user_account.py +41 -0
  78. secator/output_types/vulnerability.py +101 -0
  79. secator/output_types/warning.py +30 -0
  80. secator/report.py +140 -0
  81. secator/rich.py +130 -0
  82. secator/runners/__init__.py +14 -0
  83. secator/runners/_base.py +1240 -0
  84. secator/runners/_helpers.py +218 -0
  85. secator/runners/celery.py +18 -0
  86. secator/runners/command.py +1178 -0
  87. secator/runners/python.py +126 -0
  88. secator/runners/scan.py +87 -0
  89. secator/runners/task.py +81 -0
  90. secator/runners/workflow.py +168 -0
  91. secator/scans/__init__.py +29 -0
  92. secator/serializers/__init__.py +8 -0
  93. secator/serializers/dataclass.py +39 -0
  94. secator/serializers/json.py +45 -0
  95. secator/serializers/regex.py +25 -0
  96. secator/tasks/__init__.py +8 -0
  97. secator/tasks/_categories.py +487 -0
  98. secator/tasks/arjun.py +113 -0
  99. secator/tasks/arp.py +53 -0
  100. secator/tasks/arpscan.py +70 -0
  101. secator/tasks/bbot.py +372 -0
  102. secator/tasks/bup.py +118 -0
  103. secator/tasks/cariddi.py +193 -0
  104. secator/tasks/dalfox.py +87 -0
  105. secator/tasks/dirsearch.py +84 -0
  106. secator/tasks/dnsx.py +186 -0
  107. secator/tasks/feroxbuster.py +93 -0
  108. secator/tasks/ffuf.py +135 -0
  109. secator/tasks/fping.py +85 -0
  110. secator/tasks/gau.py +102 -0
  111. secator/tasks/getasn.py +60 -0
  112. secator/tasks/gf.py +36 -0
  113. secator/tasks/gitleaks.py +96 -0
  114. secator/tasks/gospider.py +84 -0
  115. secator/tasks/grype.py +109 -0
  116. secator/tasks/h8mail.py +75 -0
  117. secator/tasks/httpx.py +167 -0
  118. secator/tasks/jswhois.py +36 -0
  119. secator/tasks/katana.py +203 -0
  120. secator/tasks/maigret.py +87 -0
  121. secator/tasks/mapcidr.py +42 -0
  122. secator/tasks/msfconsole.py +179 -0
  123. secator/tasks/naabu.py +85 -0
  124. secator/tasks/nmap.py +487 -0
  125. secator/tasks/nuclei.py +151 -0
  126. secator/tasks/search_vulns.py +225 -0
  127. secator/tasks/searchsploit.py +109 -0
  128. secator/tasks/sshaudit.py +299 -0
  129. secator/tasks/subfinder.py +48 -0
  130. secator/tasks/testssl.py +283 -0
  131. secator/tasks/trivy.py +130 -0
  132. secator/tasks/trufflehog.py +240 -0
  133. secator/tasks/urlfinder.py +100 -0
  134. secator/tasks/wafw00f.py +106 -0
  135. secator/tasks/whois.py +34 -0
  136. secator/tasks/wpprobe.py +116 -0
  137. secator/tasks/wpscan.py +202 -0
  138. secator/tasks/x8.py +94 -0
  139. secator/tasks/xurlfind3r.py +83 -0
  140. secator/template.py +294 -0
  141. secator/thread.py +24 -0
  142. secator/tree.py +196 -0
  143. secator/utils.py +922 -0
  144. secator/utils_test.py +297 -0
  145. secator/workflows/__init__.py +29 -0
  146. secator-0.22.0.dist-info/METADATA +447 -0
  147. secator-0.22.0.dist-info/RECORD +150 -0
  148. secator-0.22.0.dist-info/WHEEL +4 -0
  149. secator-0.22.0.dist-info/entry_points.txt +2 -0
  150. secator-0.22.0.dist-info/licenses/LICENSE +60 -0
secator/loader.py ADDED
@@ -0,0 +1,128 @@
1
+ from functools import cache
2
+ from pkgutil import iter_modules
3
+ from secator.rich import console
4
+ from secator.config import CONFIG, CONFIGS_FOLDER
5
+ from secator.template import TemplateLoader
6
+ from secator.utils import debug
7
+ from pathlib import Path
8
+ import glob
9
+ import importlib
10
+ import inspect
11
+ import sys
12
+
13
+
14
+ @cache
15
+ def find_templates():
16
+ discover_tasks() # always load tasks first
17
+ results = []
18
+ dirs = [CONFIGS_FOLDER]
19
+ if CONFIG.dirs.templates:
20
+ dirs.append(CONFIG.dirs.templates)
21
+ paths = []
22
+ for dir in dirs:
23
+ config_paths = [
24
+ Path(path)
25
+ for path in glob.glob(str(dir).rstrip('/') + '/**/*.y*ml', recursive=True)
26
+ ]
27
+ debug(f'Found {len(config_paths)} templates in {dir}', sub='template')
28
+ paths.extend(config_paths)
29
+ for path in paths:
30
+ config = TemplateLoader(input=path)
31
+ debug(f'Loaded template from {path}', sub='template')
32
+ results.append(config)
33
+ return results
34
+
35
+
36
+ @cache
37
+ def get_configs_by_type(type):
38
+ if type == 'task':
39
+ tasks = discover_tasks()
40
+ task_config = [TemplateLoader({
41
+ 'name': cls.__name__,
42
+ 'type': 'task',
43
+ 'description': cls.__doc__,
44
+ 'input_types': cls.input_types,
45
+ 'output_types': [t.get_name() for t in cls.output_types],
46
+ 'default_inputs': cls.default_inputs,
47
+ 'proxychains': getattr(cls, 'proxychains', True),
48
+ 'proxy_socks5': getattr(cls, 'proxy_socks5', True),
49
+ 'proxy_http': getattr(cls, 'proxy_http', True),
50
+ 'default_cmd': getattr(cls, 'cmd', None),
51
+ 'install_cmd': getattr(cls, 'install_cmd', None),
52
+ }) for cls in tasks] # noqa: E501
53
+ return sorted(task_config, key=lambda x: x['name'])
54
+ return sorted([t for t in find_templates() if t.type == type], key=lambda x: x.name)
55
+
56
+
57
+ @cache
58
+ def discover_tasks():
59
+ """Find all secator tasks (internal + external)."""
60
+ return discover_internal_tasks() + discover_external_tasks()
61
+
62
+
63
+ @cache
64
+ def discover_internal_tasks():
65
+ """Find internal secator tasks."""
66
+ from secator.runners import Runner
67
+ package_dir = Path(__file__).resolve().parent / 'tasks'
68
+ task_classes = []
69
+ for (_, module_name, _) in iter_modules([str(package_dir)]):
70
+ if module_name.startswith('_'):
71
+ continue
72
+ try:
73
+ module = importlib.import_module(f'secator.tasks.{module_name}')
74
+ except ImportError as e:
75
+ console.print(f'[bold red]Could not import secator.tasks.{module_name}:[/]')
76
+ console.print(f'\t[bold red]{type(e).__name__}[/]: {str(e)}')
77
+ continue
78
+ for attribute_name in dir(module):
79
+ attribute = getattr(module, attribute_name)
80
+ if inspect.isclass(attribute):
81
+ bases = inspect.getmro(attribute)
82
+ if Runner in bases and hasattr(attribute, '__task__'):
83
+ attribute.__external__ = False
84
+ task_classes.append(attribute)
85
+
86
+ # Sort task_classes by category
87
+ task_classes = sorted(
88
+ task_classes,
89
+ # key=lambda x: (get_command_category(x), x.__name__)
90
+ key=lambda x: x.__name__)
91
+ return task_classes
92
+
93
+
94
+ @cache
95
+ def discover_external_tasks():
96
+ """Find external secator tasks."""
97
+ output = []
98
+ prev_state = sys.dont_write_bytecode
99
+ sys.dont_write_bytecode = True
100
+ for path in CONFIG.dirs.templates.glob('**/*.py'):
101
+ try:
102
+ task_name = path.stem
103
+ module_name = f'secator.tasks.{task_name}'
104
+
105
+ # console.print(f'Importing module {module_name} from {path}')
106
+ spec = importlib.util.spec_from_file_location(module_name, path)
107
+ module = importlib.util.module_from_spec(spec)
108
+ if not spec:
109
+ console.print(f'[bold red]Could not load external module {path.name}: invalid import spec.[/] ({path})')
110
+ continue
111
+ # console.print(f'Adding module "{module_name}" to sys path')
112
+ sys.modules[module_name] = module
113
+
114
+ # console.print(f'Executing module "{module}"')
115
+ spec.loader.exec_module(module)
116
+
117
+ # console.print(f'Checking that {module} contains task {task_name}')
118
+ if not hasattr(module, task_name):
119
+ console.print(f'[bold orange1]Could not load external task "{task_name}" from module {path.name}[/] ({path})')
120
+ continue
121
+ cls = getattr(module, task_name)
122
+ console.print(f'[bold green]Successfully loaded external task "{task_name}"[/] ({path})')
123
+ cls.__external__ = True
124
+ output.append(cls)
125
+ except Exception as e:
126
+ console.print(f'[bold red]Could not load external module {path.name}. Reason: {str(e)}.[/] ({path})')
127
+ sys.dont_write_bytecode = prev_state
128
+ return output
@@ -0,0 +1,49 @@
1
+ __all__ = [
2
+ 'Domain',
3
+ 'Error',
4
+ 'OutputType',
5
+ 'Info',
6
+ 'Ip',
7
+ 'Port',
8
+ 'Progress',
9
+ 'Record',
10
+ 'Stat',
11
+ 'State',
12
+ 'Subdomain',
13
+ 'Url',
14
+ 'UrlParam',
15
+ 'UserAccount',
16
+ 'Vulnerability',
17
+ 'Warning',
18
+ ]
19
+ from secator.output_types._base import OutputType
20
+ from secator.output_types.progress import Progress
21
+ from secator.output_types.ip import Ip
22
+ from secator.output_types.exploit import Exploit
23
+ from secator.output_types.port import Port
24
+ from secator.output_types.subdomain import Subdomain
25
+ from secator.output_types.tag import Tag
26
+ from secator.output_types.target import Target
27
+ from secator.output_types.url import Url
28
+ from secator.output_types.user_account import UserAccount
29
+ from secator.output_types.vulnerability import Vulnerability
30
+ from secator.output_types.record import Record
31
+ from secator.output_types.certificate import Certificate
32
+ from secator.output_types.info import Info
33
+ from secator.output_types.warning import Warning
34
+ from secator.output_types.error import Error
35
+ from secator.output_types.stat import Stat
36
+ from secator.output_types.state import State
37
+ from secator.output_types.domain import Domain
38
+
39
+
40
+ EXECUTION_TYPES = [
41
+ Target, Progress, Info, Warning, Error, State
42
+ ]
43
+ STAT_TYPES = [
44
+ Stat
45
+ ]
46
+ FINDING_TYPES = [
47
+ Subdomain, Ip, Port, Url, Tag, Exploit, UserAccount, Vulnerability, Certificate, Record, Domain
48
+ ]
49
+ OUTPUT_TYPES = FINDING_TYPES + EXECUTION_TYPES + STAT_TYPES
@@ -0,0 +1,108 @@
1
+ import logging
2
+ import re
3
+ from dataclasses import _MISSING_TYPE, dataclass, fields
4
+ from secator.definitions import DEBUG
5
+ from secator.rich import console
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ @dataclass
11
+ class OutputType:
12
+ _table_fields = []
13
+ _sort_by = ()
14
+
15
+ def __str__(self):
16
+ return self.__class__.__name__
17
+
18
+ def __gt__(self, other):
19
+ if not self.__eq__(other):
20
+ return False
21
+
22
+ # Point-based system based on number of non-empty extra-data present.
23
+ # In this configuration, a > b if a == b AND a has more non-empty fields than b
24
+ # extra_fields = [f for f in fields(self) if not f.compare]
25
+ # points1 = 0
26
+ # points2 = 0
27
+ # for field in extra_fields:
28
+ # v1 = getattr(self, field.name)
29
+ # v2 = getattr(other, field.name)
30
+ # if v1 and not v2:
31
+ # points1 += 1
32
+ # elif v2 and not v1:
33
+ # points2 += 1
34
+ # if points1 > points2:
35
+ # return True
36
+
37
+ # Timestamp-based system: return newest object
38
+ return self._timestamp > other._timestamp
39
+
40
+ def __ge__(self, other):
41
+ return self == other
42
+
43
+ def __lt__(self, other):
44
+ return other > self
45
+
46
+ def __le__(self, other):
47
+ return self == other
48
+
49
+ def __post_init__(self):
50
+ """Initialize default fields to their proper types."""
51
+ for field in fields(self):
52
+ default_factory = field.default_factory
53
+ default = field.default
54
+ if getattr(self, field.name) is None:
55
+ if not isinstance(default, _MISSING_TYPE):
56
+ setattr(self, field.name, field.default)
57
+ elif not isinstance(default_factory, _MISSING_TYPE):
58
+ setattr(self, field.name, default_factory())
59
+
60
+ @classmethod
61
+ def load(cls, item, output_map={}):
62
+ new_item = {}
63
+
64
+ # Check for explicit _type keys
65
+ _type = item.get('_type')
66
+ if _type and _type != cls.get_name():
67
+ raise TypeError(f'Item has different _type set: {_type}')
68
+
69
+ for field in fields(cls):
70
+ key = field.name
71
+ if key in output_map:
72
+ mapped_key = output_map[key]
73
+ if callable(mapped_key):
74
+ try:
75
+ mapped_val = mapped_key(item)
76
+ except Exception as e:
77
+ mapped_val = None
78
+ if DEBUG > 1:
79
+ console.print_exception(show_locals=True)
80
+ raise TypeError(
81
+ f'Fail to transform value for "{key}" using output_map function. Exception: '
82
+ f'{type(e).__name__}: {str(e)}')
83
+ else:
84
+ mapped_val = item.get(mapped_key)
85
+ new_item[key] = mapped_val
86
+ elif key in item:
87
+ new_item[key] = item[key]
88
+
89
+ # All values None, raise an error
90
+ if all(val is None for val in new_item.values()):
91
+ raise TypeError(f'Item does not match {cls} schema')
92
+
93
+ new_item['_type'] = cls.get_name()
94
+ return cls(**new_item)
95
+
96
+ @classmethod
97
+ def get_name(cls):
98
+ return re.sub(r'(?<!^)(?=[A-Z])', '_', cls.__name__).lower()
99
+
100
+ @classmethod
101
+ def keys(cls):
102
+ return [f.name for f in fields(cls)]
103
+
104
+ def toDict(self, exclude=[]):
105
+ data = self.__dict__.copy()
106
+ if exclude:
107
+ return {k: v for k, v in data.items() if k not in exclude}
108
+ return data
@@ -0,0 +1,78 @@
1
+ import time
2
+ from datetime import datetime, timedelta
3
+ from dataclasses import dataclass, field
4
+ from secator.output_types import OutputType
5
+ from secator.utils import rich_to_ansi
6
+ from secator.definitions import CERTIFICATE_STATUS_UNKNOWN
7
+
8
+
9
+ @dataclass
10
+ class Certificate(OutputType):
11
+ host: str
12
+ fingerprint_sha256: str = field(default='')
13
+ ip: str = field(default='', compare=False)
14
+ raw_value: str = field(default='', compare=False)
15
+ subject_cn: str = field(default='', compare=False)
16
+ subject_an: list[str] = field(default_factory=list, compare=False)
17
+ not_before: datetime = field(default=None, compare=False)
18
+ not_after: datetime = field(default=None, compare=False)
19
+ issuer_dn: str = field(default='', compare=False)
20
+ issuer_cn: str = field(default='', compare=False)
21
+ issuer: str = field(default='', compare=False)
22
+ self_signed: bool = field(default=True, compare=False)
23
+ trusted: bool = field(default=False, compare=False)
24
+ status: str = field(default=CERTIFICATE_STATUS_UNKNOWN, compare=False)
25
+ keysize: int = field(default=None, compare=False)
26
+ serial_number: str = field(default='', compare=False)
27
+ ciphers: list[str] = field(default_factory=list, compare=False)
28
+ # parent_certificate: 'Certificate' = None # noqa: F821
29
+ _source: str = field(default='', repr=True, compare=False)
30
+ _type: str = field(default='certificate', repr=True)
31
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
32
+ _uuid: str = field(default='', repr=True, compare=False)
33
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
34
+ _tagged: bool = field(default=False, repr=True, compare=False)
35
+ _duplicate: bool = field(default=False, repr=True, compare=False)
36
+ _related: list = field(default_factory=list, compare=False)
37
+ _table_fields = ['ip', 'host']
38
+ _sort_by = ('ip',)
39
+
40
+ def __str__(self) -> str:
41
+ return self.subject_cn
42
+
43
+ def is_expired(self) -> bool:
44
+ if self.not_after:
45
+ return self.not_after < datetime.now()
46
+ return True
47
+
48
+ def is_expired_soon(self, months: int = 1) -> bool:
49
+ if self.not_after:
50
+ return self.not_after < datetime.now() + timedelta(days=months * 30)
51
+ return True
52
+
53
+ @staticmethod
54
+ def format_date(date):
55
+ if date:
56
+ return date.strftime("%m/%d/%Y")
57
+ return '?'
58
+
59
+ def __repr__(self) -> str:
60
+ s = f'📜 [bold white]{self.host}[/]'
61
+ s += f' [cyan]{self.status}[/]'
62
+ s += rf' [white]\[fingerprint={self.fingerprint_sha256[:10]}][/]'
63
+ if self.subject_cn:
64
+ s += rf' [white]\[cn={self.subject_cn}][/]'
65
+ if self.subject_an:
66
+ s += rf' [white]\[an={", ".join(self.subject_an)}][/]'
67
+ if self.issuer:
68
+ s += rf' [white]\[issuer={self.issuer}][/]'
69
+ elif self.issuer_cn:
70
+ s += rf' [white]\[issuer_cn={self.issuer_cn}][/]'
71
+ expiry_date = Certificate.format_date(self.not_after)
72
+ if self.is_expired():
73
+ s += f' [red]expired since {expiry_date}[/red]'
74
+ elif self.is_expired_soon(months=2):
75
+ s += f' [yellow]expires <2 months[/yellow], [yellow]valid until {expiry_date}[/yellow]'
76
+ else:
77
+ s += f' [green]not expired[/green], [yellow]valid until {expiry_date}[/yellow]'
78
+ return rich_to_ansi(s)
@@ -0,0 +1,50 @@
1
+ import time
2
+ from datetime import datetime
3
+ from dataclasses import dataclass, field
4
+
5
+ from secator.definitions import ALIVE, DOMAIN
6
+ from secator.output_types import OutputType
7
+ from secator.utils import rich_to_ansi, format_object
8
+
9
+
10
+ @dataclass
11
+ class Domain(OutputType):
12
+ domain: str
13
+ registrar: str = ''
14
+ alive: bool = False
15
+ creation_date: str = ''
16
+ expiration_date: str = ''
17
+ registrant: str = ''
18
+ extra_data: dict = field(default_factory=dict, compare=False)
19
+ _source: str = field(default='', repr=True)
20
+ _type: str = field(default='domain', repr=True)
21
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
22
+ _uuid: str = field(default='', repr=True, compare=False)
23
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
24
+ _tagged: bool = field(default=False, repr=True, compare=False)
25
+ _duplicate: bool = field(default=False, repr=True, compare=False)
26
+ _related: list = field(default_factory=list, compare=False)
27
+
28
+ _table_fields = [DOMAIN, ALIVE]
29
+ _sort_by = (DOMAIN,)
30
+
31
+ def __str__(self) -> str:
32
+ return self.domain
33
+
34
+ def __repr__(self) -> str:
35
+ s = f'🪪 [bold white]{self.domain}[/]'
36
+ if self.registrant:
37
+ s += rf' \[[bold magenta]{self.registrant}[/]]'
38
+ if self.registrar:
39
+ s += rf' \[[bold blue]{self.registrar}[/]]'
40
+ if self.expiration_date:
41
+ now = datetime.now()
42
+ expiration_date_strptime = datetime.strptime(self.expiration_date, "%Y-%m-%d %H:%M:%S")
43
+ if expiration_date_strptime < now:
44
+ s += rf' \[[bold red]{self.expiration_date}[/]]'
45
+ else:
46
+ s += rf' \[[bold green]{self.expiration_date}[/]]'
47
+ if self.extra_data:
48
+ s += format_object(self.extra_data, 'yellow')
49
+
50
+ return rich_to_ansi(s)
@@ -0,0 +1,42 @@
1
+ from dataclasses import dataclass, field
2
+ import time
3
+ from secator.output_types import OutputType
4
+ from secator.utils import rich_to_ansi, traceback_as_string, rich_escape as _s
5
+
6
+
7
+ @dataclass
8
+ class Error(OutputType):
9
+ message: str
10
+ traceback: str = field(default='', compare=False)
11
+ traceback_title: str = field(default='', compare=False)
12
+ _source: str = field(default='', repr=True)
13
+ _type: str = field(default='error', repr=True)
14
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
15
+ _uuid: str = field(default='', repr=True, compare=False)
16
+ _context: dict = field(default_factory=dict, 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 = ['message', 'traceback']
21
+ _sort_by = ('_timestamp',)
22
+
23
+ def from_exception(e, **kwargs):
24
+ errtype = type(e).__name__
25
+ if str(e):
26
+ errtype += f': {str(e)}'
27
+ message = kwargs.pop('message', errtype)
28
+ traceback = traceback_as_string(e) if errtype not in ['KeyboardInterrupt', 'GreenletExit'] else ''
29
+ error = Error(message=_s(message), traceback=traceback, **kwargs)
30
+ return error
31
+
32
+ def __str__(self):
33
+ return self.message
34
+
35
+ def __repr__(self):
36
+ s = rf"\[[bold red]ERR[/]] {self.message}"
37
+ if self.traceback:
38
+ traceback_pretty = ' ' + _s(self.traceback).replace('\n', '\n ')
39
+ if self.traceback_title:
40
+ traceback_pretty = f' {self.traceback_title}:\n{traceback_pretty}'
41
+ s += f'\n[dim]{_s(traceback_pretty)}[/]'
42
+ return rich_to_ansi(s)
@@ -0,0 +1,58 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from secator.output_types import OutputType
4
+ from secator.utils import rich_to_ansi, rich_escape as _s, format_object
5
+ from secator.definitions import MATCHED_AT, NAME, ID, EXTRA_DATA, REFERENCE
6
+
7
+
8
+ @dataclass
9
+ class Exploit(OutputType):
10
+ name: str
11
+ provider: str
12
+ id: str
13
+ matched_at: str = ''
14
+ ip: str = ''
15
+ confidence: str = 'low'
16
+ reference: str = field(default='', repr=True, compare=False)
17
+ cves: list = field(default_factory=list, compare=False)
18
+ tags: list = field(default_factory=list, compare=False)
19
+ extra_data: dict = field(default_factory=dict, compare=False)
20
+ _source: str = field(default='', repr=True, compare=False)
21
+ _type: str = field(default='exploit', repr=True)
22
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
23
+ _uuid: str = field(default='', repr=True, compare=False)
24
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
25
+ _tagged: bool = field(default=False, repr=True, compare=False)
26
+ _duplicate: bool = field(default=False, repr=True, compare=False)
27
+ _related: list = field(default_factory=list, compare=False)
28
+
29
+ _table_fields = [
30
+ MATCHED_AT,
31
+ NAME,
32
+ ID,
33
+ EXTRA_DATA,
34
+ REFERENCE
35
+ ]
36
+ _sort_by = ('matched_at', 'name')
37
+
38
+ def __str__(self):
39
+ return self.name
40
+
41
+ def __repr__(self):
42
+ s = rf'[bold red]⍼[/] \[[bold red]{self.name}'
43
+ if self.reference:
44
+ s += f' [link={_s(self.reference)}]🡕[/link]'
45
+ s += '[/]]'
46
+ if self.matched_at:
47
+ s += f' {_s(self.matched_at)}'
48
+ if self.cves:
49
+ cves_str = ', '.join(self.cves)
50
+ s += rf' \[[red]{cves_str}[/]]'
51
+ if self.tags:
52
+ tags_str = ', '.join(self.tags)
53
+ s += rf' \[[cyan]{tags_str}[/]]'
54
+ if self.extra_data:
55
+ s += format_object(self.extra_data, 'yellow')
56
+ if self.confidence == 'low':
57
+ s = f'[dim]{s}[/]'
58
+ return rich_to_ansi(s)
@@ -0,0 +1,24 @@
1
+ from dataclasses import dataclass, field
2
+ import time
3
+ from secator.output_types import OutputType
4
+ from secator.utils import rich_to_ansi
5
+
6
+
7
+ @dataclass
8
+ class Info(OutputType):
9
+ message: str
10
+ task_id: str = field(default='', compare=False)
11
+ _source: str = field(default='', repr=True)
12
+ _type: str = field(default='info', 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
+ _duplicate: bool = field(default=False, repr=True, compare=False)
17
+ _related: list = field(default_factory=list, compare=False)
18
+
19
+ _table_fields = ['message', 'task_id']
20
+ _sort_by = ('_timestamp',)
21
+
22
+ def __repr__(self):
23
+ s = rf"\[[blue]INF[/]] {self.message}"
24
+ return rich_to_ansi(s)
@@ -0,0 +1,47 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+ from enum import Enum
4
+
5
+ from secator.definitions import ALIVE, IP
6
+ from secator.output_types import OutputType
7
+ from secator.utils import rich_to_ansi, format_object
8
+
9
+
10
+ class IpProtocol(str, Enum):
11
+ IPv6 = 'IPv6'
12
+ IPv4 = 'IPv4'
13
+
14
+
15
+ @dataclass
16
+ class Ip(OutputType):
17
+ ip: str
18
+ host: str = field(default='', repr=True, compare=False)
19
+ alive: bool = False
20
+ protocol: str = field(default=IpProtocol.IPv4)
21
+ extra_data: dict = field(default_factory=dict, compare=False)
22
+ _source: str = field(default='', repr=True, compare=False)
23
+ _type: str = field(default='ip', repr=True)
24
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
25
+ _uuid: str = field(default='', repr=True, compare=False)
26
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
27
+ _tagged: bool = field(default=False, repr=True, compare=False)
28
+ _duplicate: bool = field(default=False, repr=True, compare=False)
29
+ _related: list = field(default_factory=list, compare=False)
30
+
31
+ _table_fields = [IP, ALIVE]
32
+ _sort_by = (IP,)
33
+
34
+ def __str__(self) -> str:
35
+ return self.ip
36
+
37
+ def __repr__(self) -> str:
38
+ s = f'💻 [bold white]{self.ip}[/]'
39
+ if self.host and self.host != self.ip:
40
+ s += rf' \[[bold magenta]{self.host}[/]]'
41
+ if self.alive:
42
+ s += r' \[[bold green]alive[/]]'
43
+ if self.extra_data:
44
+ s += format_object(self.extra_data, 'yellow')
45
+ if not self.alive:
46
+ s = f'[dim]{s}[/]'
47
+ return rich_to_ansi(s)
@@ -0,0 +1,55 @@
1
+ import time
2
+ from dataclasses import dataclass, field
3
+
4
+ from secator.definitions import CPES, EXTRA_DATA, HOST, IP, PORT
5
+ from secator.output_types import OutputType
6
+ from secator.utils import rich_to_ansi, format_object
7
+
8
+
9
+ @dataclass
10
+ class Port(OutputType):
11
+ port: int
12
+ ip: str
13
+ state: str = 'UNKNOWN'
14
+ service_name: str = field(default='', compare=False)
15
+ cpes: list = field(default_factory=list, compare=False)
16
+ host: str = field(default='', repr=True, compare=False)
17
+ protocol: str = field(default='tcp', repr=True, compare=False)
18
+ extra_data: dict = field(default_factory=dict, compare=False)
19
+ confidence: str = field(default='low', repr=False, compare=False)
20
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
21
+ _source: str = field(default='', repr=True, compare=False)
22
+ _type: str = field(default='port', repr=True)
23
+ _uuid: str = field(default='', repr=True, compare=False)
24
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
25
+ _tagged: bool = field(default=False, repr=True, compare=False)
26
+ _duplicate: bool = field(default=False, repr=True, compare=False)
27
+ _related: list = field(default_factory=list, compare=False)
28
+
29
+ _table_fields = [IP, PORT, HOST, CPES, EXTRA_DATA]
30
+ _sort_by = (PORT, IP)
31
+
32
+ def __gt__(self, other):
33
+ # favor nmap over other port detection tools
34
+ if self._source == 'nmap' and other._source != 'nmap':
35
+ return True
36
+ return super().__gt__(other)
37
+
38
+ def __str__(self) -> str:
39
+ return f'{self.host}:{self.port}'
40
+
41
+ def __repr__(self) -> str:
42
+ s = f'🔓 {self.ip}:[bold red]{self.port:<4}[/] [bold yellow]{self.state.upper()}[/]'
43
+ if self.protocol != 'TCP':
44
+ s += rf' \[[yellow3]{self.protocol}[/]]'
45
+ if self.service_name:
46
+ conf = ''
47
+ if self.confidence == 'low':
48
+ conf = '[bold orange3]?[/]'
49
+ s += rf' \[[bold purple]{self.service_name}{conf}[/]]'
50
+ if self.host and self.host != self.ip:
51
+ s += rf' \[[cyan]{self.host}[/]]'
52
+ if self.extra_data:
53
+ skip_keys = ['name', 'servicefp', 'method', 'service_name', 'product', 'version', 'conf']
54
+ s += format_object(self.extra_data, 'yellow', skip_keys=skip_keys)
55
+ return rich_to_ansi(s)
@@ -0,0 +1,36 @@
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, format_object
6
+
7
+
8
+ @dataclass
9
+ class Progress(OutputType):
10
+ percent: int = 0
11
+ extra_data: dict = field(default_factory=dict)
12
+ _source: str = field(default='', repr=True, compare=False)
13
+ _type: str = field(default='progress', repr=True)
14
+ _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
15
+ _uuid: str = field(default='', repr=True, compare=False)
16
+ _context: dict = field(default_factory=dict, repr=True, compare=False)
17
+ _tagged: bool = field(default=False, repr=True, compare=False)
18
+ _duplicate: bool = field(default=False, repr=True, compare=False)
19
+ _related: list = field(default_factory=list, compare=False)
20
+
21
+ _table_fields = ['percent']
22
+ _sort_by = ('percent',)
23
+
24
+ def __post_init__(self):
25
+ super().__post_init__()
26
+ if not 0 <= self.percent <= 100:
27
+ self.percent = 0
28
+
29
+ def __str__(self) -> str:
30
+ return f'{self.percent}%'
31
+
32
+ def __repr__(self) -> str:
33
+ s = f'[dim]⏳ [bold]{self.percent}%[/] ' + '█' * (self.percent // 10) + '[/]'
34
+ ed = format_object(self.extra_data, color='yellow3', skip_keys=['startedAt'])
35
+ s += f'[dim]{ed}[/]'
36
+ return rich_to_ansi(s)