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 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://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/directory-list-2.3-small.txt', # noqa: E501
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
- maigret:
12
- description: Hunt user accounts
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.autoinstall_commands and IN_CELERY_WORKER_PROCESS:
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}'
@@ -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={self.pid}] [cpu={self.cpu:.2f}%] [memory={self.memory:.2f}%]'
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} \[pid={self.pid}] \[cpu={self.cpu:.2f}%] \[memory={self.memory:.2f}%]'
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' \[connections={self.net_conns}]'
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('adding prior results from MongoDB', sub='init')
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 dynamic opts ({len(self.dynamic_opts)})', obj=self.dynamic_opts, sub='init')
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
- # Keep existing ancestor id in context
467
- ancestor_id = item._context.get('ancestor_id', None)
468
-
469
- # Set context
470
- item._context.update(self.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')
@@ -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
- sudo_password, error = self._prompt_sudo(self.cmd)
423
- if error:
424
- yield Error(message=error)
425
- return
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.security.memory_limit_mb
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
- # Yield command stats (CPU, memory, conns ...)
533
- # TODO: enable stats support with timer
534
- if self.last_updated_stat and (time() - self.last_updated_stat) < CONFIG.runners.stat_update_frequency:
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
- yield from self.stats(self.memory_limit_mb)
538
- self.last_updated_stat = time()
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
@@ -20,7 +20,7 @@ class bup(Http):
20
20
  output_types = [Url, Progress]
21
21
  tags = ['url', 'bypass']
22
22
  input_flag = '-u'
23
- file_flag = '-R'
23
+ file_flag = '-u'
24
24
  json_flag = '--jsonl'
25
25
  opt_prefix = '--'
26
26
  opts = {
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
- 'reverse_dns': {'is_flag': True, 'default': False, 'short': 'r', 'help': 'Reverse DNS lookup (slower)'}
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
- 'reverse_dns': 'r'
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
- host, ip = tuple(t.strip() for t in line.rstrip(')').split('('))
45
- if (validators.ipv4(host) or validators.ipv6(host)):
46
- host = ''
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.0a'
46
- install_cmd = 'pipx install git+https://github.com/soxoj/maigret --force'
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
- if (latest_version and number < latest_version):
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
- if (latest_version and number < latest_version):
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
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secator
3
- Version: 0.17.0
3
+ Version: 0.19.0
4
4
  Summary: The pentester's swiss knife.
5
5
  Project-URL: Homepage, https://github.com/freelabz/secator
6
6
  Project-URL: Issues, https://github.com/freelabz/secator/issues
@@ -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=6CjGLMNNFWg1PAvW16v8SGceyngFs2SCEKjJhV2NtOU,20805
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=-gw6jSCCezuRgKdrlKYYK7UIORP4OWyx69bohM-8tfc,21129
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=WX3bpsPWexLXs5bF-OkniPwm8T6fXws7f284Zrybi8I,189
50
- secator/configs/workflows/wordpress.yaml,sha256=n-I1uNZEPS6oVmF7Rn996K85csSenTtoVycJt0PWnzk,340
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=YpKZRP5fge42oTmlWSYEfixDPx764g-5aVBeilQM0io,1263
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=IkAQfPzz_kou5Pa82y-2Wmtp_lIudKMc9ix8_NP4370,40663
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=A--1cFpzHAxvzO0xvpMSyY0Tb0OZxkJc8HsJaTNsfB0,31096
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=9IXsCqMdhOeZcCsQB2L4IJ3Kzm2oQKDE7mflGljm0lM,3867
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=uTOq24DcNQpNgpXQlFV4xxBdn8P9gJWM5mmhkobqW-Y,1575
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=jjuyR8lAZYUybmN8SwEj3hrRB25p9xm4X_361auZK_Q,2173
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=dBkbG9EODHDUBAA8uNVULX4SdVgTCAi_F1T1oCfRbsI,5852
126
+ secator/tasks/wpscan.py,sha256=vRhtNfU6nSlKIZ8_kWFyDjrUnjSmkfGeTMkKYwN0CXU,6441
127
127
  secator/workflows/__init__.py,sha256=XOviyjSylZ4cuVmmQ76yuqZRdmvOEghqAnuw_4cLmfk,702
128
- secator-0.17.0.dist-info/METADATA,sha256=FPBlaaLaBXMP4i2-bz5q39-Z2i_LVn3ezeu-xNuC0Ro,17253
129
- secator-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- secator-0.17.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
131
- secator-0.17.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
132
- secator-0.17.0.dist-info/RECORD,,
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,,