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": { "six": {
"hashes": [ "hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
], ],
"version": "==1.13.0" "version": "==1.14.0"
}, },
"tornado": { "tornado": {
"hashes": [ "hashes": [

View File

@@ -1,6 +1,10 @@
# Automated Reconnaissance Pipeline # 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. 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: if self.sentry:
# only set once the Luigi Execution Summary is seen # 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"): elif output.startswith("INFO: Informed") and output.strip().endswith("PENDING"):
# luigi Task has been queued for execution # luigi Task has been queued for execution
@@ -140,7 +140,7 @@ class ReconShell(cmd2.Cmd):
Possible scans include Possible scans include
AmassScan CORScannerScan GobusterScan SearchsploitScan AmassScan CORScannerScan GobusterScan SearchsploitScan
ThreadedNmapScan WebanalyzeScan AquatoneScan FullScan ThreadedNmapScan WebanalyzeScan AquatoneScan FullScan
MasscanScan SubjackScan TKOSubsScan MasscanScan SubjackScan TKOSubsScan HTBScan
""" """
self.async_alert( self.async_alert(
style( style(
@@ -158,7 +158,7 @@ class ReconShell(cmd2.Cmd):
# luigi --module recon.web.webanalyze WebanalyzeScan --target-file tesla --top-ports 1000 --interface eth0 # luigi --module recon.web.webanalyze WebanalyzeScan --target-file tesla --top-ports 1000 --interface eth0
command = ["luigi", "--module", scans.get(args.scantype)[0]] command = ["luigi", "--module", scans.get(args.scantype)[0]]
command.extend(args.__statement__.arg_list) command.extend(args.__statement__.arg_list)
self.async_alert(" ".join(command))
if args.verbose: if args.verbose:
# verbose is not a luigi option, need to remove it # verbose is not a luigi option, need to remove it
command.pop(command.index("--verbose")) command.pop(command.index("--verbose"))
@@ -227,13 +227,13 @@ class ReconShell(cmd2.Cmd):
# used to determine whether the tool installed correctly or not # used to determine whether the tool installed correctly or not
retvals = list() 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"): for command in tools.get(args.tool).get("commands"):
# run all commands required to install the tool # run all commands required to install the tool
# print each command being run # 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"): if tools.get(args.tool).get("shell"):
@@ -260,7 +260,7 @@ class ReconShell(cmd2.Cmd):
if all(x == 0 for x in retvals): 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 # 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 tools[args.tool]["installed"] = True
else: else:

View File

@@ -159,6 +159,11 @@ scan_parser.add_argument(
scan_parser.add_argument( scan_parser.add_argument(
"--exempt-list", completer_method=cmd2.Cmd.path_complete, help="list of blacklisted ips/domains" "--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( scan_parser.add_argument(
"--wordlist", completer_method=cmd2.Cmd.path_complete, help="path to wordlist used by gobuster" "--wordlist", completer_method=cmd2.Cmd.path_complete, help="path to wordlist used by gobuster"
) )

View File

@@ -21,6 +21,7 @@ class AmassScan(ExternalProgramTask):
Args: Args:
exempt_list: Path to a file providing blacklisted subdomains, one per line. 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 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="") exempt_list = luigi.Parameter(default="")
@@ -33,7 +34,8 @@ class AmassScan(ExternalProgramTask):
Returns: Returns:
luigi.ExternalTask - TargetList 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): def output(self):
""" Returns the target output for this task. """ Returns the target output for this task.
@@ -43,7 +45,7 @@ class AmassScan(ExternalProgramTask):
Returns: Returns:
luigi.local_target.LocalTarget 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): def program_args(self):
""" Defines the options/arguments sent to amass after processing. """ Defines the options/arguments sent to amass after processing.
@@ -51,6 +53,7 @@ class AmassScan(ExternalProgramTask):
Returns: Returns:
list: list of options/arguments, beginning with the name of the executable to run 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"): if not self.input().path.endswith("domains"):
return f"touch {self.output().path}".split() return f"touch {self.output().path}".split()
@@ -65,7 +68,7 @@ class AmassScan(ExternalProgramTask):
"-df", "-df",
self.input().path, self.input().path,
"-json", "-json",
f"amass.{self.target_file}.json", self.output().path,
] ]
if self.exempt_list: if self.exempt_list:
@@ -82,6 +85,7 @@ class ParseAmassOutput(luigi.Task):
Args: Args:
target_file: specifies the file on disk containing a list of ips or domains *--* Required by upstream Task 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 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): def requires(self):
@@ -94,7 +98,11 @@ class ParseAmassOutput(luigi.Task):
luigi.ExternalTask - TargetList 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) return AmassScan(**args)
def output(self): def output(self):
@@ -109,9 +117,11 @@ class ParseAmassOutput(luigi.Task):
dict(str: luigi.local_target.LocalTarget) dict(str: luigi.local_target.LocalTarget)
""" """
return { return {
"target-ips": luigi.LocalTarget(f"{self.target_file}.ips"), "target-ips": luigi.LocalTarget(f"{self.results_dir}/{self.target_file}.ips"),
"target-ip6s": luigi.LocalTarget(f"{self.target_file}.ip6s"), "target-ip6s": luigi.LocalTarget(f"{self.results_dir}/{self.target_file}.ip6s"),
"target-subdomains": luigi.LocalTarget(f"{self.target_file}.subdomains"), "target-subdomains": luigi.LocalTarget(
f"{self.results_dir}/{self.target_file}.subdomains"
),
} }
def run(self): 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 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 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 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", "")) 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 top_ports = luigi.IntParameter(default=0) # IntParameter -> top_ports expected as int
ports = luigi.Parameter(default="") 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): def output(self):
""" Returns the target output for this task. """ Returns the target output for this task.
@@ -51,7 +48,7 @@ class MasscanScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Defines the options/arguments sent to masscan after processing. """ Defines the options/arguments sent to masscan after processing.
@@ -59,6 +56,7 @@ class MasscanScan(luigi.Task):
Returns: Returns:
list: list of options/arguments, beginning with the name of the executable to run 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: if self.ports and self.top_ports:
# can't have both # can't have both
logging.error("Only --ports or --top-ports is permitted, not 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.ports = f"{top_tcp_ports_str},U:{top_udp_ports_str}"
self.top_ports = 0 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"): 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 = [ command = [
"masscan", "masscan",
@@ -97,7 +99,7 @@ class MasscanScan(luigi.Task):
"-e", "-e",
self.interface, self.interface,
"-oJ", "-oJ",
self.masscan_output, self.output().path,
"--ports", "--ports",
self.ports, self.ports,
"-iL", "-iL",
@@ -117,6 +119,7 @@ class ParseMasscanOutput(luigi.Task):
interface: use the named raw network interface, such as "eth0" *--* Required by upstream 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 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 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): def requires(self):
@@ -128,6 +131,7 @@ class ParseMasscanOutput(luigi.Task):
luigi.Task - Masscan luigi.Task - Masscan
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -144,7 +148,7 @@ class ParseMasscanOutput(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Reads masscan JSON results and creates a pickled dictionary of pertinent information for processing. """ """ 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 top_ports: Scan top N most popular ports *--* Required by upstream Task
ports: specifies the port(s) to be scanned *--* 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 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", "")) threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -44,6 +45,7 @@ class ThreadedNmapScan(luigi.Task):
luigi.Task - ParseMasscanOutput luigi.Task - ParseMasscanOutput
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -64,7 +66,7 @@ class ThreadedNmapScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Parses pickled target info dictionary and runs targeted nmap scans against only open ports. """ """ 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_command = [ # placeholders will be overwritten with appropriate info in loop below
"nmap", "nmap",
"--open", "--open",
"PLACEHOLDER-IDX-2" "-n", "PLACEHOLDER-IDX-2",
"-n",
"-sC", "-sC",
"-T", "-T",
"4", "4",
@@ -140,6 +143,7 @@ class SearchsploitScan(luigi.Task):
top_ports: Scan top N most popular ports *--* Required by upstream 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 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 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): def requires(self):
@@ -159,6 +163,7 @@ class SearchsploitScan(luigi.Task):
"top_ports": self.top_ports, "top_ports": self.top_ports,
"interface": self.interface, "interface": self.interface,
"target_file": self.target_file, "target_file": self.target_file,
"results_dir": self.results_dir,
} }
return ThreadedNmapScan(**args) return ThreadedNmapScan(**args)
@@ -173,7 +178,7 @@ class SearchsploitScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Grabs the xml files created by ThreadedNmap and runs searchsploit --nmap on each one, saving the output. """ """ 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 shutil
import logging import logging
import ipaddress import ipaddress
from pathlib import Path
import luigi import luigi
from recon.config import defaults
class TargetList(luigi.ExternalTask): 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() target_file = luigi.Parameter()
results_dir = luigi.Parameter(default=defaults.get("results-dir", ""))
def output(self): def output(self):
""" Returns the target output for this task. target_file.ips || target_file.domains """ Returns the target output for this task. target_file.ips || target_file.domains
@@ -24,8 +32,9 @@ class TargetList(luigi.ExternalTask):
Returns: Returns:
luigi.local_target.LocalTarget luigi.local_target.LocalTarget
""" """
print(f"debug-epi: targets {self.results_dir}")
try: try:
with open(self.target_file) as f: with open(str(self.target_file)) as f:
first_line = f.readline() first_line = f.readline()
ipaddress.ip_interface(first_line.strip()) # is it a valid ip/network? ipaddress.ip_interface(first_line.strip()) # is it a valid ip/network?
except OSError as e: except OSError as e:
@@ -39,5 +48,10 @@ class TargetList(luigi.ExternalTask):
# no exception thrown; ip address found # no exception thrown; ip address found
with_suffix = f"{self.target_file}.ips" 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) 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 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 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 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", "")) threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -44,6 +45,7 @@ class AquatoneScan(luigi.Task):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -61,7 +63,7 @@ class AquatoneScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Defines the options/arguments sent to aquatone after processing. """ 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 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 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 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", "")) threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -46,6 +47,7 @@ class CORScannerScan(ExternalProgramTask):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -63,7 +65,7 @@ class CORScannerScan(ExternalProgramTask):
Returns: Returns:
luigi.local_target.LocalTarget 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): def program_args(self):
""" Defines the options/arguments sent to tko-subs after processing. """ 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 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 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 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", "")) proxy = luigi.Parameter(default=defaults.get("proxy", ""))
@@ -59,6 +60,7 @@ class GobusterScan(luigi.Task):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -79,7 +81,7 @@ class GobusterScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Defines the options/arguments sent to gobuster after processing. """ 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 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 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 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): def requires(self):
@@ -37,6 +38,7 @@ class TKOSubsScan(ExternalProgramTask):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -54,7 +56,7 @@ class TKOSubsScan(ExternalProgramTask):
Returns: Returns:
luigi.local_target.LocalTarget 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): def program_args(self):
""" Defines the options/arguments sent to tko-subs after processing. """ 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 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 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 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", "")) threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -107,6 +110,7 @@ class SubjackScan(ExternalProgramTask):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -124,7 +128,7 @@ class SubjackScan(ExternalProgramTask):
Returns: Returns:
luigi.local_target.LocalTarget 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): def program_args(self):
""" Defines the options/arguments sent to subjack after processing. """ Defines the options/arguments sent to subjack after processing.

View File

@@ -3,9 +3,9 @@ import pickle
import luigi import luigi
from luigi.util import inherits from luigi.util import inherits
from recon.config import web_ports
from recon.amass import ParseAmassOutput from recon.amass import ParseAmassOutput
from recon.masscan import ParseMasscanOutput from recon.masscan import ParseMasscanOutput
from recon.config import web_ports
@inherits(ParseMasscanOutput, ParseAmassOutput) @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 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 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 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): def requires(self):
@@ -31,6 +32,7 @@ class GatherWebTargets(luigi.Task):
dict(str: ParseMasscanOutput, str: ParseAmassOutput) dict(str: ParseMasscanOutput, str: ParseAmassOutput)
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -40,7 +42,9 @@ class GatherWebTargets(luigi.Task):
return { return {
"masscan-output": ParseMasscanOutput(**args), "masscan-output": ParseMasscanOutput(**args),
"amass-output": ParseAmassOutput( "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: Returns:
luigi.local_target.LocalTarget 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): def run(self):
""" Gather all potential web targets into a single file to pass farther down the pipeline. """ """ 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 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 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 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", "")) threads = luigi.Parameter(default=defaults.get("threads", ""))
@@ -53,6 +54,7 @@ class WebanalyzeScan(luigi.Task):
luigi.Task - GatherWebTargets luigi.Task - GatherWebTargets
""" """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -72,7 +74,7 @@ class WebanalyzeScan(luigi.Task):
Returns: Returns:
luigi.local_target.LocalTarget 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): def _wrapped_subprocess(self, cmd):
with open(f"webanalyze.{cmd[2].replace('//', '_').replace(':', '')}.txt", "wb") as f: 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): def requires(self):
""" FullScan is a wrapper, as such it requires any Tasks that it wraps. """ """ FullScan is a wrapper, as such it requires any Tasks that it wraps. """
args = { args = {
"results_dir": self.results_dir,
"rate": self.rate, "rate": self.rate,
"target_file": self.target_file, "target_file": self.target_file,
"top_ports": self.top_ports, "top_ports": self.top_ports,
@@ -39,9 +40,11 @@ class FullScan(luigi.WrapperTask):
yield GobusterScan(**args) 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"): for gobuster_opt in ("proxy", "wordlist", "extensions", "recursive"):
del args[gobuster_opt] del args[gobuster_opt]
# add aquatone scan specific option
args.update({"scan_timeout": self.scan_timeout}) args.update({"scan_timeout": self.scan_timeout})
yield AquatoneScan(**args) yield AquatoneScan(**args)
@@ -56,3 +59,41 @@ class FullScan(luigi.WrapperTask):
del args["threads"] del args["threads"]
yield TKOSubsScan(**args) 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)