added outdir option; install/scan colors match

This commit is contained in:
epi052
2020-01-19 13:51:24 -06:00
parent 1390303989
commit bc0c975d69
16 changed files with 150 additions and 48 deletions

6
Pipfile.lock generated
View File

@@ -81,10 +81,10 @@
},
"six": {
"hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.13.0"
"version": "==1.14.0"
},
"tornado": {
"hashes": [

View File

@@ -1,6 +1,10 @@
# Automated Reconnaissance Pipeline
![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
![version](https://img.shields.io/badge/version-0.7.0-informational?style=for-the-badge)
![python](https://img.shields.io/badge/python-3.7-informational?style=for-the-badge)
![luigi](https://img.shields.io/badge/luigi-2.8.11-yellowgreen?style=for-the-badge)
![cmd2](https://img.shields.io/badge/cmd2-0.9.23-yellowgreen?style=for-the-badge)
![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge)
There are an [accompanying set of blog posts](https://epi052.gitlab.io/notes-to-self/blog/2019-09-01-how-to-build-an-automated-recon-pipeline-with-python-and-luigi/) detailing the development process and underpinnings of the pipeline. Feel free to check them out if you're so inclined, but they're in no way required reading to use the tool.

View File

@@ -108,7 +108,7 @@ class ReconShell(cmd2.Cmd):
if self.sentry:
# only set once the Luigi Execution Summary is seen
self.async_alert(cmd2.utils.align_center(style(output.strip(), fg="bright_blue")))
self.async_alert(style(output.strip(), fg="bright_blue"))
elif output.startswith("INFO: Informed") and output.strip().endswith("PENDING"):
# luigi Task has been queued for execution
@@ -140,7 +140,7 @@ class ReconShell(cmd2.Cmd):
Possible scans include
AmassScan CORScannerScan GobusterScan SearchsploitScan
ThreadedNmapScan WebanalyzeScan AquatoneScan FullScan
MasscanScan SubjackScan TKOSubsScan
MasscanScan SubjackScan TKOSubsScan HTBScan
"""
self.async_alert(
style(
@@ -158,7 +158,7 @@ class ReconShell(cmd2.Cmd):
# luigi --module recon.web.webanalyze WebanalyzeScan --target-file tesla --top-ports 1000 --interface eth0
command = ["luigi", "--module", scans.get(args.scantype)[0]]
command.extend(args.__statement__.arg_list)
self.async_alert(" ".join(command))
if args.verbose:
# verbose is not a luigi option, need to remove it
command.pop(command.index("--verbose"))
@@ -227,13 +227,13 @@ class ReconShell(cmd2.Cmd):
# used to determine whether the tool installed correctly or not
retvals = list()
self.async_alert(style(f"[*] Installing {args.tool}...", fg="blue", bold=True))
self.async_alert(style(f"[*] Installing {args.tool}...", fg="bright_yellow"))
for command in tools.get(args.tool).get("commands"):
# run all commands required to install the tool
# print each command being run
self.async_alert(style(f"[-] {command}", fg="cyan"))
self.async_alert(style(f"[=] {command}", fg="cyan"))
if tools.get(args.tool).get("shell"):
@@ -260,7 +260,7 @@ class ReconShell(cmd2.Cmd):
if all(x == 0 for x in retvals):
# all return values in retvals are 0, i.e. all exec'd successfully; tool has been installed
self.async_alert(style(f"[+] {args.tool} installed!", fg="bright_green", bold=True))
self.async_alert(style(f"[+] {args.tool} installed!", fg="bright_green"))
tools[args.tool]["installed"] = True
else:

View File

@@ -159,6 +159,11 @@ scan_parser.add_argument(
scan_parser.add_argument(
"--exempt-list", completer_method=cmd2.Cmd.path_complete, help="list of blacklisted ips/domains"
)
scan_parser.add_argument(
"--results-dir",
completer_method=cmd2.Cmd.path_complete,
help="directory in which to save scan results",
)
scan_parser.add_argument(
"--wordlist", completer_method=cmd2.Cmd.path_complete, help="path to wordlist used by gobuster"
)

View File

@@ -21,6 +21,7 @@ class AmassScan(ExternalProgramTask):
Args:
exempt_list: Path to a file providing blacklisted subdomains, one per line.
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
exempt_list = luigi.Parameter(default="")
@@ -33,7 +34,8 @@ class AmassScan(ExternalProgramTask):
Returns:
luigi.ExternalTask - TargetList
"""
return TargetList(self.target_file)
args = {"target_file": self.target_file, "results_dir": self.results_dir}
return TargetList(**args)
def output(self):
""" Returns the target output for this task.
@@ -43,7 +45,7 @@ class AmassScan(ExternalProgramTask):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"amass.{self.target_file}.json")
return luigi.LocalTarget(f"{self.results_dir}/amass.{self.target_file}.json")
def program_args(self):
""" Defines the options/arguments sent to amass after processing.
@@ -51,6 +53,7 @@ class AmassScan(ExternalProgramTask):
Returns:
list: list of options/arguments, beginning with the name of the executable to run
"""
print(f"debug-epi: amass {self.results_dir}")
if not self.input().path.endswith("domains"):
return f"touch {self.output().path}".split()
@@ -65,7 +68,7 @@ class AmassScan(ExternalProgramTask):
"-df",
self.input().path,
"-json",
f"amass.{self.target_file}.json",
self.output().path,
]
if self.exempt_list:
@@ -82,6 +85,7 @@ class ParseAmassOutput(luigi.Task):
Args:
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
exempt_list: Path to a file providing blacklisted subdomains, one per line. *--* Optional for upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
def requires(self):
@@ -94,7 +98,11 @@ class ParseAmassOutput(luigi.Task):
luigi.ExternalTask - TargetList
"""
args = {"target_file": self.target_file, "exempt_list": self.exempt_list}
args = {
"target_file": self.target_file,
"exempt_list": self.exempt_list,
"results_dir": self.results_dir,
}
return AmassScan(**args)
def output(self):
@@ -109,9 +117,11 @@ class ParseAmassOutput(luigi.Task):
dict(str: luigi.local_target.LocalTarget)
"""
return {
"target-ips": luigi.LocalTarget(f"{self.target_file}.ips"),
"target-ip6s": luigi.LocalTarget(f"{self.target_file}.ip6s"),
"target-subdomains": luigi.LocalTarget(f"{self.target_file}.subdomains"),
"target-ips": luigi.LocalTarget(f"{self.results_dir}/{self.target_file}.ips"),
"target-ip6s": luigi.LocalTarget(f"{self.results_dir}/{self.target_file}.ip6s"),
"target-subdomains": luigi.LocalTarget(
f"{self.results_dir}/{self.target_file}.subdomains"
),
}
def run(self):

File diff suppressed because one or more lines are too long

View File

@@ -32,6 +32,7 @@ class MasscanScan(luigi.Task):
ports: specifies the port(s) to be scanned
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
exempt_list: Path to a file providing blacklisted subdomains, one per line. *--* Optional for upstream Task
results_dir: specifies the directory on disk to which all Task results are written *--* Optional for upstream Task
"""
rate = luigi.Parameter(default=defaults.get("masscan-rate", ""))
@@ -39,10 +40,6 @@ class MasscanScan(luigi.Task):
top_ports = luigi.IntParameter(default=0) # IntParameter -> top_ports expected as int
ports = luigi.Parameter(default="")
def __init__(self, *args, **kwargs):
super(MasscanScan, self).__init__(*args, **kwargs)
self.masscan_output = f"masscan.{self.target_file}.json"
def output(self):
""" Returns the target output for this task.
@@ -51,7 +48,7 @@ class MasscanScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(self.masscan_output)
return luigi.LocalTarget(f"{self.results_dir}/masscan.{self.target_file}.json")
def run(self):
""" Defines the options/arguments sent to masscan after processing.
@@ -59,6 +56,7 @@ class MasscanScan(luigi.Task):
Returns:
list: list of options/arguments, beginning with the name of the executable to run
"""
print(f"debug-epi: masscan {self.results_dir}")
if self.ports and self.top_ports:
# can't have both
logging.error("Only --ports or --top-ports is permitted, not both.")
@@ -82,10 +80,14 @@ class MasscanScan(luigi.Task):
self.ports = f"{top_tcp_ports_str},U:{top_udp_ports_str}"
self.top_ports = 0
target_list = yield TargetList(target_file=self.target_file)
target_list = yield TargetList(target_file=self.target_file, results_dir=self.results_dir)
if target_list.path.endswith("domains"):
yield ParseAmassOutput(target_file=self.target_file, exempt_list=self.exempt_list)
yield ParseAmassOutput(
target_file=self.target_file,
exempt_list=self.exempt_list,
results_dir=self.results_dir,
)
command = [
"masscan",
@@ -97,7 +99,7 @@ class MasscanScan(luigi.Task):
"-e",
self.interface,
"-oJ",
self.masscan_output,
self.output().path,
"--ports",
self.ports,
"-iL",
@@ -117,6 +119,7 @@ class ParseMasscanOutput(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
def requires(self):
@@ -128,6 +131,7 @@ class ParseMasscanOutput(luigi.Task):
luigi.Task - Masscan
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -144,7 +148,7 @@ class ParseMasscanOutput(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"masscan.{self.target_file}.parsed.pickle")
return luigi.LocalTarget(f"{self.results_dir}/masscan.{self.target_file}.parsed.pickle")
def run(self):
""" Reads masscan JSON results and creates a pickled dictionary of pertinent information for processing. """

View File

@@ -30,6 +30,7 @@ class ThreadedNmapScan(luigi.Task):
top_ports: Scan top N most popular ports *--* Required by upstream Task
ports: specifies the port(s) to be scanned *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -44,6 +45,7 @@ class ThreadedNmapScan(luigi.Task):
luigi.Task - ParseMasscanOutput
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -64,7 +66,7 @@ class ThreadedNmapScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"nmap-{self.target_file}-results")
return luigi.LocalTarget(f"{self.results_dir}/nmap-{self.target_file}-results")
def run(self):
""" Parses pickled target info dictionary and runs targeted nmap scans against only open ports. """
@@ -78,7 +80,8 @@ class ThreadedNmapScan(luigi.Task):
nmap_command = [ # placeholders will be overwritten with appropriate info in loop below
"nmap",
"--open",
"PLACEHOLDER-IDX-2" "-n",
"PLACEHOLDER-IDX-2",
"-n",
"-sC",
"-T",
"4",
@@ -140,6 +143,7 @@ class SearchsploitScan(luigi.Task):
top_ports: Scan top N most popular ports *--* Required by upstream Task
ports: specifies the port(s) to be scanned *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifies the directory on disk to which all Task results are written *--* Required by upstream Task
"""
def requires(self):
@@ -159,6 +163,7 @@ class SearchsploitScan(luigi.Task):
"top_ports": self.top_ports,
"interface": self.interface,
"target_file": self.target_file,
"results_dir": self.results_dir,
}
return ThreadedNmapScan(**args)
@@ -173,7 +178,7 @@ class SearchsploitScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"searchsploit-{self.target_file}-results")
return luigi.LocalTarget(f"{self.results_dir}/searchsploit-{self.target_file}-results")
def run(self):
""" Grabs the xml files created by ThreadedNmap and runs searchsploit --nmap on each one, saving the output. """

View File

@@ -1,14 +1,22 @@
import shutil
import logging
import ipaddress
from pathlib import Path
import luigi
from recon.config import defaults
class TargetList(luigi.ExternalTask):
""" External task. `TARGET_FILE` is generated manually by the user from target's scope. """
""" External task. `TARGET_FILE` is generated manually by the user from target's scope.
Args:
results_dir: specifies the directory on disk to which all Task results are written
"""
target_file = luigi.Parameter()
results_dir = luigi.Parameter(default=defaults.get("results-dir", ""))
def output(self):
""" Returns the target output for this task. target_file.ips || target_file.domains
@@ -24,8 +32,9 @@ class TargetList(luigi.ExternalTask):
Returns:
luigi.local_target.LocalTarget
"""
print(f"debug-epi: targets {self.results_dir}")
try:
with open(self.target_file) as f:
with open(str(self.target_file)) as f:
first_line = f.readline()
ipaddress.ip_interface(first_line.strip()) # is it a valid ip/network?
except OSError as e:
@@ -39,5 +48,10 @@ class TargetList(luigi.ExternalTask):
# no exception thrown; ip address found
with_suffix = f"{self.target_file}.ips"
shutil.copy(self.target_file, with_suffix) # copy file with new extension
Path(str(self.results_dir)).mkdir(parents=True, exist_ok=True)
with_suffix = f"{self.results_dir}/{with_suffix}"
# copy file with new extension
shutil.copy(str(self.target_file), with_suffix)
return luigi.LocalTarget(with_suffix)

View File

@@ -29,6 +29,7 @@ class AquatoneScan(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -44,6 +45,7 @@ class AquatoneScan(luigi.Task):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -61,7 +63,7 @@ class AquatoneScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"aquatone-{self.target_file}-results")
return luigi.LocalTarget(f"{self.results_dir}/aquatone-{self.target_file}-results")
def run(self):
""" Defines the options/arguments sent to aquatone after processing.

View File

@@ -32,6 +32,7 @@ class CORScannerScan(ExternalProgramTask):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -46,6 +47,7 @@ class CORScannerScan(ExternalProgramTask):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -63,7 +65,7 @@ class CORScannerScan(ExternalProgramTask):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"corscanner.{self.target_file}.json")
return luigi.LocalTarget(f"{self.results_dir}/corscanner.{self.target_file}.json")
def program_args(self):
""" Defines the options/arguments sent to tko-subs after processing.

View File

@@ -41,6 +41,7 @@ class GobusterScan(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
proxy = luigi.Parameter(default=defaults.get("proxy", ""))
@@ -59,6 +60,7 @@ class GobusterScan(luigi.Task):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -79,7 +81,7 @@ class GobusterScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"gobuster-{self.target_file}-results")
return luigi.LocalTarget(f"{self.results_dir}/gobuster-{self.target_file}-results")
def run(self):
""" Defines the options/arguments sent to gobuster after processing.

View File

@@ -25,6 +25,7 @@ class TKOSubsScan(ExternalProgramTask):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
def requires(self):
@@ -37,6 +38,7 @@ class TKOSubsScan(ExternalProgramTask):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -54,7 +56,7 @@ class TKOSubsScan(ExternalProgramTask):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"tkosubs.{self.target_file}.csv")
return luigi.LocalTarget(f"{self.results_dir}/tkosubs.{self.target_file}.csv")
def program_args(self):
""" Defines the options/arguments sent to tko-subs after processing.
@@ -93,6 +95,7 @@ class SubjackScan(ExternalProgramTask):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -107,6 +110,7 @@ class SubjackScan(ExternalProgramTask):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -124,7 +128,7 @@ class SubjackScan(ExternalProgramTask):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"subjack.{self.target_file}.txt")
return luigi.LocalTarget(f"{self.results_dir}/subjack.{self.target_file}.txt")
def program_args(self):
""" Defines the options/arguments sent to subjack after processing.

View File

@@ -3,9 +3,9 @@ import pickle
import luigi
from luigi.util import inherits
from recon.config import web_ports
from recon.amass import ParseAmassOutput
from recon.masscan import ParseMasscanOutput
from recon.config import web_ports
@inherits(ParseMasscanOutput, ParseAmassOutput)
@@ -19,6 +19,7 @@ class GatherWebTargets(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
def requires(self):
@@ -31,6 +32,7 @@ class GatherWebTargets(luigi.Task):
dict(str: ParseMasscanOutput, str: ParseAmassOutput)
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -40,7 +42,9 @@ class GatherWebTargets(luigi.Task):
return {
"masscan-output": ParseMasscanOutput(**args),
"amass-output": ParseAmassOutput(
exempt_list=self.exempt_list, target_file=self.target_file
exempt_list=self.exempt_list,
target_file=self.target_file,
results_dir=self.results_dir,
),
}
@@ -52,7 +56,7 @@ class GatherWebTargets(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"webtargets.{self.target_file}.txt")
return luigi.LocalTarget(f"{self.results_dir}/webtargets.{self.target_file}.txt")
def run(self):
""" Gather all potential web targets into a single file to pass farther down the pipeline. """

View File

@@ -39,6 +39,7 @@ class WebanalyzeScan(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream Task
rate: desired rate for transmitting packets (packets per second) *--* Required by upstream Task
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task
results_dir: specifes the directory on disk to which all Task results are written *--* Required by upstream Task
"""
threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -53,6 +54,7 @@ class WebanalyzeScan(luigi.Task):
luigi.Task - GatherWebTargets
"""
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -72,7 +74,7 @@ class WebanalyzeScan(luigi.Task):
Returns:
luigi.local_target.LocalTarget
"""
return luigi.LocalTarget(f"webanalyze-{self.target_file}-results")
return luigi.LocalTarget(f"{self.results_dir}/webanalyze-{self.target_file}-results")
def _wrapped_subprocess(self, cmd):
with open(f"webanalyze.{cmd[2].replace('//', '_').replace(':', '')}.txt", "wb") as f:

View File

@@ -24,6 +24,7 @@ class FullScan(luigi.WrapperTask):
def requires(self):
""" FullScan is a wrapper, as such it requires any Tasks that it wraps. """
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
@@ -39,9 +40,11 @@ class FullScan(luigi.WrapperTask):
yield GobusterScan(**args)
# remove options that are gobuster specific; if left dictionary unpacking to other scans throws an exception
for gobuster_opt in ("proxy", "wordlist", "extensions", "recursive"):
del args[gobuster_opt]
# add aquatone scan specific option
args.update({"scan_timeout": self.scan_timeout})
yield AquatoneScan(**args)
@@ -56,3 +59,41 @@ class FullScan(luigi.WrapperTask):
del args["threads"]
yield TKOSubsScan(**args)
@inherits(SearchsploitScan, AquatoneScan, GobusterScan, WebanalyzeScan)
class HTBScan(luigi.WrapperTask):
""" Wraps multiple scan types in order to run tasks on the same hierarchical level at the same time. """
def requires(self):
""" HTBScan is a wrapper, as such it requires any Tasks that it wraps. """
args = {
"results_dir": self.results_dir,
"rate": self.rate,
"target_file": self.target_file,
"top_ports": self.top_ports,
"interface": self.interface,
"ports": self.ports,
"exempt_list": self.exempt_list,
"threads": self.threads,
"proxy": self.proxy,
"wordlist": self.wordlist,
"extensions": self.extensions,
"recursive": self.recursive,
}
yield GobusterScan(**args)
# remove options that are gobuster specific; if left dictionary unpacking to other scans throws an exception
for gobuster_opt in ("proxy", "wordlist", "extensions", "recursive"):
del args[gobuster_opt]
# add aquatone scan specific option
args.update({"scan_timeout": self.scan_timeout})
yield AquatoneScan(**args)
del args["scan_timeout"]
yield SearchsploitScan(**args)
yield WebanalyzeScan(**args)