def barstr(width, percent, color=None, inf=False): ''' Returns the string representation of an ASCII 'progress bar'. :param width: the maximum space of the bar in number of of characters :param percent: the percentage of ``width`` that the bar will consume. :param color: string specifying the color of the bar :param inf: boolean determining whether the bar is supposed to be "infinite". :return: the string representation of the progress bar. ''' width = width - 13 # constant number of characters for the numbers if not inf: barw = int(round(width * percent)) bar = ''.ljust(barw, '=') bar = bar.ljust(width, ' ') else: bar = infbarstr(width, int(percent)) if color is not None: filler = '\u25A0' bar = bar.replace('=', filler) bar = stylize('[', colored.attr('bold')) + stylize( bar, colored.fg(color)) + stylize(']', colored.attr('bold')) else: bar = '[%s]' % bar if inf: return bar return '{0} {1: >7.3f} %'.format(bar, percent * 100.)
def check_cves(self, vulnerabilities: Dict[Text, List[Text]]) -> None: cvelist: Dict[Text, Vulnerability] = {} for cve, description in self.vulnerability_list.items(): version_min = description.get('version_min', "") version_max = description.get('version_max', "") indocs = description.get('docs', False) if self.between_versions(version_min, version_max): cvelist[cve] = Vulnerability(cve, indocs) cvemessagelist: List[Text] = [] if cvelist: for e in cvelist.values(): cvemessagelist.append(f" * {e.cve}: {e.url}") if e.cve in vulnerabilities.keys(): if isinstance(vulnerabilities[e.cve], list): for e1 in vulnerabilities[e.cve]: cvemessagelist.append(f" - {e1}") else: cvemessagelist.append("\n".join( [f" - {v}" for v in vulnerabilities[e.cve]])) logging.info("".join([ stylize(EMOJI['warning'] + " client affected by CVEs:\n", fg('yellow') + attr('bold')), "\n".join(cvemessagelist) ]))
def connect(self, user: Text, host: Text, port: int, method: AuthenticationMethod, password: Optional[Text] = None, key: Optional[PKey] = None, *, run_post_auth: bool = True) -> int: if not host: raise MissingHostException() auth_status = paramiko.common.AUTH_FAILED self.session.ssh_client = SSHClient(host, port, method, password, user, key, self.session) self.pre_auth_action() try: if self.session.ssh_client is not None and self.session.ssh_client.connect( ): auth_status = paramiko.common.AUTH_SUCCESSFUL except paramiko.SSHException: logging.error( stylize("Connection to remote server refused", fg('red') + attr('bold'))) return paramiko.common.AUTH_FAILED if run_post_auth: self.post_auth_action( auth_status == paramiko.common.AUTH_SUCCESSFUL) return auth_status
def run_audit(self) -> None: vulnerabilities: DefaultDict[Text, List[Text]] = defaultdict(list) for k, v in self.check_key_negotiation().items(): vulnerabilities[k].extend(v) vulnerabilities["clientaudit"].extend(self.audit()) self.check_cves(vulnerabilities) client_audits = vulnerabilities.get("clientaudit", []) if client_audits: logging.info("".join([ stylize(EMOJI['warning'] + " client audit tests:\n", fg('yellow') + attr('bold')), "\n".join([f" * {v}" for v in client_audits]) ]))
def check_key_negotiation(self) -> Dict[Text, List[Text]]: messages: List[Text] = [] if not self.SERVER_HOST_KEY_ALGORITHMS or not self.SERVER_HOST_KEY_ALGORITHMS_CVE: return {} if isinstance(self.key_negotiation_data.session.proxyserver.host_key, ECDSAKey): logging.warning( "%s: ecdsa-sha2 key is a bad choice; this will produce false positives!", self.client_name()) for host_key_algo in self.SERVER_HOST_KEY_ALGORITHMS: if self.key_negotiation_data.server_host_key_algorithms == host_key_algo: messages.append( stylize( "client connecting for the first time or using default key order!", fg('green'))) break else: messages.append( stylize("client has a locally cached remote fingerprint.", fg('yellow'))) messages.append( f"Preferred server host key algorithm: {self.key_negotiation_data.server_host_key_algorithms[0]}" ) return {self.SERVER_HOST_KEY_ALGORITHMS_CVE: messages}
def print_log(predictions, corrects=None): max_val = max(predictions) colors = [237, 237, 237, 237, 240, 243, 246, 249, 252, 255] log = '' for idx, p in enumerate(predictions): color_id = int((p / max_val) * 10 + 0.5) color_id = min(9, color_id) color_id = max(0, color_id) if corrects is not None: c = corrects[idx] else: c = '' log += stylize('%.3f%s ' % (p, c), colored.fg(colors[color_id])) argmax_id = np.argmax(predictions.detach().cpu().numpy()) log += str(decode_desc(argmax_id)) print(log)
def auth_fallback(self, username: Text) -> int: if not self.args.fallback_host: if self.session.agent: logging.error("\n".join([ stylize( EMOJI['exclamation'] + " ssh agent keys are not allowed for signing. Remote authentication not possible.", fg('red') + attr('bold')), stylize( EMOJI['information'] + " To intercept clients, you can provide credentials for a honeypot.", fg('yellow') + attr('bold')) ])) else: logging.error("\n".join([ stylize( EMOJI['exclamation'] + " ssh agent not forwarded. Login to remote host not possible with publickey authentication.", fg('red') + attr('bold')), stylize( EMOJI['information'] + " To intercept clients without a forwarded agent, you can provide credentials for a honeypot.", fg('yellow') + attr('bold')) ])) return paramiko.common.AUTH_FAILED auth_status = self.connect(user=self.args.fallback_username or username, password=self.args.fallback_password, host=self.args.fallback_host, port=self.args.fallback_port, method=AuthenticationMethod.password, run_post_auth=False) if auth_status == paramiko.common.AUTH_SUCCESSFUL: logging.warning( stylize( EMOJI['warning'] + " publickey authentication failed - no agent forwarded - connecting to honeypot!", fg('yellow') + attr('bold')), ) else: logging.error( stylize( EMOJI['exclamation'] + " Authentication against honeypot failed!", fg('red') + attr('bold')), ) return auth_status
def to_color_str(value, max_val): colors = [237, 237, 237, 237, 240, 243, 246, 249, 252, 255] color_id = int((value / max_val) * 10 + 0.5) color_id = min(9, color_id) color_id = max(0, color_id) return stylize('%.3f' % value, colored.fg(colors[color_id]))
def post_auth_action(self, success: bool) -> None: @typechecked def get_agent_pubkeys() -> List[Tuple[Text, SSHKey, bool, Text]]: pubkeyfile_path = None keys_parsed: List[Tuple[Text, SSHKey, bool, Text]] = [] if self.session.agent is None: return keys_parsed keys = self.session.agent.get_keys() for k in keys: ssh_pub_key = SSHKey(f"{k.get_name()} {k.get_base64()}") ssh_pub_key.parse() keys_parsed.append( (k.get_name(), ssh_pub_key, k.can_sign(), k.get_base64())) if self.session.session_log_dir: os.makedirs(self.session.session_log_dir, exist_ok=True) pubkeyfile_path = os.path.join(self.session.session_log_dir, 'publickeys') with open(pubkeyfile_path, 'a+') as pubkeyfile: pubkeyfile.write("".join([ f"{k[0]} {k[3]} saved-from-agent\n" for k in keys_parsed ])) return keys_parsed logmessage = [] if success: logmessage.append( stylize("Remote authentication succeeded", fg('green') + attr('bold'))) else: logmessage.append( stylize("Remote authentication failed", fg('red'))) if self.session.ssh_client is not None: logmessage.append( f"\tRemote Address: {self.session.ssh_client.host}:{self.session.ssh_client.port}" ) logmessage.append(f"\tUsername: {self.session.username_provided}") if self.session.password_provided: display_password = None if not self.args.auth_hide_credentials: display_password = self.session.password_provided logmessage.append( f"\tPassword: {display_password or stylize('*******', fg('dark_gray'))}" ) if self.session.accepted_key is not None and self.session.remote_key != self.session.accepted_key: ssh_pub_key = SSHKey( f"{self.session.accepted_key.get_name()} {self.session.accepted_key.get_base64()}" ) ssh_pub_key.parse() logmessage.append( f"\tAccepted-Publickey: {self.session.accepted_key.get_name()} {ssh_pub_key.hash_sha256()} {ssh_pub_key.bits}bits" ) if self.session.remote_key is not None: ssh_pub_key = SSHKey( f"{self.session.remote_key.get_name()} {self.session.remote_key.get_base64()}" ) ssh_pub_key.parse() logmessage.append( f"\tRemote-Publickey: {self.session.remote_key.get_name()} {ssh_pub_key.hash_sha256()} {ssh_pub_key.bits}bits" ) ssh_keys = None if self.session.agent: ssh_keys = get_agent_pubkeys() logmessage.append( f"\tAgent: {f'available keys: {len(ssh_keys or [])}' if ssh_keys else 'no agent'}" ) if ssh_keys is not None: logmessage.append("\n".join([ f"\t\tAgent-Key: {k[0]} {k[1].hash_sha256()} {k[1].bits}bits, can sign: {k[2]}" for k in ssh_keys ])) logging.info("\n".join(logmessage))