diff --git a/.gitignore b/.gitignore index 894a44c..0052f14 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,8 @@ venv.bak/ # mypy .mypy_cache/ + +.idea +.flake8 +.pre-commit-config.yaml +pyproject.toml \ No newline at end of file diff --git a/recon-pipeline.py b/recon-pipeline.py index e657823..603d6b7 100755 --- a/recon-pipeline.py +++ b/recon-pipeline.py @@ -16,11 +16,11 @@ os.environ["PYTHONPATH"] = f"{os.environ.get('PYTHONPATH')}:{str(Path(__file__). os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" # third party imports -import cmd2 -from cmd2.ansi import style +import cmd2 # noqa: E402 +from cmd2.ansi import style # noqa: E402 # project's module imports -from recon import get_scans, tools, scan_parser, install_parser +from recon import get_scans, tools, scan_parser, install_parser # noqa: F401,E402 # select loop, handles async stdout/stderr processing of subprocesses selector = selectors.DefaultSelector() @@ -150,15 +150,16 @@ class ReconShell(cmd2.Cmd): ) ) - # get_scans() returns mapping of {modulename: classname} in the recon module + # get_scans() returns mapping of {classname: [modulename, ...]} in the recon module # each classname corresponds to a potential recon-pipeline command, i.e. AmassScan, CORScannerScan ... scans = get_scans() # command is a list that will end up looking something like what's below # 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")) @@ -209,11 +210,7 @@ class ReconShell(cmd2.Cmd): continue self.async_alert( - style( - f"[!] {args.tool} has an unmet dependency; installing {dependency}", - fg="yellow", - bold=True, - ) + style(f"[!] {args.tool} has an unmet dependency; installing {dependency}", fg="yellow", bold=True,) ) # install the dependency before continuing with installation @@ -238,18 +235,12 @@ class ReconShell(cmd2.Cmd): if tools.get(args.tool).get("shell"): # go tools use subshells (cmd1 && cmd2 && cmd3 ...) during install, so need shell=True - proc = subprocess.Popen( - command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: # "normal" command, split up the string as usual and run it - proc = subprocess.Popen( - shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE) - # register stderr for more visually pleasing output on failed commands - # selector.register(proc.stderr, selectors.EVENT_READ, self._install_error_reporter) out, err = proc.communicate() if err: @@ -281,7 +272,5 @@ class ReconShell(cmd2.Cmd): if __name__ == "__main__": - rs = ReconShell( - persistent_history_file="~/.reconshell_history", persistent_history_length=10000 - ) + rs = ReconShell(persistent_history_file="~/.reconshell_history", persistent_history_length=10000) sys.exit(rs.cmdloop()) diff --git a/recon/__init__.py b/recon/__init__.py index c32d9f1..7b8782f 100644 --- a/recon/__init__.py +++ b/recon/__init__.py @@ -1,7 +1,9 @@ +# flake8: noqa E231 import sys import socket import inspect import pkgutil +import importlib from pathlib import Path from collections import defaultdict @@ -25,11 +27,7 @@ tools = { "shell": True, }, "luigi": {"installed": False, "dependencies": ["pipenv"], "commands": ["pipenv install luigi"]}, - "pipenv": { - "installed": False, - "dependencies": None, - "commands": ["apt-get install -y -q pipenv"], - }, + "pipenv": {"installed": False, "dependencies": None, "commands": ["apt-get install -y -q pipenv"],}, "masscan": { "installed": False, "dependencies": None, @@ -40,11 +38,7 @@ tools = { "rm -rf /tmp/masscan", ], }, - "amass": { - "installed": False, - "dependencies": None, - "commands": ["apt-get install -y -q amass"], - }, + "amass": {"installed": False, "dependencies": None, "commands": ["apt-get install -y -q amass"],}, "aquatone": { "installed": False, "dependencies": None, @@ -88,10 +82,7 @@ tools = { "subjack": { "installed": False, "dependencies": ["go"], - "commands": [ - "go get github.com/haccer/subjack", - "(cd ~/go/src/github.com/haccer/subjack && go install)", - ], + "commands": ["go get github.com/haccer/subjack", "(cd ~/go/src/github.com/haccer/subjack && go install)",], "shell": True, }, "webanalyze": { @@ -122,20 +113,26 @@ def get_scans(): *** A contract exists here that says any scans need to end with the word scan in order to be found by this function. Returns: - dict() containing mapping of {modulename: classname} for all potential recon-pipeline commands + dict() containing mapping of {classname: [modulename, ...]} for all potential recon-pipeline commands + ex: defaultdict(, {'AmassScan': ['recon.amass'], 'MasscanScan': ['recon.masscan'], ... }) """ scans = defaultdict(list) # recursively walk packages; import each module in each package - for loader, module_name, is_pkg in pkgutil.walk_packages(recon.__path__, prefix="recon."): - _module = loader.find_module(module_name).load_module(module_name) - globals()[module_name] = _module + # walk_packages yields ModuleInfo objects for all modules recursively on path + # prefix is a string to output on the front of every module name on output. + for loader, module_name, is_pkg in pkgutil.walk_packages(path=recon.__path__, prefix="recon."): + importlib.import_module(module_name) - # walk all modules, grabbing classes that we've written and add them to the classlist set + # walk all modules, grabbing classes that we've written and add them to the classlist defaultdict + # getmembers returns all members of an object in a list of tuples (name, value) for name, obj in inspect.getmembers(sys.modules[__name__]): if inspect.ismodule(obj) and not name.startswith("_"): + # we're only interested in modules that don't begin with _ i.e. magic methods __len__ etc... + for subname, subobj in inspect.getmembers(obj): if inspect.isclass(subobj) and subname.lower().endswith("scan"): + # now we only care about classes that end in [Ss]can scans[subname].append(name) return scans @@ -143,9 +140,7 @@ def get_scans(): # options for ReconShell's 'install' command install_parser = cmd2.Cmd2ArgumentParser() -install_parser.add_argument( - "tool", help="which tool to install", choices=list(tools.keys()) + ["all"] -) +install_parser.add_argument("tool", help="which tool to install", choices=list(tools.keys()) + ["all"]) # options for ReconShell's 'scan' command @@ -160,9 +155,7 @@ 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", + "--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" @@ -172,30 +165,19 @@ scan_parser.add_argument( choices_function=lambda: [x[1] for x in socket.if_nameindex()], help="which interface masscan should use", ) -scan_parser.add_argument( - "--recursive", action="store_true", help="whether or not to recursively gobust" -) +scan_parser.add_argument("--recursive", action="store_true", help="whether or not to recursively gobust") scan_parser.add_argument("--rate", help="rate at which masscan should scan") scan_parser.add_argument( - "--top-ports", - help="ports to scan as specified by nmap's list of top-ports (only meaningful to around 5000)", -) -scan_parser.add_argument( - "--ports", help="port specification for masscan (all ports example: 1-65535,U:1-65535)" -) -scan_parser.add_argument( - "--threads", help="number of threads for all of the threaded applications to use" + "--top-ports", help="ports to scan as specified by nmap's list of top-ports (only meaningful to around 5000)", ) +scan_parser.add_argument("--ports", help="port specification for masscan (all ports example: 1-65535,U:1-65535)") +scan_parser.add_argument("--threads", help="number of threads for all of the threaded applications to use") scan_parser.add_argument("--scan-timeout", help="scan timeout for aquatone") scan_parser.add_argument("--proxy", help="proxy for gobuster if desired (ex. 127.0.0.1:8080)") scan_parser.add_argument("--extensions", help="list of extensions for gobuster (ex. asp,html,aspx)") scan_parser.add_argument( - "--local-scheduler", - action="store_true", - help="use the local scheduler instead of the central scheduler (luigid)", + "--local-scheduler", action="store_true", help="use the local scheduler instead of the central scheduler (luigid)", ) scan_parser.add_argument( - "--verbose", - action="store_true", - help="shows debug messages from luigi, useful for troubleshooting", + "--verbose", action="store_true", help="shows debug messages from luigi, useful for troubleshooting", )