secator 0.16.5__py3-none-any.whl → 0.18.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.

Files changed (41) hide show
  1. secator/celery.py +75 -9
  2. secator/celery_signals.py +2 -1
  3. secator/cli.py +7 -1
  4. secator/config.py +2 -1
  5. secator/configs/workflows/user_hunt.yaml +14 -2
  6. secator/configs/workflows/wordpress.yaml +13 -0
  7. secator/hooks/gcs.py +11 -1
  8. secator/hooks/mongodb.py +71 -66
  9. secator/installer.py +1 -1
  10. secator/output_types/certificate.py +1 -1
  11. secator/output_types/exploit.py +1 -1
  12. secator/output_types/ip.py +1 -1
  13. secator/output_types/progress.py +1 -1
  14. secator/output_types/record.py +1 -1
  15. secator/output_types/stat.py +1 -1
  16. secator/output_types/state.py +1 -1
  17. secator/output_types/subdomain.py +1 -1
  18. secator/output_types/tag.py +1 -1
  19. secator/output_types/target.py +1 -1
  20. secator/output_types/user_account.py +1 -1
  21. secator/output_types/vulnerability.py +1 -1
  22. secator/runners/_base.py +8 -8
  23. secator/runners/command.py +29 -11
  24. secator/tasks/bup.py +1 -1
  25. secator/tasks/cariddi.py +37 -1
  26. secator/tasks/dalfox.py +2 -2
  27. secator/tasks/dirsearch.py +0 -1
  28. secator/tasks/feroxbuster.py +0 -1
  29. secator/tasks/ffuf.py +0 -1
  30. secator/tasks/fping.py +24 -7
  31. secator/tasks/katana.py +3 -0
  32. secator/tasks/maigret.py +7 -2
  33. secator/tasks/naabu.py +1 -2
  34. secator/tasks/nuclei.py +4 -1
  35. secator/tasks/wpscan.py +15 -4
  36. secator/utils.py +9 -0
  37. {secator-0.16.5.dist-info → secator-0.18.0.dist-info}/METADATA +1 -1
  38. {secator-0.16.5.dist-info → secator-0.18.0.dist-info}/RECORD +41 -41
  39. {secator-0.16.5.dist-info → secator-0.18.0.dist-info}/WHEEL +0 -0
  40. {secator-0.16.5.dist-info → secator-0.18.0.dist-info}/entry_points.txt +0 -0
  41. {secator-0.16.5.dist-info → secator-0.18.0.dist-info}/licenses/LICENSE +0 -0
secator/celery.py CHANGED
@@ -1,3 +1,4 @@
1
+ import gc
1
2
  import json
2
3
  import logging
3
4
  import os
@@ -5,6 +6,7 @@ import os
5
6
  from time import time
6
7
 
7
8
  from celery import Celery, chord
9
+ from celery.canvas import signature
8
10
  from celery.app import trace
9
11
 
10
12
  from rich.logging import RichHandler
