secator 0.17.0__py3-none-any.whl → 0.19.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/config.py +3 -2
- secator/configs/workflows/user_hunt.yaml +14 -2
- secator/configs/workflows/wordpress.yaml +13 -0
- secator/installer.py +1 -1
- secator/output_types/stat.py +8 -3
- secator/runners/_base.py +8 -10
- secator/runners/command.py +140 -19
- secator/tasks/bup.py +1 -1
- secator/tasks/fping.py +24 -7
- secator/tasks/maigret.py +7 -2
- secator/tasks/wpscan.py +15 -4
- {secator-0.17.0.dist-info → secator-0.19.0.dist-info}/METADATA +1 -1
- {secator-0.17.0.dist-info → secator-0.19.0.dist-info}/RECORD +16 -16
- {secator-0.17.0.dist-info → secator-0.19.0.dist-info}/WHEEL +0 -0
- {secator-0.17.0.dist-info → secator-0.19.0.dist-info}/entry_points.txt +0 -0
- {secator-0.17.0.dist-info → secator-0.19.0.dist-info}/licenses/LICENSE +0 -0
secator/config.py
CHANGED
|
@@ -67,6 +67,8 @@ class Celery(StrictModel):
|
|
|
67
67
|
task_acks_late: bool = False
|
|
68
68
|
task_send_sent_event: bool = False
|
|
69
69
|
task_reject_on_worker_lost: bool = False
|
|
70
|
+
task_max_timeout: int = -1
|
|
71
|
+
task_memory_limit_mb: int = -1
|
|
70
72
|
worker_max_tasks_per_child: int = 20
|
|
71
73
|
worker_prefetch_multiplier: int = 1
|
|
72
74
|
worker_send_task_events: bool = False
|
|
@@ -100,7 +102,6 @@ class Security(StrictModel):
|
|
|
100
102
|
allow_local_file_access: bool = True
|
|
101
103
|
auto_install_commands: bool = True
|
|
102
104
|
force_source_install: bool = False
|
|
103
|
-
memory_limit_mb: int = -1
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
class HTTP(StrictModel):
|
|
@@ -145,7 +146,7 @@ class Wordlists(StrictModel):
|
|
|
145
146
|
templates: Dict[str, str] = {
|
|
146
147
|
'bo0m_fuzz': 'https://raw.githubusercontent.com/Bo0oM/fuzz.txt/master/fuzz.txt',
|
|
147
148
|
'combined_subdomains': 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt', # noqa: E501
|
|
148
|
-
'directory_list_small': 'https://
|
|
149
|
+
'directory_list_small': 'https://gist.githubusercontent.com/sl4v/c087e36164e74233514b/raw/c51a811c70bbdd87f4725521420cc30e7232b36d/directory-list-2.3-small.txt', # noqa: E501
|
|
149
150
|
}
|
|
150
151
|
lists: Dict[str, List[str]] = {}
|
|
151
152
|
|
|
@@ -6,7 +6,19 @@ tags: [user_account]
|
|
|
6
6
|
input_types:
|
|
7
7
|
- slug
|
|
8
8
|
- string
|
|
9
|
+
- email
|
|
9
10
|
|
|
10
11
|
tasks:
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
_group/hunt_users:
|
|
13
|
+
maigret:
|
|
14
|
+
description: Hunt user accounts
|
|
15
|
+
targets_:
|
|
16
|
+
- type: target
|
|
17
|
+
field: name
|
|
18
|
+
condition: target.type != 'email'
|
|
19
|
+
h8mail:
|
|
20
|
+
description: Find password leaks
|
|
21
|
+
targets_:
|
|
22
|
+
- type: target
|
|
23
|
+
field: name
|
|
24
|
+
condition: target.type == 'email'
|
|
@@ -5,15 +5,28 @@ description: Wordpress vulnerability scan
|
|
|
5
5
|
tags: [http, wordpress, vulnerability]
|
|
6
6
|
input_types:
|
|
7
7
|
- url
|
|
8
|
+
- ip
|
|
9
|
+
- host
|
|
10
|
+
- host:port
|
|
8
11
|
|
|
9
12
|
tasks:
|
|
13
|
+
httpx:
|
|
14
|
+
description: URL probe
|
|
15
|
+
tech_detect: True
|
|
16
|
+
|
|
10
17
|
_group/hunt_wordpress:
|
|
11
18
|
nuclei:
|
|
12
19
|
description: Nuclei Wordpress scan
|
|
13
20
|
tags: [wordpress]
|
|
21
|
+
targets_:
|
|
22
|
+
- url.url
|
|
14
23
|
|
|
15
24
|
wpscan:
|
|
16
25
|
description: WPScan
|
|
26
|
+
targets_:
|
|
27
|
+
- url.url
|
|
17
28
|
|
|
18
29
|
wpprobe:
|
|
19
30
|
description: WPProbe
|
|
31
|
+
targets_:
|
|
32
|
+
- url.url
|
secator/installer.py
CHANGED
|
@@ -150,7 +150,7 @@ class PackageInstaller:
|
|
|
150
150
|
|
|
151
151
|
# Installer cmd
|
|
152
152
|
cmd = distribution.pm_installer
|
|
153
|
-
if CONFIG.security.
|
|
153
|
+
if CONFIG.security.auto_install_commands and IN_CELERY_WORKER_PROCESS:
|
|
154
154
|
cmd = f'flock /tmp/install.lock {cmd}'
|
|
155
155
|
if getpass.getuser() != 'root':
|
|
156
156
|
cmd = f'sudo {cmd}'
|
secator/output_types/stat.py
CHANGED
|
@@ -11,6 +11,7 @@ class Stat(OutputType):
|
|
|
11
11
|
pid: int
|
|
12
12
|
cpu: int
|
|
13
13
|
memory: int
|
|
14
|
+
memory_limit: int
|
|
14
15
|
net_conns: int = field(default=None, repr=True)
|
|
15
16
|
extra_data: dict = field(default_factory=dict)
|
|
16
17
|
_source: str = field(default='', repr=True, compare=False)
|
|
@@ -26,11 +27,15 @@ class Stat(OutputType):
|
|
|
26
27
|
_sort_by = ('name', 'pid')
|
|
27
28
|
|
|
28
29
|
def __str__(self) -> str:
|
|
29
|
-
return f'{self.name} [pid
|
|
30
|
+
return f'{self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%) ([bold]memory[/]:{self.memory:.2f}MB / {self.memory_limit}MB)' # noqa: E501
|
|
30
31
|
|
|
31
32
|
def __repr__(self) -> str:
|
|
32
|
-
s = rf'[dim yellow3]📊 {self.name}
|
|
33
|
+
s = rf'[dim yellow3]📊 {self.name} ([bold]pid[/]:{self.pid}) ([bold]cpu[/]:{self.cpu:.2f}%)'
|
|
34
|
+
s += rf' ([bold]memory[/]:{self.memory:.2f}MB'
|
|
35
|
+
if self.memory_limit != -1:
|
|
36
|
+
s += rf' / {self.memory_limit}MB'
|
|
37
|
+
s += ')'
|
|
33
38
|
if self.net_conns:
|
|
34
|
-
s += rf'
|
|
39
|
+
s += rf' ([bold]connections[/]:{self.net_conns})'
|
|
35
40
|
s += ' [/]'
|
|
36
41
|
return rich_to_ansi(s)
|
secator/runners/_base.py
CHANGED
|
@@ -179,14 +179,14 @@ class Runner:
|
|
|
179
179
|
# Add prior results to runner results
|
|
180
180
|
self.debug(f'adding {len(results)} prior results to runner', sub='init')
|
|
181
181
|
if CONFIG.addons.mongodb.enabled:
|
|
182
|
-
self.debug('
|
|
182
|
+
self.debug(f'loading {len(results)} results from MongoDB', sub='init')
|
|
183
183
|
from secator.hooks.mongodb import get_results
|
|
184
184
|
results = get_results(results)
|
|
185
185
|
for result in results:
|
|
186
186
|
self.add_result(result, print=False, output=False, hooks=False, queue=not self.has_parent)
|
|
187
187
|
|
|
188
188
|
# Determine inputs
|
|
189
|
-
self.debug(f'resolving inputs with
|
|
189
|
+
self.debug(f'resolving inputs with {len(self.dynamic_opts)} dynamic opts', obj=self.dynamic_opts, sub='init')
|
|
190
190
|
self.inputs = [inputs] if not isinstance(inputs, list) else inputs
|
|
191
191
|
self.inputs = list(set(self.inputs))
|
|
192
192
|
targets = [Target(name=target) for target in self.inputs]
|
|
@@ -463,12 +463,11 @@ class Runner:
|
|
|
463
463
|
if item._uuid and item._uuid in self.uuids:
|
|
464
464
|
return
|
|
465
465
|
|
|
466
|
-
#
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
item._context.
|
|
471
|
-
item._context['ancestor_id'] = ancestor_id or self.ancestor_id
|
|
466
|
+
# Update context with runner info
|
|
467
|
+
ctx = item._context.copy()
|
|
468
|
+
item._context = self.context.copy()
|
|
469
|
+
item._context.update(ctx)
|
|
470
|
+
item._context['ancestor_id'] = ctx.get('ancestor_id') or self.ancestor_id
|
|
472
471
|
|
|
473
472
|
# Set uuid
|
|
474
473
|
if not item._uuid:
|
|
@@ -756,6 +755,7 @@ class Runner:
|
|
|
756
755
|
'last_updated_db': self.last_updated_db,
|
|
757
756
|
'context': self.context,
|
|
758
757
|
'errors': [e.toDict() for e in self.errors],
|
|
758
|
+
'warnings': [w.toDict() for w in self.warnings],
|
|
759
759
|
})
|
|
760
760
|
return data
|
|
761
761
|
|
|
@@ -788,8 +788,6 @@ class Runner:
|
|
|
788
788
|
continue
|
|
789
789
|
result = hook(self, *args)
|
|
790
790
|
self.debug('hook success', obj={'name': hook_type, 'fun': fun}, sub=sub, verbose='item' in sub) # noqa: E501
|
|
791
|
-
if isinstance(result, Error):
|
|
792
|
-
self.add_result(result, hooks=False)
|
|
793
791
|
except Exception as e:
|
|
794
792
|
self.debug('hook failed', obj={'name': hook_type, 'fun': fun}, sub=sub) # noqa: E501
|
|
795
793
|
error = Error.from_exception(e, message=f'Hook "{fun}" execution failed')
|
secator/runners/command.py
CHANGED
|
@@ -2,11 +2,13 @@ import copy
|
|
|
2
2
|
import getpass
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import queue
|
|
5
6
|
import re
|
|
6
7
|
import shlex
|
|
7
8
|
import signal
|
|
8
9
|
import subprocess
|
|
9
10
|
import sys
|
|
11
|
+
import threading
|
|
10
12
|
|
|
11
13
|
from time import time
|
|
12
14
|
|
|
@@ -178,6 +180,13 @@ class Command(Runner):
|
|
|
178
180
|
# Process
|
|
179
181
|
self.process = None
|
|
180
182
|
|
|
183
|
+
# Monitor thread (lazy initialization)
|
|
184
|
+
self.monitor_thread = None
|
|
185
|
+
self.monitor_stop_event = None
|
|
186
|
+
self.monitor_queue = None
|
|
187
|
+
self.process_start_time = None
|
|
188
|
+
# self.retry_count = 0 # TODO: remove this
|
|
189
|
+
|
|
181
190
|
# Sudo
|
|
182
191
|
self.requires_sudo = False
|
|
183
192
|
|
|
@@ -205,6 +214,13 @@ class Command(Runner):
|
|
|
205
214
|
item_loaders.append(instance_func)
|
|
206
215
|
self.item_loaders = item_loaders
|
|
207
216
|
|
|
217
|
+
def _init_monitor_objects(self):
|
|
218
|
+
"""Initialize monitor thread objects when needed (lazy initialization)."""
|
|
219
|
+
if self.monitor_stop_event is None:
|
|
220
|
+
self.monitor_stop_event = threading.Event()
|
|
221
|
+
if self.monitor_queue is None:
|
|
222
|
+
self.monitor_queue = queue.Queue()
|
|
223
|
+
|
|
208
224
|
def toDict(self):
|
|
209
225
|
res = super().toDict()
|
|
210
226
|
res.update({
|
|
@@ -419,10 +435,13 @@ class Command(Runner):
|
|
|
419
435
|
self.print_command()
|
|
420
436
|
|
|
421
437
|
# Check for sudo requirements and prepare the password if needed
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
sudo_required = re.search(r'\bsudo\b', self.cmd)
|
|
439
|
+
sudo_password = None
|
|
440
|
+
if sudo_required:
|
|
441
|
+
sudo_password, error = self._prompt_sudo(self.cmd)
|
|
442
|
+
if error:
|
|
443
|
+
yield Error(message=error)
|
|
444
|
+
return
|
|
426
445
|
|
|
427
446
|
# Prepare cmds
|
|
428
447
|
command = self.cmd if self.shell else shlex.split(self.cmd)
|
|
@@ -440,7 +459,7 @@ class Command(Runner):
|
|
|
440
459
|
# Output and results
|
|
441
460
|
self.return_code = 0
|
|
442
461
|
self.killed = False
|
|
443
|
-
self.memory_limit_mb = CONFIG.
|
|
462
|
+
self.memory_limit_mb = CONFIG.celery.task_memory_limit_mb
|
|
444
463
|
|
|
445
464
|
# Run the command using subprocess
|
|
446
465
|
env = os.environ
|
|
@@ -450,11 +469,18 @@ class Command(Runner):
|
|
|
450
469
|
stdout=subprocess.PIPE,
|
|
451
470
|
stderr=subprocess.STDOUT,
|
|
452
471
|
universal_newlines=True,
|
|
453
|
-
preexec_fn=os.setsid,
|
|
472
|
+
preexec_fn=os.setsid if not sudo_required else None,
|
|
454
473
|
shell=self.shell,
|
|
455
474
|
env=env,
|
|
456
475
|
cwd=self.cwd)
|
|
457
476
|
|
|
477
|
+
# Initialize monitor objects and start monitor thread
|
|
478
|
+
self._init_monitor_objects()
|
|
479
|
+
self.process_start_time = time()
|
|
480
|
+
self.monitor_stop_event.clear()
|
|
481
|
+
self.monitor_thread = threading.Thread(target=self._monitor_process, daemon=True)
|
|
482
|
+
self.monitor_thread.start()
|
|
483
|
+
|
|
458
484
|
# If sudo password is provided, send it to stdin
|
|
459
485
|
if sudo_password:
|
|
460
486
|
self.process.stdin.write(f"{sudo_password}\n")
|
|
@@ -466,6 +492,7 @@ class Command(Runner):
|
|
|
466
492
|
if not line:
|
|
467
493
|
break
|
|
468
494
|
yield from self.process_line(line)
|
|
495
|
+
yield from self.process_monitor_queue()
|
|
469
496
|
|
|
470
497
|
# Run hooks after cmd has completed successfully
|
|
471
498
|
result = self.run_hooks('on_cmd_done', sub='end')
|
|
@@ -475,11 +502,6 @@ class Command(Runner):
|
|
|
475
502
|
except FileNotFoundError as e:
|
|
476
503
|
yield from self.handle_file_not_found(e)
|
|
477
504
|
|
|
478
|
-
except MemoryError as e:
|
|
479
|
-
self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
|
|
480
|
-
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
481
|
-
yield Warning(message=f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
|
|
482
|
-
|
|
483
505
|
except BaseException as e:
|
|
484
506
|
self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
|
|
485
507
|
self.stop_process()
|
|
@@ -529,13 +551,16 @@ class Command(Runner):
|
|
|
529
551
|
if self.no_process:
|
|
530
552
|
return
|
|
531
553
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if self.
|
|
554
|
+
def process_monitor_queue(self):
|
|
555
|
+
"""Process and yield any queued items from monitor thread."""
|
|
556
|
+
if self.monitor_queue is None:
|
|
535
557
|
return
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
558
|
+
while not self.monitor_queue.empty():
|
|
559
|
+
try:
|
|
560
|
+
monitor_item = self.monitor_queue.get_nowait()
|
|
561
|
+
yield monitor_item
|
|
562
|
+
except queue.Empty:
|
|
563
|
+
break
|
|
539
564
|
|
|
540
565
|
def print_description(self):
|
|
541
566
|
"""Print description"""
|
|
@@ -582,6 +607,100 @@ class Command(Runner):
|
|
|
582
607
|
if exit_ok:
|
|
583
608
|
self.exit_ok = True
|
|
584
609
|
|
|
610
|
+
def _stop_monitor_thread(self):
|
|
611
|
+
"""Stop monitor thread."""
|
|
612
|
+
if self.monitor_thread and self.monitor_thread.is_alive() and self.monitor_stop_event:
|
|
613
|
+
self.monitor_stop_event.set()
|
|
614
|
+
self.monitor_thread.join(timeout=2.0)
|
|
615
|
+
|
|
616
|
+
def _monitor_process(self):
|
|
617
|
+
"""Monitor thread that checks process health and kills if necessary."""
|
|
618
|
+
last_stats_time = 0
|
|
619
|
+
|
|
620
|
+
while not self.monitor_stop_event.is_set():
|
|
621
|
+
if not self.process or not self.process.pid:
|
|
622
|
+
break
|
|
623
|
+
|
|
624
|
+
try:
|
|
625
|
+
current_time = time()
|
|
626
|
+
self.debug('Collecting monitor items', sub='monitor')
|
|
627
|
+
|
|
628
|
+
# Collect and queue stats at regular intervals
|
|
629
|
+
if (current_time - last_stats_time) >= CONFIG.runners.stat_update_frequency:
|
|
630
|
+
stats_items = list(self._collect_stats())
|
|
631
|
+
for stat_item in stats_items:
|
|
632
|
+
if self.monitor_queue is not None:
|
|
633
|
+
self.monitor_queue.put(stat_item)
|
|
634
|
+
last_stats_time = current_time
|
|
635
|
+
|
|
636
|
+
# Check memory usage from collected stats
|
|
637
|
+
if self.memory_limit_mb and self.memory_limit_mb != -1:
|
|
638
|
+
total_mem = sum(stat_item.extra_data.get('memory_info', {}).get('rss', 0) / 1024 / 1024 for stat_item in stats_items) # noqa: E501
|
|
639
|
+
if total_mem > self.memory_limit_mb:
|
|
640
|
+
warning = Warning(message=f'Memory limit {self.memory_limit_mb}MB exceeded (actual: {total_mem:.2f}MB)')
|
|
641
|
+
if self.monitor_queue is not None:
|
|
642
|
+
self.monitor_queue.put(warning)
|
|
643
|
+
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
644
|
+
break
|
|
645
|
+
|
|
646
|
+
# Check execution time
|
|
647
|
+
if self.process_start_time and CONFIG.celery.task_max_timeout != -1:
|
|
648
|
+
elapsed_time = current_time - self.process_start_time
|
|
649
|
+
if elapsed_time > CONFIG.celery.task_max_timeout:
|
|
650
|
+
warning = Warning(message=f'Task timeout {CONFIG.celery.task_max_timeout}s exceeded')
|
|
651
|
+
if self.monitor_queue is not None:
|
|
652
|
+
self.monitor_queue.put(warning)
|
|
653
|
+
self.stop_process(exit_ok=True, sig=signal.SIGTERM)
|
|
654
|
+
break
|
|
655
|
+
|
|
656
|
+
# Check retry count
|
|
657
|
+
# TODO: remove this
|
|
658
|
+
# if CONFIG.celery.task_max_retries and self.retry_count >= CONFIG.celery.task_max_retries:
|
|
659
|
+
# warning = Warning(message=f'Max retries {CONFIG.celery.task_max_retries} exceeded (actual: {self.retry_count})')
|
|
660
|
+
# self.monitor_queue.put(warning)
|
|
661
|
+
# self.stop_process(exit_ok=False, sig=signal.SIGTERM)
|
|
662
|
+
# break
|
|
663
|
+
|
|
664
|
+
except Exception as e:
|
|
665
|
+
self.debug(f'Monitor thread error: {e}', sub='monitor')
|
|
666
|
+
warning = Warning(message=f'Monitor thread error: {e}')
|
|
667
|
+
if self.monitor_queue is not None:
|
|
668
|
+
self.monitor_queue.put(warning)
|
|
669
|
+
break
|
|
670
|
+
|
|
671
|
+
# Sleep for a short interval before next check (stat update frequency)
|
|
672
|
+
self.monitor_stop_event.wait(CONFIG.runners.stat_update_frequency)
|
|
673
|
+
|
|
674
|
+
def _collect_stats(self):
|
|
675
|
+
"""Collect stats about the current running process, if any."""
|
|
676
|
+
if not self.process or not self.process.pid:
|
|
677
|
+
return
|
|
678
|
+
proc = psutil.Process(self.process.pid)
|
|
679
|
+
stats = Command.get_process_info(proc, children=True)
|
|
680
|
+
total_mem = 0
|
|
681
|
+
for info in stats:
|
|
682
|
+
name = info['name']
|
|
683
|
+
pid = info['pid']
|
|
684
|
+
cpu_percent = info['cpu_percent']
|
|
685
|
+
# mem_percent = info['memory_percent']
|
|
686
|
+
mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
|
|
687
|
+
total_mem += mem_rss
|
|
688
|
+
self.debug(f'{name} {pid} {mem_rss}MB', sub='monitor')
|
|
689
|
+
net_conns = info.get('net_connections') or []
|
|
690
|
+
extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
|
|
691
|
+
yield Stat(
|
|
692
|
+
name=name,
|
|
693
|
+
pid=pid,
|
|
694
|
+
cpu=cpu_percent,
|
|
695
|
+
memory=mem_rss,
|
|
696
|
+
memory_limit=self.memory_limit_mb,
|
|
697
|
+
net_conns=len(net_conns),
|
|
698
|
+
extra_data=extra_data
|
|
699
|
+
)
|
|
700
|
+
# self.debug(f'Total mem: {total_mem}MB, memory limit: {self.memory_limit_mb}', sub='monitor')
|
|
701
|
+
# if self.memory_limit_mb and self.memory_limit_mb != -1 and total_mem > self.memory_limit_mb:
|
|
702
|
+
# raise MemoryError(f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
|
|
703
|
+
|
|
585
704
|
def stats(self, memory_limit_mb=None):
|
|
586
705
|
"""Gather stats about the current running process, if any."""
|
|
587
706
|
if not self.process or not self.process.pid:
|
|
@@ -596,7 +715,7 @@ class Command(Runner):
|
|
|
596
715
|
mem_percent = info['memory_percent']
|
|
597
716
|
mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
|
|
598
717
|
total_mem += mem_rss
|
|
599
|
-
self.debug(f'{name} {pid} {mem_rss}MB', sub='stats')
|
|
718
|
+
self.debug(f'process: {name} pid: {pid} memory: {mem_rss}MB', sub='stats')
|
|
600
719
|
net_conns = info.get('net_connections') or []
|
|
601
720
|
extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
|
|
602
721
|
yield Stat(
|
|
@@ -688,7 +807,7 @@ class Command(Runner):
|
|
|
688
807
|
['sudo', '-S', '-p', '', 'true'],
|
|
689
808
|
input=sudo_password + "\n",
|
|
690
809
|
text=True,
|
|
691
|
-
capture_output=True
|
|
810
|
+
capture_output=True,
|
|
692
811
|
)
|
|
693
812
|
if result.returncode == 0:
|
|
694
813
|
return sudo_password, None # Password is correct
|
|
@@ -698,6 +817,8 @@ class Command(Runner):
|
|
|
698
817
|
|
|
699
818
|
def _wait_for_end(self):
|
|
700
819
|
"""Wait for process to finish and process output and return code."""
|
|
820
|
+
self._stop_monitor_thread()
|
|
821
|
+
yield from self.process_monitor_queue()
|
|
701
822
|
if not self.process:
|
|
702
823
|
return
|
|
703
824
|
for line in self.process.stdout.readlines():
|
secator/tasks/bup.py
CHANGED
secator/tasks/fping.py
CHANGED
|
@@ -17,7 +17,10 @@ class fping(ReconIp):
|
|
|
17
17
|
file_flag = '-f'
|
|
18
18
|
input_flag = None
|
|
19
19
|
opts = {
|
|
20
|
-
'
|
|
20
|
+
'count': {'type': int, 'default': None, 'help': 'Number of request packets to send to each target'},
|
|
21
|
+
'show_name': {'is_flag': True, 'default': False, 'help': 'Show network addresses as well as hostnames'},
|
|
22
|
+
'use_dns': {'is_flag': True, 'default': False, 'help': 'Use DNS to lookup address of return packet (same as -n but will force reverse-DNS lookup for hostnames)'}, # noqa: E501
|
|
23
|
+
'summary': {'is_flag': True, 'default': False, 'help': 'Print cumulative statistics upon exit'},
|
|
21
24
|
}
|
|
22
25
|
opt_prefix = '--'
|
|
23
26
|
opt_key_map = {
|
|
@@ -27,11 +30,14 @@ class fping(ReconIp):
|
|
|
27
30
|
RETRIES: 'retry',
|
|
28
31
|
TIMEOUT: 'timeout',
|
|
29
32
|
THREADS: OPT_NOT_SUPPORTED,
|
|
30
|
-
'
|
|
33
|
+
'count': '-c',
|
|
34
|
+
'show_name': '-n',
|
|
35
|
+
'use_dns': '-d',
|
|
36
|
+
'summary': '-s',
|
|
31
37
|
}
|
|
32
38
|
opt_value_map = {
|
|
33
|
-
DELAY: lambda x: x * 1000, # convert s to ms
|
|
34
|
-
TIMEOUT: lambda x: x * 1000 # convert s to ms
|
|
39
|
+
DELAY: lambda x: int(x) * 1000, # convert s to ms
|
|
40
|
+
TIMEOUT: lambda x: int(x) * 1000 # convert s to ms
|
|
35
41
|
}
|
|
36
42
|
install_github_handle = 'schweikert/fping'
|
|
37
43
|
install_version = 'v5.1'
|
|
@@ -41,9 +47,20 @@ class fping(ReconIp):
|
|
|
41
47
|
@staticmethod
|
|
42
48
|
def item_loader(self, line):
|
|
43
49
|
if '(' in line:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
|
|
51
|
+
line_part = line.split(' : ')[0] if ' : ' in line else line # Removing the stat parts that appears when using -c
|
|
52
|
+
|
|
53
|
+
start_paren = line_part.find('(')
|
|
54
|
+
end_paren = line_part.find(')', start_paren)
|
|
55
|
+
|
|
56
|
+
if start_paren != -1 and end_paren != -1:
|
|
57
|
+
host = line_part[:start_paren].strip()
|
|
58
|
+
ip = line_part[start_paren+1:end_paren].strip()
|
|
59
|
+
|
|
60
|
+
if (validators.ipv4(host) or validators.ipv6(host)):
|
|
61
|
+
host = ''
|
|
62
|
+
else:
|
|
63
|
+
return
|
|
47
64
|
else:
|
|
48
65
|
ip = line.strip()
|
|
49
66
|
host = ''
|
secator/tasks/maigret.py
CHANGED
|
@@ -42,8 +42,13 @@ class maigret(ReconUser):
|
|
|
42
42
|
EXTRA_DATA: lambda x: x['status'].get('ids', {})
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
install_version = '0.5.
|
|
46
|
-
|
|
45
|
+
install_version = '0.5.0'
|
|
46
|
+
# install_pre = {
|
|
47
|
+
# 'apt': ['libcairo2-dev'],
|
|
48
|
+
# 'yum|zypper': ['cairo-devel'],
|
|
49
|
+
# '*': ['cairo']
|
|
50
|
+
# }
|
|
51
|
+
install_cmd = 'pipx install maigret==[install_version] --force'
|
|
47
52
|
socks5_proxy = True
|
|
48
53
|
profile = 'io'
|
|
49
54
|
|
secator/tasks/wpscan.py
CHANGED
|
@@ -11,6 +11,7 @@ from secator.definitions import (CONFIDENCE, CVSS_SCORE, DELAY, DESCRIPTION,
|
|
|
11
11
|
URL, USER_AGENT)
|
|
12
12
|
from secator.output_types import Tag, Vulnerability, Info, Error
|
|
13
13
|
from secator.tasks._categories import VulnHttp
|
|
14
|
+
from secator.installer import parse_version
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@task()
|
|
@@ -110,6 +111,12 @@ class wpscan(VulnHttp):
|
|
|
110
111
|
# Get URL
|
|
111
112
|
target = data.get('target_url', self.inputs[0])
|
|
112
113
|
|
|
114
|
+
# Get errors
|
|
115
|
+
scan_aborted = data.get('scan_aborted', False)
|
|
116
|
+
if scan_aborted:
|
|
117
|
+
yield Error(message=scan_aborted, traceback='\n'.join(data.get('trace', [])))
|
|
118
|
+
return
|
|
119
|
+
|
|
113
120
|
# Wordpress version
|
|
114
121
|
version = data.get('version', {})
|
|
115
122
|
if version:
|
|
@@ -133,7 +140,7 @@ class wpscan(VulnHttp):
|
|
|
133
140
|
location = main_theme['location']
|
|
134
141
|
if version:
|
|
135
142
|
number = version['number']
|
|
136
|
-
latest_version = main_theme.get('latest_version')
|
|
143
|
+
latest_version = main_theme.get('latest_version') or 'unknown'
|
|
137
144
|
yield Tag(
|
|
138
145
|
name=f'Wordpress theme - {slug} {number}',
|
|
139
146
|
match=target,
|
|
@@ -142,10 +149,12 @@ class wpscan(VulnHttp):
|
|
|
142
149
|
'latest_version': latest_version
|
|
143
150
|
}
|
|
144
151
|
)
|
|
145
|
-
|
|
152
|
+
outdated = latest_version and parse_version(number) < parse_version(latest_version)
|
|
153
|
+
if outdated:
|
|
146
154
|
yield Vulnerability(
|
|
147
155
|
matched_at=target,
|
|
148
156
|
name=f'Wordpress theme - {slug} {number} outdated',
|
|
157
|
+
description=f'The wordpress theme {slug} is outdated, consider updating to the latest version {latest_version}',
|
|
149
158
|
confidence='high',
|
|
150
159
|
severity='info'
|
|
151
160
|
)
|
|
@@ -163,7 +172,7 @@ class wpscan(VulnHttp):
|
|
|
163
172
|
location = data['location']
|
|
164
173
|
if version:
|
|
165
174
|
number = version['number']
|
|
166
|
-
latest_version = data.get('latest_version')
|
|
175
|
+
latest_version = data.get('latest_version') or 'unknown'
|
|
167
176
|
yield Tag(
|
|
168
177
|
name=f'Wordpress plugin - {slug} {number}',
|
|
169
178
|
match=target,
|
|
@@ -172,10 +181,12 @@ class wpscan(VulnHttp):
|
|
|
172
181
|
'latest_version': latest_version
|
|
173
182
|
}
|
|
174
183
|
)
|
|
175
|
-
|
|
184
|
+
outdated = latest_version and parse_version(number) < parse_version(latest_version)
|
|
185
|
+
if outdated:
|
|
176
186
|
yield Vulnerability(
|
|
177
187
|
matched_at=target,
|
|
178
188
|
name=f'Wordpress plugin - {slug} {number} outdated',
|
|
189
|
+
description=f'The wordpress plugin {slug} is outdated, consider updating to the latest version {latest_version}.',
|
|
179
190
|
confidence='high',
|
|
180
191
|
severity='info'
|
|
181
192
|
)
|
|
@@ -6,11 +6,11 @@ secator/celery_utils.py,sha256=vhL5ZxXDn3ODvyVxMijKyUTJ1dOisMDjF_PhFUyOVSA,9451
|
|
|
6
6
|
secator/cli.py,sha256=zmaMa-RhN3ENAPcfltTwUEE3Dobg9kKeVGTxsMN1v1g,61267
|
|
7
7
|
secator/cli_helper.py,sha256=EJFl80fd1HcgMYbmiddMZssCD32YDiFLnr-UbLp61aQ,13720
|
|
8
8
|
secator/click.py,sha256=pg7XPI7-wAhhEhd4aeAC8vHSqKi-H0zeFRlh0T-ayYg,2662
|
|
9
|
-
secator/config.py,sha256=
|
|
9
|
+
secator/config.py,sha256=pEBob-af1OG7aMb1esVgWotFde0bJobWOoGtyfCE5NI,20846
|
|
10
10
|
secator/cve.py,sha256=j47VOGyZjOvCY_xwVYS9fiXQPKHL5bPRtCnVAmbQthE,21356
|
|
11
11
|
secator/decorators.py,sha256=uygU8MguxEO0BKXRvF4Nn2QEDnjqdIer8ReBj_j9ALg,88
|
|
12
12
|
secator/definitions.py,sha256=sJaR9e_4aEgAo7cVzYQcD2lotXQPN_3lze_qWhKvo1M,3275
|
|
13
|
-
secator/installer.py,sha256
|
|
13
|
+
secator/installer.py,sha256=THNtVyyVZm0OtUcUx4tRZUBWvB5kuIvkOv-5dGdToEU,21130
|
|
14
14
|
secator/loader.py,sha256=fR0oAdBgZlII8guOmSs_htQq917mUZZIiAzf0fvUq0Y,4139
|
|
15
15
|
secator/report.py,sha256=4lEjW_GzDgsPBe1eQHX4ntcHWs0nsAMIbrNMw0UfWHc,4025
|
|
16
16
|
secator/rich.py,sha256=jITAXV_Wgj32Q7FfkssDN-DMD8TxK1wwlrIlkaCNc70,3960
|
|
@@ -46,8 +46,8 @@ secator/configs/workflows/url_dirsearch.yaml,sha256=_4TdMSVLt2lIbx8ucn0R04tkMUqh
|
|
|
46
46
|
secator/configs/workflows/url_fuzz.yaml,sha256=a-ZvZrcPBaeVhRrxox8fq25SKMJflyAkKWLqJeC3xD4,911
|
|
47
47
|
secator/configs/workflows/url_params_fuzz.yaml,sha256=ufGbW4GUtEZee0M1WPVo0w6ZCEH6xmuDO6VCjPaw8AQ,796
|
|
48
48
|
secator/configs/workflows/url_vuln.yaml,sha256=35uY0SpQGgaPulkBkQUcy0AdVwjslEJfVGhM9DQAXkk,1817
|
|
49
|
-
secator/configs/workflows/user_hunt.yaml,sha256=
|
|
50
|
-
secator/configs/workflows/wordpress.yaml,sha256=
|
|
49
|
+
secator/configs/workflows/user_hunt.yaml,sha256=6XyiG-MnAdYmQsLe6qSqvT_8zFVisZAp-zJAu9ASv1U,485
|
|
50
|
+
secator/configs/workflows/wordpress.yaml,sha256=1Lv23G3jcnkbfHzTMBEcyWG2baQ0Pdo3-pYXiwl82nY,525
|
|
51
51
|
secator/exporters/__init__.py,sha256=PnT9Ra4ArHt9VQTK5Cpc4CPY89XRwLLUGtZ8nUcknm0,415
|
|
52
52
|
secator/exporters/_base.py,sha256=wM1UT1PsSP1gX4gylvpQjBeAsk59F2Q2eFrt7AFU7jM,68
|
|
53
53
|
secator/exporters/console.py,sha256=vbmSln4UrIpzjCQCs6JdZ2VRxjX8qQ1gznCPx89xbX0,263
|
|
@@ -69,7 +69,7 @@ secator/output_types/ip.py,sha256=rKSv6yAu9AKZ3nKBh7pzRg8eai8d1fE36vKUAMjyMJ8,12
|
|
|
69
69
|
secator/output_types/port.py,sha256=JdqXnEF8XuwaWFMT8Vghj7fKLwtsImuUdRfMmITgmWM,1879
|
|
70
70
|
secator/output_types/progress.py,sha256=D598UDh4VBcSLetOjcd-DY1H_EVgymQXVgbMK6FE-ZY,1228
|
|
71
71
|
secator/output_types/record.py,sha256=ehbJ-4rcVuFUxof5RXkYcuoh34DdnJEeFZazPQN4QKo,1265
|
|
72
|
-
secator/output_types/stat.py,sha256=
|
|
72
|
+
secator/output_types/stat.py,sha256=hb5s8eUs7OQAaxLSo-DAs0HtZcl7CflPxjmzVks1n94,1470
|
|
73
73
|
secator/output_types/state.py,sha256=kY5ArRYpRVfIULj7Qt93Lx8YeeIEMa1Ke7q8vnK0Yzk,996
|
|
74
74
|
secator/output_types/subdomain.py,sha256=1pWVXGKA6r7IWaBSt0TRe4tC3tEVbBsRQBettr0FEH8,1359
|
|
75
75
|
secator/output_types/tag.py,sha256=4-khdI4W9tahW3G4YPh1WEWKHoOYW69M9UDtzPlrqnU,1656
|
|
@@ -79,10 +79,10 @@ secator/output_types/user_account.py,sha256=IqPg0nfKzSsxA5DerLA3PEWIN9HscV_D7PRK
|
|
|
79
79
|
secator/output_types/vulnerability.py,sha256=cuS5r_BKFuO-DQlrSEiN7elmunwlu2sdC4Rt9WDa10g,2864
|
|
80
80
|
secator/output_types/warning.py,sha256=iy949Aj5OXJLWif7HFB5EvjcYrgKHAzIP9ffyLTV7LA,830
|
|
81
81
|
secator/runners/__init__.py,sha256=EBbOk37vkBy9p8Hhrbi-2VtM_rTwQ3b-0ggTyiD22cE,290
|
|
82
|
-
secator/runners/_base.py,sha256=
|
|
82
|
+
secator/runners/_base.py,sha256=5x9A10wGNV8h9hqYOIXW_4ftl6y9Qke_L08D0uF4h1c,40640
|
|
83
83
|
secator/runners/_helpers.py,sha256=TeebZnpo4cp-9tpgPlDoFm_gmr00_CERAC1aOYhTzA4,6281
|
|
84
84
|
secator/runners/celery.py,sha256=bqvDTTdoHiGRCt0FRvlgFHQ_nsjKMP5P0PzGbwfCj_0,425
|
|
85
|
-
secator/runners/command.py,sha256=
|
|
85
|
+
secator/runners/command.py,sha256=8_neWdq6jk7E1FIFM44Zv7TZpD_xFAT-uwxAzxdF6oc,35755
|
|
86
86
|
secator/runners/scan.py,sha256=axT_OmGhixogCPMUS1OUeMLnFtk8PxY7zL9NYCugFVU,2578
|
|
87
87
|
secator/runners/task.py,sha256=PrkVns8UAGht2JbCmCUWycA6B39Z5oeMmAMq69KtXKI,2199
|
|
88
88
|
secator/runners/workflow.py,sha256=YnpTSdmp54d55vORe4khWLSx2J7gtDFNryKfZXYAWnY,6076
|
|
@@ -95,14 +95,14 @@ secator/tasks/__init__.py,sha256=Op0O0Aa8c124AfDG-cEB9VLRsXZ1wXTpVrT3g-wxMNg,184
|
|
|
95
95
|
secator/tasks/_categories.py,sha256=ZmUNzeFIZ9-_er9sLJw66PTYIL5nO799JQU3EoW-6nE,15394
|
|
96
96
|
secator/tasks/arjun.py,sha256=WdRZtTCd2Ejbv5HlLS_FoWVKgGpMsR6RCDekV2kR788,3061
|
|
97
97
|
secator/tasks/bbot.py,sha256=moIkwd52jCKaeg1v6Nv4Gfmd4GPObo9c9nwOzQvf-2M,9236
|
|
98
|
-
secator/tasks/bup.py,sha256=
|
|
98
|
+
secator/tasks/bup.py,sha256=DXfOvtGJMe17RN9t764X0eZ__lKKz7evlI4Bl91IKGA,3867
|
|
99
99
|
secator/tasks/cariddi.py,sha256=pc1z6FWFV4dauSJxWL9BKD-MXjCo14sgcNtAkGuKy5I,5194
|
|
100
100
|
secator/tasks/dalfox.py,sha256=dllEP9A8-7YaX12fGRmLMltfNjm_9Us6wYoS86C_VO0,2507
|
|
101
101
|
secator/tasks/dirsearch.py,sha256=-oa2P2Pq8LjF61PguUEtjgr9rgvVpGLzRZRDXIJMswY,2453
|
|
102
102
|
secator/tasks/dnsx.py,sha256=2qNC-wSjS33geuHMOwuBapLwKEvWTlDgnmvM67ZSJVA,4220
|
|
103
103
|
secator/tasks/feroxbuster.py,sha256=H7_WT8B0cPIBeq7FOownpQlrZ468R07zRLqrDLNCkg8,3006
|
|
104
104
|
secator/tasks/ffuf.py,sha256=L2Rb34YIH30CFJacvaY8QVF1gDah9E0nNCdgAHWL9jo,4103
|
|
105
|
-
secator/tasks/fping.py,sha256=
|
|
105
|
+
secator/tasks/fping.py,sha256=8OMb5UxjLErkRW5G_kie9cS0mVtIKCKi1o8asZ_UUjs,2324
|
|
106
106
|
secator/tasks/gau.py,sha256=SJaza2yQoMeJeE6TOCRrRv0udbwRIoiXX4gRE64GXoU,1804
|
|
107
107
|
secator/tasks/gf.py,sha256=svNRzaBr_DYW3QGFoPmUBWZh0Xm07XDS2bbNH-tfcA4,1028
|
|
108
108
|
secator/tasks/gitleaks.py,sha256=cajL0NDm7dRFpcq4fJOCSkQMpquUiOy9HODq93h36Xg,2638
|
|
@@ -111,7 +111,7 @@ secator/tasks/grype.py,sha256=OasQs5WQwgt--o6M2_uh3RYZZaA3-difweCS46Uc5-w,2573
|
|
|
111
111
|
secator/tasks/h8mail.py,sha256=XsDnL8LPk_jIHfJhqeYMj2423epk0NADorjd_JhBa9o,2033
|
|
112
112
|
secator/tasks/httpx.py,sha256=0Umt2ouL36TELxmoaZ4dKSGXgipN3ve__IQFgUKrWZQ,6498
|
|
113
113
|
secator/tasks/katana.py,sha256=10Sml1d0bO_UDwT1y_4TDQ_a0ihWKAW6L6-n8M9ArUw,6220
|
|
114
|
-
secator/tasks/maigret.py,sha256=
|
|
114
|
+
secator/tasks/maigret.py,sha256=4QSAn3ccHTvM5DMe6IlMQLi3-AV81X_XSEcrdxkbXc0,2270
|
|
115
115
|
secator/tasks/mapcidr.py,sha256=tMTHQspHSs92F4R-9HVYjFBpiu9ZhxoJSNvpd8KwKKc,1057
|
|
116
116
|
secator/tasks/msfconsole.py,sha256=3VjAEpwEAFDcGxyYMhKyDLHRObXELYFx_H306fzmtMw,6566
|
|
117
117
|
secator/tasks/naabu.py,sha256=Z8kYvAMeeSLrhnojLRx8GzxvwhFhDCfDj9a7r9Wbr1A,2407
|
|
@@ -123,10 +123,10 @@ secator/tasks/testssl.py,sha256=rrpKerOYcNA4NJr9RQ_uAtAbl3W50FRp3bPo3yD8EEg,8787
|
|
|
123
123
|
secator/tasks/trivy.py,sha256=loIVQeHlYzz-_mVI6-HrL1O3H-22LA0vl4J9Ja_fy2I,3307
|
|
124
124
|
secator/tasks/wafw00f.py,sha256=9CnV9F7ZrykO27F3PAb5HtwULDMYEKGSTbz-jh0kc2g,3189
|
|
125
125
|
secator/tasks/wpprobe.py,sha256=1QPJ-7JvhL7LFvjUTAmqpH2Krp-Qmi079lonso16YPQ,3229
|
|
126
|
-
secator/tasks/wpscan.py,sha256=
|
|
126
|
+
secator/tasks/wpscan.py,sha256=vRhtNfU6nSlKIZ8_kWFyDjrUnjSmkfGeTMkKYwN0CXU,6441
|
|
127
127
|
secator/workflows/__init__.py,sha256=XOviyjSylZ4cuVmmQ76yuqZRdmvOEghqAnuw_4cLmfk,702
|
|
128
|
-
secator-0.
|
|
129
|
-
secator-0.
|
|
130
|
-
secator-0.
|
|
131
|
-
secator-0.
|
|
132
|
-
secator-0.
|
|
128
|
+
secator-0.19.0.dist-info/METADATA,sha256=xk-XJAU7UoZxJM-50xGjZvQcBN24L20qhNE1y4Og720,17253
|
|
129
|
+
secator-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
130
|
+
secator-0.19.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
|
|
131
|
+
secator-0.19.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
|
|
132
|
+
secator-0.19.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|