Example #1
0
    def _fill_template(self, scan_name, cmd) -> List[str]:
        """Replace template parameters with values."""
        cmd = (cmd.replace('<target>', self.target).replace(
            '<wordlist>', get_db_value('web-word-list')).replace(
                '<userlist>', get_db_value('brute-user-list')).replace(
                    '<passlist>', get_db_value('brute-pass-list')))

        if '<ports>' in cmd:
            fout = get_scan_file(
                self.target, self.name + '.' +
                '.'.join([str(p) for p in self.ports]) + '.' + scan_name)
            return [
                cmd.replace('<ports>',
                            self.port_str()).replace('<fout>', fout)
            ]
        elif '<port>' in cmd:
            cmds = []
            for port in self.ports:
                fout = get_scan_file(
                    self.target, self.name + '.' + str(port) + '.' + scan_name)
                cmds.append(
                    cmd.replace('<port>', str(port)).replace('<fout>', fout))
            return cmds
        else:
            fout = get_scan_file(self.target, self.name + '.' + scan_name)
            # handling edge-case where a qs-spawned non-port scan could be
            # overwritten by a ts-spawned non-port scan of the same service
            i = 0
            while file_exists(fout):
                fout = get_scan_file(
                    self.target, self.name + '.' + str(i) + '.' + scan_name)
                i += 1
            cmd = cmd.replace('<fout>', fout)
            return [cmd]
Example #2
0
def join_services(target: str,
                  services: Set[ParsedService]) ->\
                  Tuple[Set[ParsedService], List[DetectedService]]:
    """Join services on multiple ports into a consolidated set.

    Returns:
        A set of unmatched services and a list of consolidated service
        matches.

    """
    defined_services = get_db_value('services')
    unmatched_services = services.copy()
    joined_services = []
    for protocol, config in defined_services.items():
        matches = [s for s in services if s.name in
                   config['nmap-service-names']]
        if matches:
            ds = DetectedService(
                protocol,
                target,
                tuple(sorted(s.port for s in matches)),
                config['scans'],
                tuple(config['recommendations']))
            joined_services.append(ds)
            unmatched_services -= set(matches)

    return unmatched_services, joined_services
Example #3
0
async def run_udp_s(target: str) -> Set[ParsedService]:
    """Run a UDP scan on a target."""
    print_i_d2(target, ': beginning UDP scan')
    udp_config = get_db_value('udp-scan')
    cmd = udp_config.scan.format(
        target=target,
        fout=get_scan_file(target, 'udp.' + udp_config.name))
    services = await _parse_port_scan(target, cmd, udp_config.pattern)
    print_i_d2(target, ': finished UDP scan')
    return services
Example #4
0
async def run_ts(target: str) -> Set[ParsedService]:
    """Run a thorough TCP scan on a target using the configured method."""
    print_i_d2(target, ': beginning TCP thorough scan')
    ts_config = get_db_value('thorough-scan')
    cmd = ts_config.scan.format(
        target=target,
        fout=get_scan_file(target, 'tcp.thorough.' + ts_config.name))
    services = await _parse_port_scan(target, cmd, ts_config.pattern)
    print_i_d2(target, ': finished TCP thorough scan')
    return services
Example #5
0
async def run_qs(target: str) -> Set[ParsedService]:
    """Run a quick scan on a target via the configured method."""
    print_i_d2(target, ': beginning TCP quick scan')
    qs_config = get_db_value('quick-scan')
    cmd = qs_config.scan.format(
        target=target,
        fout=get_scan_file(target, 'tcp.quickscan.' + qs_config.name))
    services = await _parse_port_scan(target, cmd, qs_config.pattern)
    print_i_d2(target, ': finished TCP quick scan')
    return services
Example #6
0
def highlight_patterns(target: str, line: str) -> None:
    """Print a string with configured pattern matches highlighted in purple."""
    patterns = get_db_value('patterns')
    pos = 0
    highlighted_line = ''
    did_match = False
    for match in re.finditer(patterns, line):
        did_match = True
        highlighted_line += line[pos:match.start()]
        highlighted_line += purple(match.group(0))
        pos = match.end()

    if did_match:
        highlighted_line += line[pos:]
        print_i_d3(
            target, ': matched pattern in line `', highlighted_line, '`')