@@ -61,9 +63,10 @@ app.conf.update({
61
63
  'result_backend': CONFIG.celery.result_backend,
62
64
  'result_expires': CONFIG.celery.result_expires,
63
65
  'result_backend_transport_options': json.loads(CONFIG.celery.result_backend_transport_options) if CONFIG.celery.result_backend_transport_options else {}, # noqa: E501
64
- 'result_extended': True,
66
+ 'result_extended': not CONFIG.addons.mongodb.enabled,
65
67
  'result_backend_thread_safe': True,
66
68
  'result_serializer': 'pickle',
69
+ 'result_accept_content': ['application/x-python-serialize'],
67
70
 
68
71
  # Task config
69
72
  'task_acks_late': CONFIG.celery.task_acks_late,
@@ -81,6 +84,11 @@ app.conf.update({
81
84
  'task_store_eager_result': True,
82
85
  'task_send_sent_event': CONFIG.celery.task_send_sent_event,
83
86
  'task_serializer': 'pickle',
87
+ 'task_accept_content': ['application/x-python-serialize'],
88
+
89
+ # Event config
90
+ 'event_serializer': 'pickle',
91
+ 'event_accept_content': ['application/x-python-serialize'],
84
92
 
85
93
  # Worker config
86
94
  # 'worker_direct': True, # TODO: consider enabling this to allow routing to specific workers
@@ -168,6 +176,12 @@ def run_scan(self, args=[], kwargs={}):
168
176
 
169
177
  @app.task(bind=True)
170
178
  def run_command(self, results, name, targets, opts={}):
179
+ # Set Celery request id in context
180
+ context = opts.get('context', {})
181
+ context['celery_id'] = self.request.id
182
+ context['worker_name'] = os.environ.get('WORKER_NAME', 'unknown')
183
+
184
+ # Set routing key in context
171
185
  if IN_CELERY_WORKER_PROCESS:
172
186
  quiet = not CONFIG.cli.worker_command_verbose
173
187
  opts.update({
@@ -179,15 +193,13 @@ def run_command(self, results, name, targets, opts={}):
179
193
  'quiet': quiet
180
194
  })
181
195
  routing_key = self.request.delivery_info['routing_key']
196
+ context['routing_key'] = routing_key
182
197
  debug(f'Task "{name}" running with routing key "{routing_key}"', sub='celery.state')
183
198
 
184
199
  # Flatten + dedupe + filter results
185
200
  results = forward_results(results)
186
201
 
187
- # Set Celery request id in context
188
- context = opts.get('context', {})
189
- context['celery_id'] = self.request.id
190
- context['worker_name'] = os.environ.get('WORKER_NAME', 'unknown')
202
+ # Set task opts
191
203
  opts['context'] = context
192
204
  opts['results'] = results
193
205
  opts['sync'] = True
@@ -204,10 +216,13 @@ def run_command(self, results, name, targets, opts={}):
204
216
  # Chunk task if needed
205
217
  if chunk_it:
206
218
  if IN_CELERY_WORKER_PROCESS:
207
- console.print(Info(message=f'Task {name} requires chunking, breaking into {len(targets)} tasks'))
208
- tasks = break_task(task, opts, results=results)
219
+ console.print(Info(message=f'Task {name} requires chunking'))
220
+ workflow = break_task(task, opts, results=results)
221
+ if IN_CELERY_WORKER_PROCESS:
222
+ console.print(Info(message=f'Task {name} successfully broken into {len(workflow)} chunks'))
209
223
  update_state(self, task, force=True)
210
- return self.replace(tasks)
224
+ console.print(Info(message=f'Task {name} updated state, replacing task with Celery chord workflow'))
225
+ return replace(self, workflow)
211
226
 
212
227
  # Update state live
213
228
  for _ in task:
@@ -327,6 +342,52 @@ def is_celery_worker_alive():
327
342
  return result
328
343
 
329
344
 
345
+ def replace(task_instance, sig):
346
+ """Replace this task, with a new task inheriting the task id.
347
+
348
+ Execution of the host task ends immediately and no subsequent statements
349
+ will be run.
350
+
351
+ .. versionadded:: 4.0
352
+
353
+ Arguments:
354
+ sig (Signature): signature to replace with.
355
+ visitor (StampingVisitor): Visitor API object.
356
+
357
+ Raises:
358
+ ~@Ignore: This is always raised when called in asynchronous context.
359
+ It is best to always use ``return self.replace(...)`` to convey
360
+ to the reader that the task won't continue after being replaced.
361
+ """
362
+ console.print('Replacing task')
363
+ chord = task_instance.request.chord
364
+ sig.freeze(task_instance.request.id)
365
+ replaced_task_nesting = task_instance.request.get('replaced_task_nesting', 0) + 1
366
+ sig.set(
367
+ chord=chord,
368
+ group_id=task_instance.request.group,
369
+ group_index=task_instance.request.group_index,
370
+ root_id=task_instance.request.root_id,
371
+ replaced_task_nesting=replaced_task_nesting
372
+ )
373
+ import psutil
374
+ import os
375
+ process = psutil.Process(os.getpid())
376
+ length = len(task_instance.request.chain) if task_instance.request.chain else 0
377
+ console.print(f'Adding {length} chain tasks from request chain')
378
+ for ix, t in enumerate(reversed(task_instance.request.chain or [])):
379
+ console.print(f'Adding chain task {t.name} from request chain ({ix + 1}/{length})')
380
+ chain_task = signature(t, app=task_instance.app)
381
+ chain_task.set(replaced_task_nesting=replaced_task_nesting)
382
+ sig |= chain_task
383
+ del chain_task
384
+ del t
385
+ memory_bytes = process.memory_info().rss
386
+ console.print(f'Memory usage: {memory_bytes / 1024 / 1024:.2f} MB (chain task {ix + 1}/{length})')
387
+ gc.collect()
388
+ return task_instance.on_replace(sig)
389
+
390
+
330
391
  def break_task(task, task_opts, results=[]):
331
392
  """Break a task into multiple of the same type."""
332
393
  chunks = task.inputs
@@ -370,16 +431,21 @@ def break_task(task, task_opts, results=[]):
370
431
  task_id = sig.freeze().task_id
371
432
  full_name = f'{task.name}_{ix + 1}'
372
433
  task.add_subtask(task_id, task.name, full_name)
373
- info = Info(message=f'Celery chunked task created: {task_id}')
434
+ info = Info(message=f'Celery chunked task created ({ix + 1} / {len(chunks)}): {task_id}')
374
435
  task.add_result(info)
375
436
  sigs.append(sig)
376
437
 
377
438
  # Mark main task as async since it's being chunked
378
439
  task.sync = False
440
+ task.results = []
441
+ task.uuids = set()
442
+ console.print(Info(message=f'Task {task.unique_name} is now async, building chord with{len(sigs)} chunks'))
443
+ console.print(Info(message=f'Results: {results}'))
379
444
 
380
445
  # Build Celery workflow
381
446
  workflow = chord(
382
447
  tuple(sigs),
383
448
  mark_runner_completed.s(runner=task).set(queue='results')
384
449
  )
450
+ console.print(Info(message=f'Task {task.unique_name} chord built with {len(sigs)} chunks, returning workflow'))
385
451
  return workflow
secator/celery_signals.py CHANGED
@@ -92,8 +92,9 @@ def task_postrun_handler(**kwargs):
92
92
 
93
93
  # Get sender name from kwargs
94
94
  sender_name = kwargs['sender'].name
95
+ # console.print(Info(message=f'Task postrun handler --> Sender name: {sender_name}'))
95
96
 
96
- if CONFIG.celery.worker_kill_after_task and sender_name.startswith('secator.'):
97
+ if CONFIG.celery.worker_kill_after_task and (sender_name.startswith('secator.') or sender_name.startswith('api.')):
97
98
  worker_name = os.environ.get('WORKER_NAME', 'unknown')
98
99
  console.print(Info(message=f'Shutdown worker {worker_name} since config celery.worker_kill_after_task is set.'))
99
100
  kill_worker(parent=True)
secator/cli.py CHANGED
@@ -134,7 +134,10 @@ for config in SCANS:
134
134
  @click.option('--stop', is_flag=True, help='Stop a worker in dev mode (celery multi).')
135
135
  @click.option('--show', is_flag=True, help='Show command (celery multi).')
136
136
  @click.option('--use-command-runner', is_flag=True, default=False, help='Use command runner to run the command.')
137
- def worker(hostname, concurrency, reload, queue, pool, quiet, loglevel, check, dev, stop, show, use_command_runner):
137
+ @click.option('--without-gossip', is_flag=True)
138
+ @click.option('--without-mingle', is_flag=True)
139
+ @click.option('--without-heartbeat', is_flag=True)
140
+ def worker(hostname, concurrency, reload, queue, pool, quiet, loglevel, check, dev, stop, show, use_command_runner, without_gossip, without_mingle, without_heartbeat): # noqa: E501
138
141
  """Run a worker."""
139
142
 
140
143
  # Check Celery addon is installed
@@ -182,6 +185,9 @@ def worker(hostname, concurrency, reload, queue, pool, quiet, loglevel, check, d
182
185
  cmd += f' -P {pool}' if pool else ''
183
186
  cmd += f' -c {concurrency}' if concurrency else ''
184
187
  cmd += f' -l {loglevel}' if loglevel else ''
188
+ cmd += ' --without-mingle' if without_mingle else ''
189
+ cmd += ' --without-gossip' if without_gossip else ''
190
+ cmd += ' --without-heartbeat' if without_heartbeat else ''
185
191
 
186
192
  if reload:
187
193
  patterns = "celery.py;tasks/*.py;runners/*.py;serializers/*.py;output_types/*.py;hooks/*.py;exporters/*.py"
secator/config.py CHANGED
@@ -100,6 +100,7 @@ class Security(StrictModel):
100
100
  allow_local_file_access: bool = True
101
101
  auto_install_commands: bool = True
102
102
  force_source_install: bool = False
103
+ memory_limit_mb: int = -1
103
104
 
104
105
 
105
106
  class HTTP(StrictModel):
@@ -144,7 +145,7 @@ class Wordlists(StrictModel):
144
145
  templates: Dict[str, str] = {
145
146
  'bo0m_fuzz': 'https://raw.githubusercontent.com/Bo0oM/fuzz.txt/master/fuzz.txt',
146
147
  'combined_subdomains': 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt', # noqa: E501
147
- 'directory_list_small': 'https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/directory-list-2.3-small.txt', # noqa: E501
148
+ 'directory_list_small': 'https://gist.githubusercontent.com/sl4v/c087e36164e74233514b/raw/c51a811c70bbdd87f4725521420cc30e7232b36d/directory-list-2.3-small.txt', # noqa: E501
148
149
  }
149
150
  lists: Dict[str, List[str]] = {}
150
151
 
@@ -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/hooks/gcs.py CHANGED
@@ -14,6 +14,16 @@ ITEMS_TO_SEND = {
14
14
  'url': ['screenshot_path', 'stored_response_path']
15
15
  }
16
16
 
17
+ _gcs_client = None
18
+
19
+
20
+ def get_gcs_client():
21
+ """Get or create GCS client"""
22
+ global _gcs_client
23
+ if _gcs_client is None:
24
+ _gcs_client = storage.Client()
25
+ return _gcs_client
26
+
17
27
 
18
28
  def process_item(self, item):
19
29
  if item._type not in ITEMS_TO_SEND.keys():
@@ -39,7 +49,7 @@ def process_item(self, item):
39
49
  def upload_blob(bucket_name, source_file_name, destination_blob_name):
40
50
  """Uploads a file to the bucket."""
41
51
  start_time = time()
42
- storage_client = storage.Client()
52
+ storage_client = get_gcs_client()
43
53
  bucket = storage_client.bucket(bucket_name)
44
54
  blob = bucket.blob(destination_blob_name)
45
55
  with open(source_file_name, 'rb') as f:
secator/hooks/mongodb.py CHANGED
@@ -166,85 +166,90 @@ def tag_duplicates(ws_id: str = None, full_scan: bool = False):
166
166
  full_scan (bool): If True, scan all findings, otherwise only untagged findings.
167
167
  """
168
168
  debug(f'running duplicate check on workspace {ws_id}', sub='hooks.mongodb')
169
+ init_time = time.time()
169
170
  client = get_mongodb_client()
170
171
  db = client.main
171
- workspace_query = list(
172
- db.findings.find({'_context.workspace_id': str(ws_id), '_tagged': True}).sort('_timestamp', -1))
173
- untagged_query = {'_context.workspace_id': str(ws_id)}
174
- if not full_scan:
175
- untagged_query['_tagged'] = {'$ne': True}
176
- untagged_query = list(
177
- db.findings.find(untagged_query).sort('_timestamp', -1))
178
- if not untagged_query:
179
- debug('no untagged findings. Skipping.', id=ws_id, sub='hooks.mongodb')
180
- return
181
- debug(f'found {len(untagged_query)} untagged findings', id=ws_id, sub='hooks.mongodb')
172
+ start_time = time.time()
173
+ workspace_query = {'_context.workspace_id': str(ws_id), '_context.workspace_duplicate': False, '_tagged': True}
174
+ untagged_query = {'_context.workspace_id': str(ws_id), '_tagged': {'$ne': True}}
175
+ if full_scan:
176
+ del untagged_query['_tagged']
177
+ workspace_findings = load_findings(list(db.findings.find(workspace_query).sort('_timestamp', -1)))
178
+ untagged_findings = load_findings(list(db.findings.find(untagged_query).sort('_timestamp', -1)))
179
+ debug(
180
+ f'Workspace non-duplicates findings: {len(workspace_findings)}, '
181
+ f'Untagged findings: {len(untagged_findings)}. '
182
+ f'Query time: {time.time() - start_time}s',
183
+ sub='hooks.mongodb'
184
+ )
185
+ start_time = time.time()
186
+ seen = []
187
+ db_updates = {}
182
188
 
183
- untagged_findings = load_findings(untagged_query)
184
- workspace_findings = load_findings(workspace_query)
185
- non_duplicates = []
186
- duplicates = []
187
189
  for item in untagged_findings:
188
- # If already seen in duplicates
189
- seen = [f for f in duplicates if f._uuid == item._uuid]
190
- if seen:
190
+ if item._uuid in seen:
191
191
  continue
192
192
 
193
- # Check for duplicates
194
- tmp_duplicates = []
193
+ debug(
194
+ f'Processing: {repr(item)} ({item._timestamp}) [{item._uuid}]',
195
+ sub='hooks.mongodb',
196
+ verbose=True
197
+ )
198
+
199
+ duplicate_ids = [
200
+ _._uuid
201
+ for _ in untagged_findings
202
+ if _ == item and _._uuid != item._uuid
203
+ ]
204
+ seen.extend(duplicate_ids)
195
205
 
196
- # Check if already present in list of workspace_findings findings, list of duplicates, or untagged_findings
197
- workspace_dupes = [f for f in workspace_findings if f == item and f._uuid != item._uuid]
198
- untagged_dupes = [f for f in untagged_findings if f == item and f._uuid != item._uuid]
199
- seen_dupes = [f for f in duplicates if f == item and f._uuid != item._uuid]
200
- tmp_duplicates.extend(workspace_dupes)
201
- tmp_duplicates.extend(untagged_dupes)
202
- tmp_duplicates.extend(seen_dupes)
203
206
  debug(
204
- f'for item {item._uuid}',
205
- obj={
206
- 'workspace dupes': len(workspace_dupes),
207
- 'untagged dupes': len(untagged_dupes),
208
- 'seen dupes': len(seen_dupes)
209
- },
210
- id=ws_id,
207
+ f'Found {len(duplicate_ids)} duplicates for item',
211
208
  sub='hooks.mongodb',
212
- verbose=True)
213
- tmp_duplicates_ids = list(dict.fromkeys([i._uuid for i in tmp_duplicates]))
214
- debug(f'duplicate ids: {tmp_duplicates_ids}', id=ws_id, sub='hooks.mongodb', verbose=True)
215
-
216
- # Update latest object as non-duplicate
217
- if tmp_duplicates:
218
- duplicates.extend([f for f in tmp_duplicates])
219
- db.findings.update_one({'_id': ObjectId(item._uuid)}, {'$set': {'_related': tmp_duplicates_ids}})
220
- debug(f'adding {item._uuid} as non-duplicate', id=ws_id, sub='hooks.mongodb', verbose=True)
221
- non_duplicates.append(item)
222
- else:
223
- debug(f'adding {item._uuid} as non-duplicate', id=ws_id, sub='hooks.mongodb', verbose=True)
224
- non_duplicates.append(item)
209
+ verbose=True
210
+ )
225
211
 
226
- # debug(f'found {len(duplicates)} total duplicates')
212
+ duplicate_ws = [
213
+ _ for _ in workspace_findings
214
+ if _ == item and _._uuid != item._uuid
215
+ ]
216
+ debug(f' --> Found {len(duplicate_ws)} workspace duplicates for item', sub='hooks.mongodb', verbose=True)
217
+
218
+ related_ids = []
219
+ if duplicate_ws:
220
+ duplicate_ws_ids = [_._uuid for _ in duplicate_ws]
221
+ duplicate_ids.extend(duplicate_ws_ids)
222
+ for related in duplicate_ws:
223
+ related_ids.extend(related._related)
224
+
225
+ debug(f' --> Found {len(duplicate_ids)} total duplicates for item', sub='hooks.mongodb', verbose=True)
226
+
227
+ db_updates[item._uuid] = {
228
+ '_related': duplicate_ids + related_ids,
229
+ '_context.workspace_duplicate': False,
230
+ '_tagged': True
231
+ }
232
+ for uuid in duplicate_ids:
233
+ db_updates[uuid] = {
234
+ '_context.workspace_duplicate': True,
235
+ '_tagged': True
236
+ }
237
+ debug(f'Finished processing untagged findings in {time.time() - start_time}s', sub='hooks.mongodb')
238
+ start_time = time.time()
227
239
 
228
- # Update objects with _tagged and _duplicate fields
229
- duplicates_ids = list(dict.fromkeys([n._uuid for n in duplicates]))
230
- non_duplicates_ids = list(dict.fromkeys([n._uuid for n in non_duplicates]))
240
+ debug(f'Executing {len(db_updates)} database updates', sub='hooks.mongodb')
231
241
 
232
- search = {'_id': {'$in': [ObjectId(d) for d in duplicates_ids]}}
233
- update = {'$set': {'_context.workspace_duplicate': True, '_tagged': True}}
234
- db.findings.update_many(search, update)
242
+ from pymongo import UpdateOne
243
+ if not db_updates:
244
+ debug('no db updates to execute', sub='hooks.mongodb')
245
+ return
235
246
 
236
- search = {'_id': {'$in': [ObjectId(d) for d in non_duplicates_ids]}}
237
- update = {'$set': {'_context.workspace_duplicate': False, '_tagged': True}}
238
- db.findings.update_many(search, update)
239
- debug(
240
- 'completed duplicates check for workspace.',
241
- id=ws_id,
242
- obj={
243
- 'processed': len(untagged_findings),
244
- 'duplicates': len(duplicates_ids),
245
- 'non-duplicates': len(non_duplicates_ids)
246
- },
247
- sub='hooks.mongodb')
247
+ result = db.findings.bulk_write(
248
+ [UpdateOne({'_id': ObjectId(uuid)}, {'$set': update}) for uuid, update in db_updates.items()]
249
+ )
250
+ debug(result, sub='hooks.mongodb')
251
+ debug(f'Finished running db update in {time.time() - start_time}s', sub='hooks.mongodb')
252
+ debug(f'Finished running tag duplicates in {time.time() - init_time}s', sub='hooks.mongodb')
248
253
 
249
254
 
250
255
  HOOKS = {
secator/installer.py CHANGED
@@ -395,7 +395,7 @@ class GithubInstaller:
395
395
  for root, _, files in os.walk(directory):
396
396
  for file in files:
397
397
  # Match the file name exactly with the repository name
398
- if file == binary_name:
398
+ if file.startswith(binary_name):
399
399
  return os.path.join(root, file)
400
400
  return None
401
401
 
@@ -26,7 +26,7 @@ class Certificate(OutputType):
26
26
  serial_number: str = field(default='', compare=False)
27
27
  ciphers: list[str] = field(default_factory=list, compare=False)
28
28
  # parent_certificate: 'Certificate' = None # noqa: F821
29
- _source: str = field(default='', repr=True)
29
+ _source: str = field(default='', repr=True, compare=False)
30
30
  _type: str = field(default='certificate', repr=True)
31
31
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
32
32
  _uuid: str = field(default='', repr=True, compare=False)
@@ -18,7 +18,7 @@ class Exploit(OutputType):
18
18
  cves: list = field(default_factory=list, compare=False)
19
19
  tags: list = field(default_factory=list, compare=False)
20
20
  extra_data: dict = field(default_factory=dict, compare=False)
21
- _source: str = field(default='', repr=True)
21
+ _source: str = field(default='', repr=True, compare=False)
22
22
  _type: str = field(default='exploit', repr=True)
23
23
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
24
24
  _uuid: str = field(default='', repr=True, compare=False)
@@ -18,7 +18,7 @@ class Ip(OutputType):
18
18
  host: str = field(default='', repr=True, compare=False)
19
19
  alive: bool = False
20
20
  protocol: str = field(default=IpProtocol.IPv4)
21
- _source: str = field(default='', repr=True)
21
+ _source: str = field(default='', repr=True, compare=False)
22
22
  _type: str = field(default='ip', repr=True)
23
23
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
24
24
  _uuid: str = field(default='', repr=True, compare=False)
@@ -9,7 +9,7 @@ from secator.utils import rich_to_ansi, format_object
9
9
  class Progress(OutputType):
10
10
  percent: int = 0
11
11
  extra_data: dict = field(default_factory=dict)
12
- _source: str = field(default='', repr=True)
12
+ _source: str = field(default='', repr=True, compare=False)
13
13
  _type: str = field(default='progress', repr=True)
14
14
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
15
15
  _uuid: str = field(default='', repr=True, compare=False)
@@ -12,7 +12,7 @@ class Record(OutputType):
12
12
  type: str
13
13
  host: str = ''
14
14
  extra_data: dict = field(default_factory=dict, compare=False)
15
- _source: str = field(default='', repr=True)
15
+ _source: str = field(default='', repr=True, compare=False)
16
16
  _type: str = field(default='record', repr=True)
17
17
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
18
18
  _uuid: str = field(default='', repr=True, compare=False)
@@ -13,7 +13,7 @@ class Stat(OutputType):
13
13
  memory: int
14
14
  net_conns: int = field(default=None, repr=True)
15
15
  extra_data: dict = field(default_factory=dict)
16
- _source: str = field(default='', repr=True)
16
+ _source: str = field(default='', repr=True, compare=False)
17
17
  _type: str = field(default='stat', repr=True)
18
18
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
19
19
  _uuid: str = field(default='', repr=True, compare=False)
@@ -12,7 +12,7 @@ class State(OutputType):
12
12
  task_id: str
13
13
  state: str
14
14
  _type: str = field(default='state', repr=True)
15
- _source: str = field(default='', repr=True)
15
+ _source: str = field(default='', repr=True, compare=False)
16
16
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
17
17
  _uuid: str = field(default='', repr=True, compare=False)
18
18
  _context: dict = field(default_factory=dict, repr=True, compare=False)
@@ -13,7 +13,7 @@ class Subdomain(OutputType):
13
13
  domain: str
14
14
  sources: List[str] = field(default_factory=list, compare=False)
15
15
  extra_data: dict = field(default_factory=dict, compare=False)
16
- _source: str = field(default='', repr=True)
16
+ _source: str = field(default='', repr=True, compare=False)
17
17
  _type: str = field(default='subdomain', repr=True)
18
18
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
19
19
  _uuid: str = field(default='', repr=True, compare=False)
@@ -11,7 +11,7 @@ class Tag(OutputType):
11
11
  match: str
12
12
  extra_data: dict = field(default_factory=dict, repr=True, compare=False)
13
13
  stored_response_path: str = field(default='', compare=False)
14
- _source: str = field(default='', repr=True)
14
+ _source: str = field(default='', repr=True, compare=False)
15
15
  _type: str = field(default='tag', repr=True)
16
16
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
17
17
  _uuid: str = field(default='', repr=True, compare=False)
@@ -9,7 +9,7 @@ from secator.utils import autodetect_type, rich_to_ansi, rich_escape as _s
9
9
  class Target(OutputType):
10
10
  name: str
11
11
  type: str = ''
12
- _source: str = field(default='', repr=True)
12
+ _source: str = field(default='', repr=True, compare=False)
13
13
  _type: str = field(default='target', repr=True)
14
14
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
15
15
  _uuid: str = field(default='', repr=True, compare=False)
@@ -13,7 +13,7 @@ class UserAccount(OutputType):
13
13
  email: str = ''
14
14
  site_name: str = ''
15
15
  extra_data: dict = field(default_factory=dict, compare=False)
16
- _source: str = field(default='', repr=True)
16
+ _source: str = field(default='', repr=True, compare=False)
17
17
  _type: str = field(default='user_account', repr=True)
18
18
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
19
19
  _uuid: str = field(default='', repr=True, compare=False)
@@ -25,7 +25,7 @@ class Vulnerability(OutputType):
25
25
  reference: str = field(default='', compare=False)
26
26
  confidence_nb: int = 0
27
27
  severity_nb: int = 0
28
- _source: str = field(default='', repr=True)
28
+ _source: str = field(default='', repr=True, compare=False)
29
29
  _type: str = field(default='vulnerability', repr=True)
30
30
  _timestamp: int = field(default_factory=lambda: time.time(), compare=False)
31
31
  _uuid: str = field(default='', repr=True, compare=False)
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
 
@@ -18,7 +18,7 @@ from secator.config import CONFIG
18
18
  from secator.output_types import Info, Warning, Error, Stat
19
19
  from secator.runners import Runner
20
20
  from secator.template import TemplateLoader
21
- from secator.utils import debug, rich_escape as _s
21
+ from secator.utils import debug, rich_escape as _s, signal_to_name
22
22
 
23
23
 
24
24
  logger = logging.getLogger(__name__)
@@ -419,10 +419,13 @@ class Command(Runner):
419
419
  self.print_command()
420
420
 
421
421
  # 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
422
+ sudo_required = re.search(r'\bsudo\b', self.cmd)
423
+ sudo_password = None
424
+ if sudo_required:
425
+ sudo_password, error = self._prompt_sudo(self.cmd)
426
+ if error:
427
+ yield Error(message=error)
428
+ return
426
429
 
427
430
  # Prepare cmds
428
431
  command = self.cmd if self.shell else shlex.split(self.cmd)
@@ -440,6 +443,7 @@ class Command(Runner):
440
443
  # Output and results
441
444
  self.return_code = 0
442
445
  self.killed = False
446
+ self.memory_limit_mb = CONFIG.security.memory_limit_mb
443
447
 
444
448
  # Run the command using subprocess
445
449
  env = os.environ
@@ -449,6 +453,7 @@ class Command(Runner):
449
453
  stdout=subprocess.PIPE,
450
454
  stderr=subprocess.STDOUT,
451
455
  universal_newlines=True,
456
+ preexec_fn=os.setsid if not sudo_required else None,
452
457
  shell=self.shell,
453
458
  env=env,
454
459
  cwd=self.cwd)
@@ -473,6 +478,11 @@ class Command(Runner):
473
478
  except FileNotFoundError as e:
474
479
  yield from self.handle_file_not_found(e)
475
480
 
481
+ except MemoryError as e:
482
+ self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
483
+ self.stop_process(exit_ok=True, sig=signal.SIGTERM)
484
+ yield Warning(message=f'Memory limit {self.memory_limit_mb}MB reached for {self.unique_name}')
485
+
476
486
  except BaseException as e:
477
487
  self.debug(f'{self.unique_name}: {type(e).__name__}.', sub='end')
478
488
  self.stop_process()
@@ -527,7 +537,7 @@ class Command(Runner):
527
537
  if self.last_updated_stat and (time() - self.last_updated_stat) < CONFIG.runners.stat_update_frequency:
528
538
  return
529
539
 
530
- yield from self.stats()
540
+ yield from self.stats(self.memory_limit_mb)
531
541
  self.last_updated_stat = time()
532
542
 
533
543
  def print_description(self):
@@ -565,26 +575,31 @@ class Command(Runner):
565
575
  error = Error.from_exception(exc)
566
576
  yield error
567
577
 
568
- def stop_process(self, exit_ok=False):
578
+ def stop_process(self, exit_ok=False, sig=signal.SIGINT):
569
579
  """Sends SIGINT to running process, if any."""
570
580
  if not self.process:
571
581
  return
572
- self.debug(f'Sending SIGINT to process {self.process.pid}.', sub='error')
573
- self.process.send_signal(signal.SIGINT)
582
+ self.debug(f'Sending signal {signal_to_name(sig)} to process {self.process.pid}.', sub='error')
583
+ if self.process and self.process.pid:
584
+ os.killpg(os.getpgid(self.process.pid), sig)
574
585
  if exit_ok:
575
586
  self.exit_ok = True
576
587
 
577
- def stats(self):
588
+ def stats(self, memory_limit_mb=None):
578
589
  """Gather stats about the current running process, if any."""
579
590
  if not self.process or not self.process.pid:
580
591
  return
581
592
  proc = psutil.Process(self.process.pid)
582
593
  stats = Command.get_process_info(proc, children=True)
594
+ total_mem = 0
583
595
  for info in stats:
584
596
  name = info['name']
585
597
  pid = info['pid']
586
598
  cpu_percent = info['cpu_percent']
587
599
  mem_percent = info['memory_percent']
600
+ mem_rss = round(info['memory_info']['rss'] / 1024 / 1024, 2)
601
+ total_mem += mem_rss
602
+ self.debug(f'{name} {pid} {mem_rss}MB', sub='stats')
588
603
  net_conns = info.get('net_connections') or []
589
604
  extra_data = {k: v for k, v in info.items() if k not in ['cpu_percent', 'memory_percent', 'net_connections']}
590
605
  yield Stat(
@@ -595,6 +610,9 @@ class Command(Runner):
595
610
  net_conns=len(net_conns),
596
611
  extra_data=extra_data
597
612
  )
613
+ self.debug(f'Total mem: {total_mem}MB, memory limit: {memory_limit_mb}', sub='stats')
614
+ if memory_limit_mb and memory_limit_mb != -1 and total_mem > memory_limit_mb:
615
+ raise MemoryError(f'Memory limit {memory_limit_mb}MB reached for {self.unique_name}')
598
616
 
599
617
  @staticmethod
600
618
  def get_process_info(process, children=False):
@@ -673,7 +691,7 @@ class Command(Runner):
673
691
  ['sudo', '-S', '-p', '', 'true'],
674
692
  input=sudo_password + "\n",
675
693
  text=True,
676
- capture_output=True
694
+ capture_output=True,
677
695
  )
678
696
  if result.returncode == 0:
679
697
  return sudo_password, None # Password is correct
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/cariddi.py CHANGED
@@ -31,6 +31,7 @@ class cariddi(HttpCrawler):
31
31
  input_types = [URL]
32
32
  output_types = [Url, Tag]
33
33
  tags = ['url', 'crawl']
34
+ input_chunk_size = 1
34
35
  input_flag = OPT_PIPE_INPUT
35
36
  file_flag = OPT_PIPE_INPUT
36
37
  json_flag = '-json'
@@ -77,7 +78,42 @@ class cariddi(HttpCrawler):
77
78
  proxychains = False
78
79
  proxy_socks5 = True # with leaks... https://github.com/edoardottt/cariddi/issues/122
79
80
  proxy_http = True # with leaks... https://github.com/edoardottt/cariddi/issues/122
80
- profile = 'cpu'
81
+ profile = lambda opts: cariddi.dynamic_profile(opts) # noqa: E731
82
+
83
+ @staticmethod
84
+ def dynamic_profile(opts):
85
+ juicy_endpoints = cariddi._get_opt_value(
86
+ opts,
87
+ 'juicy_endpoints',
88
+ opts_conf=dict(cariddi.opts, **cariddi.meta_opts),
89
+ opt_aliases=opts.get('aliases', [])
90
+ )
91
+ juicy_extensions = cariddi._get_opt_value(
92
+ opts,
93
+ 'juicy_extensions',
94
+ opts_conf=dict(cariddi.opts, **cariddi.meta_opts),
95
+ opt_aliases=opts.get('aliases', [])
96
+ )
97
+ info = cariddi._get_opt_value(
98
+ opts,
99
+ 'info',
100
+ opts_conf=dict(cariddi.opts, **cariddi.meta_opts),
101
+ opt_aliases=opts.get('aliases', [])
102
+ )
103
+ secrets = cariddi._get_opt_value(
104
+ opts,
105
+ 'secrets',
106
+ opts_conf=dict(cariddi.opts, **cariddi.meta_opts),
107
+ opt_aliases=opts.get('aliases', [])
108
+ )
109
+ errors = cariddi._get_opt_value(
110
+ opts,
111
+ 'errors',
112
+ opts_conf=dict(cariddi.opts, **cariddi.meta_opts),
113
+ opt_aliases=opts.get('aliases', [])
114
+ )
115
+ hunt = juicy_endpoints or (juicy_extensions is not None) or info or secrets or errors
116
+ return 'cpu' if hunt is True else 'io'
81
117
 
82
118
  @staticmethod
83
119
  def on_json_loaded(self, item):
secator/tasks/dalfox.py CHANGED
@@ -26,8 +26,8 @@ class dalfox(VulnHttp):
26
26
  tags = ['url', 'fuzz']
27
27
  input_flag = 'url'
28
28
  input_chunk_size = 20
29
+ ignore_return_code = True
29
30
  file_flag = 'file'
30
- # input_chunk_size = 1
31
31
  json_flag = '--format jsonl'
32
32
  version_flag = 'version'
33
33
  opt_prefix = '--'
@@ -57,7 +57,7 @@ class dalfox(VulnHttp):
57
57
  }
58
58
  }
59
59
  install_version = 'v2.11.0'
60
- install_cmd = 'go install -v github.com/hahwul/dalfox/v2@latest'
60
+ install_cmd = 'go install -v github.com/hahwul/dalfox/v2@[install_version]'
61
61
  install_github_handle = 'hahwul/dalfox'
62
62
  encoding = 'ansi'
63
63
  proxychains = False
@@ -62,7 +62,6 @@ class dirsearch(HttpFuzzer):
62
62
  proxychains = True
63
63
  proxy_socks5 = True
64
64
  proxy_http = True
65
- profile = 'io'
66
65
 
67
66
  @staticmethod
68
67
  def on_init(self):
@@ -75,7 +75,6 @@ class feroxbuster(HttpFuzzer):
75
75
  proxychains = False
76
76
  proxy_socks5 = True
77
77
  proxy_http = True
78
- profile = 'io'
79
78
 
80
79
  @staticmethod
81
80
  def on_start(self):
secator/tasks/ffuf.py CHANGED
@@ -84,7 +84,6 @@ class ffuf(HttpFuzzer):
84
84
  proxychains = False
85
85
  proxy_socks5 = True
86
86
  proxy_http = True
87
- profile = 'io'
88
87
 
89
88
  @staticmethod
90
89
  def before_init(self):
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/katana.py CHANGED
@@ -17,6 +17,7 @@ from secator.tasks._categories import HttpCrawler
17
17
  class katana(HttpCrawler):
18
18
  """Next-generation crawling and spidering framework."""
19
19
  cmd = 'katana'
20
+ input_chunk_size = 1
20
21
  input_types = [URL]
21
22
  output_types = [Url]
22
23
  tags = ['url', 'crawl']
@@ -144,6 +145,8 @@ class katana(HttpCrawler):
144
145
  if store_responses and os.path.exists(item.stored_response_path):
145
146
  with open(item.stored_response_path, 'r', encoding='latin-1') as fin:
146
147
  data = fin.read().splitlines(True)
148
+ if not data:
149
+ return item
147
150
  first_line = data[0]
148
151
  with open(item.stored_response_path, 'w', encoding='latin-1') as fout:
149
152
  fout.writelines(data[1:])
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/naabu.py CHANGED
@@ -38,8 +38,7 @@ class naabu(ReconPort):
38
38
  # 'health_check': 'hc'
39
39
  }
40
40
  opt_value_map = {
41
- TIMEOUT: lambda x: int(x*1000) if x and x > 0 else None, # convert to milliseconds
42
- RETRIES: lambda x: 1 if x == 0 else x,
41
+ TIMEOUT: lambda x: int(x)*1000 if x and int(x) > 0 else None, # convert to milliseconds
43
42
  PROXY: lambda x: x.replace('socks5://', '')
44
43
  }
45
44
  item_loaders = [JSONSerializer()]
secator/tasks/nuclei.py CHANGED
@@ -20,6 +20,7 @@ class nuclei(VulnMulti):
20
20
  file_flag = '-l'
21
21
  input_flag = '-u'
22
22
  json_flag = '-jsonl'
23
+ input_chunk_size = 20
23
24
  opts = {
24
25
  'bulk_size': {'type': int, 'short': 'bs', 'help': 'Maximum number of hosts to be analyzed in parallel per template'}, # noqa: E501
25
26
  'debug': {'type': str, 'help': 'Debug mode'},
@@ -31,6 +32,7 @@ class nuclei(VulnMulti):
31
32
  'new_templates': {'type': str, 'short': 'nt', 'help': 'Run only new templates added in latest nuclei-templates release'}, # noqa: E501
32
33
  'automatic_scan': {'is_flag': True, 'short': 'as', 'help': 'Automatic web scan using wappalyzer technology detection to tags mapping'}, # noqa: E501
33
34
  'omit_raw': {'is_flag': True, 'short': 'or', 'default': True, 'help': 'Omit requests/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)'}, # noqa: E501
35
+ 'response_size_read': {'type': int, 'help': 'Max body size to read (bytes)'},
34
36
  'stats': {'is_flag': True, 'short': 'stats', 'default': True, 'help': 'Display statistics about the running scan'},
35
37
  'stats_json': {'is_flag': True, 'short': 'sj', 'default': True, 'help': 'Display statistics in JSONL(ines) format'},
36
38
  'stats_interval': {'type': str, 'short': 'si', 'help': 'Number of seconds to wait between showing a statistics update'}, # noqa: E501
@@ -52,7 +54,8 @@ class nuclei(VulnMulti):
52
54
  # nuclei opts
53
55
  'exclude_tags': 'exclude-tags',
54
56
  'exclude_severity': 'exclude-severity',
55
- 'templates': 't'
57
+ 'templates': 't',
58
+ 'response_size_read': 'rsr'
56
59
  }
57
60
  opt_value_map = {
58
61
  'tags': lambda x: ','.join(x) if isinstance(x, list) else x,
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
  )
secator/utils.py CHANGED
@@ -6,6 +6,7 @@ import json
6
6
  import logging
7
7
  import operator
8
8
  import os
9
+ import signal
9
10
  import tldextract
10
11
  import re
11
12
  import select
@@ -823,3 +824,11 @@ def get_versions_from_string(string):
823
824
  if not matches:
824
825
  return []
825
826
  return matches
827
+
828
+
829
+ def signal_to_name(signum):
830
+ """Convert a signal number to its name"""
831
+ for name, value in vars(signal).items():
832
+ if name.startswith('SIG') and not name.startswith('SIG_') and value == signum:
833
+ return name
834
+ return str(signum)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secator
3
- Version: 0.16.5
3
+ Version: 0.18.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
@@ -1,23 +1,23 @@
1
1
  secator/.gitignore,sha256=da8MUc3hdb6Mo0WjZu2upn5uZMbXcBGvhdhTQ1L89HI,3093
2
2
  secator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- secator/celery.py,sha256=FAahY_kfjxc3tD2R42kNre_eb-QeSvArKyGpthXeWDg,11626
4
- secator/celery_signals.py,sha256=R4ZNBPKSxUvesGCvZ7MXoRkWNOTMS5hraZzjLh5sQ0o,4191
3
+ secator/celery.py,sha256=UeF1MGz4UCZ0xH6S-8GsXF0GnYWkMColWkwh8_48olI,14214
4
+ secator/celery_signals.py,sha256=chWicrTmvh2ioMXRm43r1zN09wrhWnw3Kc4mWDUY55s,4314
5
5
  secator/celery_utils.py,sha256=vhL5ZxXDn3ODvyVxMijKyUTJ1dOisMDjF_PhFUyOVSA,9451
6
- secator/cli.py,sha256=lzgttr8-Hib1X6bGi8PCOfX90incum7ZFR5x46cDZ34,60887
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=4bZ5i5NuAhLDv4hYr9tXwnwavAZMd_P8YAt8_4YB6gQ,20778
9
+ secator/config.py,sha256=9YO8HB4qrpYkvxob-CSRVY67py-rQZA1VB8BM4hc4Xc,20813
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=oWHzUXrEp8D6oPiFHjWcOvDjqMLRhftB7kG-OcCg7PY,21120
13
+ secator/installer.py,sha256=-gw6jSCCezuRgKdrlKYYK7UIORP4OWyx69bohM-8tfc,21129
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
17
17
  secator/template.py,sha256=vLp-4cmg05YDKyvqmPtKoclH-b_NamRKvr_qprIPSGA,9905
18
18
  secator/thread.py,sha256=EqilUiqunUmVLHvZQiPl7GUYXHXVneDpI8crhqKKT_4,562
19
19
  secator/tree.py,sha256=zxZ1rXE5jzipyNNUVuTDoeq35qA-7h5yAZ4mE230ZUQ,7000
20
- secator/utils.py,sha256=TUVUjVNbKfp1vCItP8-PjWIUi1DBrCDr_kJXWFbZocg,21929
20
+ secator/utils.py,sha256=NIfmFO_V2_wn3tjFhC-lFIldq1HDXDNgwWKB9B0x-40,22174
21
21
  secator/utils_test.py,sha256=cI8JRhKhgq9X5c8Lvvhs-T_C2UxxHY1wexVo4qBStS4,10131
22
22
  secator/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  secator/configs/profiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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
@@ -57,32 +57,32 @@ secator/exporters/json.py,sha256=1ZtDf8RksPO_V0zIvnwDUxMUb630DCElAMM8_RQvyAo,474
57
57
  secator/exporters/table.py,sha256=zYNmwNGEyB6dTJ1ATVkrv-AOuPjrW6tvk1_4naLQo8Q,1114
58
58
  secator/exporters/txt.py,sha256=t_FykaJOxs4UUlqiH4k6HCccEqYqc8e3iNZndL_CKPg,739
59
59
  secator/hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- secator/hooks/gcs.py,sha256=v8Q1-ky9e4xaLgyTJaeWWpiVhu0SzWbXLIEHYV9f5h8,1550
61
- secator/hooks/mongodb.py,sha256=x6gLPdvMOBz0ViWbfaM9Xkv1plLYvXjKWpd83b0VUFU,8393
60
+ secator/hooks/gcs.py,sha256=sQ_1O16aPMrjwa_pVMOTw7g_4oQ2cSJpfc7MQJUOpb8,1724
61
+ secator/hooks/mongodb.py,sha256=tHBuHwO8wejShV7FLHK5jVNRyw_fZ52vfvqqRrkkjlU,7955
62
62
  secator/output_types/__init__.py,sha256=CJcYy2_Ek-opKiBz4wFlDHQBTm3t0JVwZ4w_2Jxoeuw,1291
63
63
  secator/output_types/_base.py,sha256=9iBqPdtlfJBldBiuC729KamHHGbKhwo69P-2UNwz-3Q,2874
64
- secator/output_types/certificate.py,sha256=IXW3GN0JRmuDgoedr8NV8ccuRQOuoInNZWnAKL8zeqY,3040
64
+ secator/output_types/certificate.py,sha256=Y6Lv1nI73zEIZt13n6yQwArgol3Z-p8sUInbj73Gs3c,3055
65
65
  secator/output_types/error.py,sha256=lA7DDNUb8iuw3nbanzMD0BjQjOEwUEQPAMZy_9sRs9o,1540
66
- secator/output_types/exploit.py,sha256=-BKTqPBg94rVgjw8YSmcYuBCI2x-73WwMd9ITP9qr3Y,1750
66
+ secator/output_types/exploit.py,sha256=TwRqaIvoTSSsXahz3ZSZC1XLGMiml0jiTIWdSTXFicA,1765
67
67
  secator/output_types/info.py,sha256=HcOwdF4OPw3Qni_X0c8bDSLqq2LZLMjIwwLgtEwBwHE,820
68
- secator/output_types/ip.py,sha256=wH06b0-_F4_7g0Bcfdh7EpDW50IdjESQoqZHPuVK0hg,1217
68
+ secator/output_types/ip.py,sha256=rKSv6yAu9AKZ3nKBh7pzRg8eai8d1fE36vKUAMjyMJ8,1232
69
69
  secator/output_types/port.py,sha256=JdqXnEF8XuwaWFMT8Vghj7fKLwtsImuUdRfMmITgmWM,1879
70
- secator/output_types/progress.py,sha256=alRcuEUv-2_ExmYOEa0p2f6GIp-DerGglgI4JLdowjQ,1213
71
- secator/output_types/record.py,sha256=HnsKxlIhkgswA_Yjz7BZ1vDjP53l6OJ0BCOtCSDwCSY,1250
72
- secator/output_types/stat.py,sha256=7ZNWgfrJWONKeJx931eEFngEV4WhJaHs38AUUxxhZC8,1248
73
- secator/output_types/state.py,sha256=TofrVIL1oPTnq8-x6pATVVLEa7Z4GGYtUSqWmrcGxF4,981
74
- secator/output_types/subdomain.py,sha256=ivJ_2kmrJ8hdB8wmvRJYlKV1BcE3Cds_vAI_5wL7ES4,1344
75
- secator/output_types/tag.py,sha256=6oNyDWydngLt5Rj7ZIcUNbJV6EfRsw0lrPDHRdZYFKA,1641
76
- secator/output_types/target.py,sha256=8XTxV3YcJUb7cDSBOqtTk5e9_pf-vIQArXeJPDik5Ic,1052
70
+ secator/output_types/progress.py,sha256=D598UDh4VBcSLetOjcd-DY1H_EVgymQXVgbMK6FE-ZY,1228
71
+ secator/output_types/record.py,sha256=ehbJ-4rcVuFUxof5RXkYcuoh34DdnJEeFZazPQN4QKo,1265
72
+ secator/output_types/stat.py,sha256=YpKZRP5fge42oTmlWSYEfixDPx764g-5aVBeilQM0io,1263
73
+ secator/output_types/state.py,sha256=kY5ArRYpRVfIULj7Qt93Lx8YeeIEMa1Ke7q8vnK0Yzk,996
74
+ secator/output_types/subdomain.py,sha256=1pWVXGKA6r7IWaBSt0TRe4tC3tEVbBsRQBettr0FEH8,1359
75
+ secator/output_types/tag.py,sha256=4-khdI4W9tahW3G4YPh1WEWKHoOYW69M9UDtzPlrqnU,1656
76
+ secator/output_types/target.py,sha256=gIrx-IXermWBaPfIS4CBs0PfsrfdtMsYlnduwmZe8BE,1067
77
77
  secator/output_types/url.py,sha256=rPbDek2zmvGK2mcjQfQoi6Ij7gKTyItIaDxMT04f2TE,3628
78
- secator/output_types/user_account.py,sha256=EvF3Ebg9eXS_-iDguU1dSHZ9wAsJimEJznDvpSt_RSY,1417
79
- secator/output_types/vulnerability.py,sha256=eWJDFCYf3sP5-hPKQT-4Kd5id9bJzTW2u-O_d_4P6EA,2849
78
+ secator/output_types/user_account.py,sha256=IqPg0nfKzSsxA5DerLA3PEWIN9HscV_D7PRKoyqllU8,1432
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=sJswPMqbmwBTOo_d_KM2dw2dSr1-2X8-mwvwRepa6zg,40716
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=5fmwmqkUkomceLUSp2rtJvn_ydE2gI95rqS4WKWciYI,30200
85
+ secator/runners/command.py,sha256=cSyp-LqGRhQzxfC4cCK1zsYFPd-y8AQiF1a5YJFHgYo,31229
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
99
- secator/tasks/cariddi.py,sha256=iT-2Aryw2PPrzPedc-N_E--DxKFz_gSrcJj4z5PGQf8,4142
100
- secator/tasks/dalfox.py,sha256=DWz0VWBH5SU_AyHU36YC88vAEyJ1hXkKKKNXgQvwlrU,2493
101
- secator/tasks/dirsearch.py,sha256=_6xPZYpNsbwR4d9NFQw3NXxQKn5zyfO1lyrWzl5p7NY,2469
98
+ secator/tasks/bup.py,sha256=DXfOvtGJMe17RN9t764X0eZ__lKKz7evlI4Bl91IKGA,3867
99
+ secator/tasks/cariddi.py,sha256=pc1z6FWFV4dauSJxWL9BKD-MXjCo14sgcNtAkGuKy5I,5194
100
+ secator/tasks/dalfox.py,sha256=dllEP9A8-7YaX12fGRmLMltfNjm_9Us6wYoS86C_VO0,2507
101
+ secator/tasks/dirsearch.py,sha256=-oa2P2Pq8LjF61PguUEtjgr9rgvVpGLzRZRDXIJMswY,2453
102
102
  secator/tasks/dnsx.py,sha256=2qNC-wSjS33geuHMOwuBapLwKEvWTlDgnmvM67ZSJVA,4220
103
- secator/tasks/feroxbuster.py,sha256=dz_DGw_CbVGw9AeFjtrAEQwoxDgKzYC-KT9VLwE5UlE,3022
104
- secator/tasks/ffuf.py,sha256=UjSnbbbeBE8fIOiy98akBqaO9gBtZWb7vYrNs9DjUX8,4119
105
- secator/tasks/fping.py,sha256=uTOq24DcNQpNgpXQlFV4xxBdn8P9gJWM5mmhkobqW-Y,1575
103
+ secator/tasks/feroxbuster.py,sha256=H7_WT8B0cPIBeq7FOownpQlrZ468R07zRLqrDLNCkg8,3006
104
+ secator/tasks/ffuf.py,sha256=L2Rb34YIH30CFJacvaY8QVF1gDah9E0nNCdgAHWL9jo,4103
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
@@ -110,23 +110,23 @@ secator/tasks/gospider.py,sha256=5cEgBCCGWIGE05XfAkjMhTap9V-MwLK2lm1iqxcbj-M,251
110
110
  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
- secator/tasks/katana.py,sha256=NQimtCi7qgIIK6npzzm8OKZSVsBWxuj950W_4VNUa8U,6164
114
- secator/tasks/maigret.py,sha256=jjuyR8lAZYUybmN8SwEj3hrRB25p9xm4X_361auZK_Q,2173
113
+ secator/tasks/katana.py,sha256=10Sml1d0bO_UDwT1y_4TDQ_a0ihWKAW6L6-n8M9ArUw,6220
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
- secator/tasks/naabu.py,sha256=dI-NNb4MQzyCgnvfkstkn3IyjUpW0ORbftcsVFmx994,2443
117
+ secator/tasks/naabu.py,sha256=Z8kYvAMeeSLrhnojLRx8GzxvwhFhDCfDj9a7r9Wbr1A,2407
118
118
  secator/tasks/nmap.py,sha256=bGfPrB_JD_XaVccUJTvMAN81cNfmWo2gI4Hd6P_ZRLI,16986
119
- secator/tasks/nuclei.py,sha256=F5r7DQu8XBGHn518QWs8DSKoRVEGSGGMMDTcOW5bEHk,5010
119
+ secator/tasks/nuclei.py,sha256=ApY9oQaQZAvrFKObVQt0JxZS9ZcoLabL_lrc5Uwxl9s,5144
120
120
  secator/tasks/searchsploit.py,sha256=gwP05nLdu3yBnpMrAVu7S2CIjgCtcS3w9_1K0Tp9WBM,3503
121
121
  secator/tasks/subfinder.py,sha256=Q7YIBSyFxHWXSskmn2dEWPxU_HZ9rZCMU3Kl4sdvPwc,1297
122
122
  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.16.5.dist-info/METADATA,sha256=Bob8xdFbxnKib4O7WIiG9VAtq9efZ9JLzwpPQIcXhAw,17253
129
- secator-0.16.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- secator-0.16.5.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
131
- secator-0.16.5.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
132
- secator-0.16.5.dist-info/RECORD,,
128
+ secator-0.18.0.dist-info/METADATA,sha256=q5_y81JOJ3bps7hr_9xlC47nJTE6CFfIVmNxVFqUy1g,17253
129
+ secator-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
+ secator-0.18.0.dist-info/entry_points.txt,sha256=lPgsqqUXWgiuGSfKy-se5gHdQlAXIwS_A46NYq7Acic,44
131
+ secator-0.18.0.dist-info/licenses/LICENSE,sha256=19W5Jsy4WTctNkqmZIqLRV1gTDOp01S3LDj9iSgWaJ0,2867
132
+ secator-0.18.0.dist-info/RECORD,,