def main(args: Optional[List[str]]=None) -> int: """Main entry point for `bscan-shells`'s command-line interface.""" try: opts = get_parsed_args(args) target = opts.target if not is_valid_ip_host_addr(target) and not is_valid_hostname(target): raise BscanConfigError('Invalid target specified: ' + target) try: port = (80 if opts.port is None else int(opts.port)) except ValueError: raise BscanConfigError('Invalid port specified: ' + opts.port) if opts.url_encode: attr = 'url_encoded_cmd' else: attr = 'cmd' _cmd_iter = sorted( reverse_shell_commands(target, port), key=lambda c: c.name) for rev_shell_cmd in _cmd_iter: print(rev_shell_cmd.name) print(getattr(rev_shell_cmd, attr)) print() return 0 except BscanConfigError as e: print('Configuration error:', e.message, file=sys.stderr) return 1 except Exception as e: print('Received unexpected exception; re-raising it!', file=sys.stderr) raise e
def load_default_config_file(filename: str) -> str: """Packaged-friendly method to load contents of a default config file.""" try: raw_contents = resource_string(__name__, 'configuration/' + filename) except FileNotFoundError: raise BscanConfigError('Unable to find default configuration file `' + filename + '`') return raw_contents.decode('utf-8')
def load_default_config_file(filename: str) -> str: """Packaged-friendly method to load contents of a default config file.""" try: pyinst_basedir = getattr(sys, '_MEIPASS', None) if pyinst_basedir is not None: # load configuration from PyInstaller bundle filepath = os.path.join(pyinst_basedir, 'configuration', filename) with open(filepath, 'r') as f: raw_contents = f.read() else: # load configuration from either Python wheel or the filesystem raw_contents = resource_string(__name__, 'configuration/' + filename).decode('utf-8') except FileNotFoundError: raise BscanConfigError('Unable to find default configuration file `' + filename + '`') return raw_contents
async def main(args: Optional[List[str]] = None) -> int: """Main entry point for `bscan`'s command-line interface. Args: args: Custom arguments to override ``sys.argv``. Returns: The exit code of the program. """ try: init_colorama() if not good_py_version(): print_w_d1('Running with Python version ', py_version_str(), 'but this program is only tested with Python 3.6') opts = get_parsed_args(args) print_i_d1('Initializing configuration from command-line arguments') mc = opts.max_concurrency try: mc = (20 if mc is None else int(mc)) if mc < 1: raise ValueError except ValueError: raise BscanConfigError( 'Invalid `--max-concurrency` positive integer value ' 'received: ' + str(mc)) async with Sublemon(max_concurrency=mc) as subl: await write_db_value('sublemon', subl) await init_config(opts) print_color_info() if not opts.targets: print_e_d1('No targets specified; use `--help` to figure ' 'out what you\'re doing') return 1 # TODO: create a full list of targets from network address and # --ping-sweep filtering targets = [] _target_set: Set[str] = set() for candidate in opts.targets: if candidate in _target_set: print_w_d1( 'Target ', candidate, ' has already been added as a ' 'target; skipping another attempted addition') continue elif is_valid_ip_host_addr(candidate): pass elif is_valid_hostname(candidate): pass elif is_valid_ip_net_addr(candidate): print_w_d1( 'Network scanning not yet supported; ' 'skipping network: ', candidate) continue else: print_e_d1('Unable to parse target ', candidate, ', skipping it') continue try: create_dir_skeleton(candidate) except BscanForceSkipTarget as e: print_e_d1(e.message) print_e_d1(candidate, ': skipping this target') continue targets.append(candidate) _target_set.add(candidate) if not targets: print_e_d1('No valid targets specified') return 1 print_i_d1('Kicking off scans of ', len(targets), ' targets') tasks = [scan_target(target) for target in targets] if get_db_value('status-interval') > 0: tasks.append(status_update_poller()) await asyncio.gather(*tasks) print_i_d1('Completed execution') return 0 except BscanConfigError as e: print_e_d1('Configuration error: ', e.message) return 1 except BscanForceSilentExit as e: return 1 except BscanInternalError as e: print_e_d1('Internal error: ', e.message) return 1 except BscanSubprocessError as e: print_e_d1('Error handling subprocess: ', e.message) return 1 except BscanError as e: print_e_d1('This should not be reached!') return 1 except Exception as e: print_e_d1('Received unexpected exception; re-raising it.', file=sys.stderr) raise e
async def init_config(ns: Namespace) -> None: """Init configuration from default files and command-line arguments.""" async with lock: # track targets being actively scanned db['active-targets'] = set() # --brute-pass-list if ns.brute_pass_list is None: db['brute-pass-list'] = '/usr/share/wordlists/fasttrack.txt' else: db['brute-pass-list'] = ns.brute_pass_list if not ns.no_file_check and not file_exists(db['brute-pass-list']): raise BscanConfigError('`--brute-pass-list` file ' + db['brute-pass-list'] + ' does not exist') # --brute-user-list if ns.brute_user_list is None: db['brute-user-list'] = ( '/usr/share/wordlists/metasploit/namelist.txt') else: db['brute-user-list'] = ns.brute_user_list if not ns.no_file_check and not file_exists(db['brute-user-list']): raise BscanConfigError('`--brute-user-list` file ' + db['brute-user-list'] + ' does not exist') # --cmd-print-width try: cmd_print_width = (80 if ns.cmd_print_width is None else int( ns.cmd_print_width)) if cmd_print_width < 5: raise ValueError except ValueError: raise BscanConfigError( 'Invalid `--cmd-print-width` value specified; must be an ' 'integer greater than or equal to 5') db['cmd-print-width'] = cmd_print_width # --output-dir if ns.output_dir is None: db['output-dir'] = os.getcwd() else: db['output-dir'] = ns.output_dir if not dir_exists(db['output-dir']): raise BscanConfigError('`--output-dir` directory ' + db['output-dir'] + ' does not exist') # --patterns; also loads from `configuration/patterns.txt` patterns = load_config_file('patterns.txt', ns.config_dir).splitlines() if ns.patterns is not None: if not ns.patterns: raise BscanConfigError( '`--patterns` requires at least one regex pattern') else: patterns.extend(ns.patterns) db['patterns'] = re.compile('|'.join(patterns)) # --no-program-check if not ns.no_program_check: not_found_progs = [] progs = load_config_file('required-programs.txt', ns.config_dir).splitlines() for prog in progs: if shutil.which(prog) is None: not_found_progs.append(prog) if not_found_progs: raise BscanConfigError('required programs ' + ', '.join(not_found_progs) + ' could not be found on this system') # --no-service-scans db['no-service-scans'] = ns.no_service_scans # load service information from `configuration/service-scans.toml` db['services'] = toml.loads( load_config_file('service-scans.toml', ns.config_dir)) # load quick scan method configuration # derived from `--qs-method` + `configuration/port-scans.toml` port_scan_config = toml.loads( load_config_file('port-scans.toml', ns.config_dir)) qs_config = port_scan_config['quick'] qs_method_name = (ns.qs_method if ns.qs_method is not None else qs_config['default']) if qs_method_name not in qs_config or qs_method_name == 'default': raise BscanConfigError('Invalid `--qs-method` specified: ' + str(qs_method_name)) qs_attrs = qs_config[qs_method_name] db['quick-scan'] = PortScanConfig(qs_method_name, re.compile(qs_attrs['pattern']), qs_attrs['scan']) # load thorough scan method configuration # derived from `--ts-method` + `configuration/port-scans.toml` ts_config = port_scan_config['thorough'] ts_method_name = (ns.ts_method if ns.ts_method is not None else ts_config['default']) if ts_method_name not in ts_config or ts_method_name == 'default': raise BscanConfigError('Invalid `--ts-method` specified: ' + str(ts_method_name)) ts_attrs = ts_config[ts_method_name] db['thorough-scan'] = PortScanConfig(ts_method_name, re.compile(ts_attrs['pattern']), ts_attrs['scan']) # load udp scan method configuration # derived from `--udp-method` + `configuration/port-scans.toml` udp_config = port_scan_config['udp'] udp_method_name = (ns.udp_method if ns.udp_method is not None else udp_config['default']) if udp_method_name not in udp_config or udp_method_name == 'default': raise BscanConfigError('Invalid `--udp-method` specified: ' + str(udp_method_name)) udp_attrs = udp_config[udp_method_name] db['udp-scan'] = PortScanConfig(udp_method_name, re.compile(udp_attrs['pattern']), udp_attrs['scan']) # --status-interval try: db['status-interval'] = (30 if ns.status_interval is None else int( ns.status_interval)) except ValueError: raise BscanConfigError( 'Invalid `--status-interval` integer specified: ' + str(ns.status_interval)) # runtime tracking of active subprocesses db['subprocesses'] = dict() # --web-word-list if ns.web_word_list is None: db['web-word-list'] = '/usr/share/dirb/wordlists/big.txt' else: db['web-word-list'] = ns.web_word_list if not ns.no_file_check and not file_exists(db['web-word-list']): raise BscanConfigError('`--web-word-list` file ' + db['web-word-list'] + ' does not exist') # --quick-only db['quick-only'] = ns.quick_only # --hard db['hard'] = ns.hard # --ping-sweep if ns.ping_sweep: raise BscanConfigError('`--ping-sweep` option not yet implemented') db['ping-sweep'] = ns.ping_sweep # --udp db['udp'] = ns.udp # --verbose-status db['verbose-status'] = ns.verbose_status