Example #7
0
def create_dir_skeleton(target: str) -> None:
    """Create the directory skeleton for a target-based scan.

    Args:
        target: The singular target of the scan.

    """
    print_i_d1(target, ': beginning creation of directory structure')

    base_dir = get_base_dir(target)
    if path_exists(base_dir):
        if not get_db_value('hard'):
            raise BscanForceSkipTarget('Base directory ' + base_dir +
                                       ' already exists, use '
                                       '`--hard` option to force overwrite')

        print_w_d2(target, ': removing existing base directory ', base_dir)
        remove_dir(base_dir)

    create_dir(base_dir)
    notes_file = get_notes_txt_file(target)
    touch_file(notes_file)
    recommendations_file = get_recommendations_txt_file(target)
    touch_file(recommendations_file)

    loot_dir = get_loot_dir(target)
    create_dir(loot_dir)
    proof_file = get_proof_txt_file(target)
    touch_file(proof_file)
    local_file = get_local_txt_file(target)
    touch_file(local_file)

    services_dir = get_services_dir(target)
    create_dir(services_dir)

    sploits_dir = get_sploits_dir(target)
    create_dir(sploits_dir)

    print_i_d1(target, ': successfully completed directory skeleton setup')
Example #8
0
async def scan_target(target: str) -> None:
    """Run quick, thorough, and service scans on a target."""
    do_ts = not get_db_value('quick-only')
    do_s_scans = not get_db_value('no-service-scans')
    await add_active_target(target)

    # block on the initial quick scan
    qs_parsed_services = await run_qs(target)
    qs_unmatched_services, qs_joined_services = \
        join_services(target, qs_parsed_services)
    _print_matched_services(target, qs_joined_services)
    _print_unmatched_services(target, qs_unmatched_services)

    # schedule service scans based on qs-found ports
    if do_s_scans:
        qs_s_scan_cmds: List[List[str]] = \
            [js.build_scans() for js in qs_joined_services]
        qs_s_scans: List[Coroutine[Any, Any, Any]] = \
            [run_service_s(target, cmd) for cmd in chain(*qs_s_scan_cmds)]
        qs_s_scan_tasks = [ensure_future(scan) for scan in qs_s_scans]
    else:
        qs_s_scan_tasks = []

    # block on the thorough scan, if enabled
    if do_ts:
        ts_parsed_services: Set[ParsedService] = await run_ts(target)
    else:
        ts_parsed_services = set()
        print_i_d2(target, ': skipping thorough scan')

    # diff open ports between quick and thorough scans
    new_services: Set[ParsedService] = ts_parsed_services - qs_parsed_services
    ts_joined_services: List[DetectedService] = []
    if new_services and do_s_scans:
        ts_unmatched_services, ts_joined_services = \
            join_services(target, new_services)
        _print_matched_services(target, ts_joined_services)
        _print_unmatched_services(target, ts_unmatched_services)
        ts_s_scan_cmds = [js.build_scans() for js in ts_joined_services]
        ts_s_scans = [run_service_s(target, cmd) for
                      cmd in chain(*ts_s_scan_cmds)]
        ts_s_scan_tasks = [ensure_future(scan) for scan in ts_s_scans]
    elif do_ts:
        print_i_d2(target, ': thorough scan discovered no additional '
                   'services')
        ts_s_scan_tasks = []
    else:
        ts_s_scan_tasks = []

    # write recommendations file for further manual TCP commands
    for js in chain(qs_joined_services, ts_joined_services):
        if not js.recommendations:
            continue

        with open(get_recommendations_txt_file(js.target), 'a') as f:
            fprint = partial(print, file=f, sep='')
            section_header = (
                'The following commands are recommended for service ' +
                js.name + ' running on port(s) ' + js.port_str() + ':')
            fprint(section_header)
            fprint('-'*len(section_header))
            for rec in js.build_recommendations():
                fprint(rec)
            fprint()

    # run UDP scan
    if get_db_value('udp'):
        udp_services = await run_udp_s(target)
        for service in udp_services:
            print_i_d3(
                target, ': detected service ', blue(service.name),
                ' on UDP port ', blue(str(service.port)))

    # block on any pending service scan tasks
    await gather(*chain(qs_s_scan_tasks, ts_s_scan_tasks))

    await remove_active_target(target)
Example #9
0
def get_base_dir(target: str) -> str:
    """Get the path of the base directory for a scan."""
    return os.path.join(get_db_value('output-dir'), f'{target}.bscan.d')
Example #10
0
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