def output_compatibility(out: OutputBuffer, algs: Algorithms, client_audit: bool, for_server: bool = True) -> None: # Don't output any compatibility info if we're doing a client audit. if client_audit: return ssh_timeframe = algs.get_ssh_timeframe(for_server) comp_text = [] for ssh_prod in [Product.OpenSSH, Product.DropbearSSH]: if ssh_prod not in ssh_timeframe: continue v_from = ssh_timeframe.get_from(ssh_prod, for_server) v_till = ssh_timeframe.get_till(ssh_prod, for_server) if v_from is None: continue if v_till is None: comp_text.append('{} {}+'.format(ssh_prod, v_from)) elif v_from == v_till: comp_text.append('{} {}'.format(ssh_prod, v_from)) else: software = Software(None, ssh_prod, v_from, None, None) if software.compare_version(v_till) > 0: tfmt = '{0} {1}+ (some functionality from {2})' else: tfmt = '{0} {1}-{2}' comp_text.append(tfmt.format(ssh_prod, v_from, v_till)) if len(comp_text) > 0: out.good('(gen) compatibility: ' + ', '.join(comp_text))
def output_security(banner: Optional[Banner], client_audit: bool, padlen: int, is_json_output: bool) -> None: with OutputBuffer() as obuf: if banner is not None: software = Software.parse(banner) output_security_sub('cve', software, client_audit, padlen) output_security_sub('txt', software, client_audit, padlen) if len(obuf) > 0 and not is_json_output: out.head('# security') obuf.flush() out.sep()
def output_security(out: OutputBuffer, banner: Optional[Banner], client_audit: bool, padlen: int, is_json_output: bool) -> None: with out: if banner is not None: software = Software.parse(banner) output_security_sub(out, 'cve', software, client_audit, padlen) output_security_sub(out, 'txt', software, client_audit, padlen) if banner.protocol[0] == 1: p = '' if out.batch else ' ' * (padlen - 14) out.fail('(sec) SSH v1 enabled{} -- SSH v1 can be exploited to recover plaintext passwords'.format(p)) if not out.is_section_empty() and not is_json_output: out.head('# security') out.flush_section() out.sep()
def output(out: OutputBuffer, aconf: AuditConf, banner: Optional[Banner], header: List[str], client_host: Optional[str] = None, kex: Optional[SSH2_Kex] = None, pkm: Optional[SSH1_PublicKeyMessage] = None, print_target: bool = False) -> int: program_retval = exitcodes.GOOD client_audit = client_host is not None # If set, this is a client audit. sshv = 1 if pkm is not None else 2 algs = Algorithms(pkm, kex) with out: if print_target: host = aconf.host # Print the port if it's not the default of 22. if aconf.port != 22: # Check if this is an IPv6 address, as that is printed in a different format. if Utils.is_ipv6_address(aconf.host): host = '[%s]:%d' % (aconf.host, aconf.port) else: host = '%s:%d' % (aconf.host, aconf.port) out.good('(gen) target: {}'. format(host)) if client_audit: out.good('(gen) client IP: {}'.format(client_host)) if len(header) > 0: out.info('(gen) header: ' + '\n'.join(header)) if banner is not None: banner_line = '(gen) banner: {}'.format(banner) if sshv == 1 or banner.protocol[0] == 1: out.fail(banner_line) out.fail('(gen) protocol SSH1 enabled') else: out.good(banner_line) if not banner.valid_ascii: # NOTE: RFC 4253, Section 4.2 out.warn('(gen) banner contains non-printable ASCII') software = Software.parse(banner) if software is not None: out.good('(gen) software: {}'.format(software)) else: software = None output_compatibility(out, algs, client_audit) if kex is not None: compressions = [x for x in kex.server.compression if x != 'none'] if len(compressions) > 0: cmptxt = 'enabled ({})'.format(', '.join(compressions)) else: cmptxt = 'disabled' out.good('(gen) compression: {}'.format(cmptxt)) if not out.is_section_empty() and not aconf.json: # Print output when it exists and JSON output isn't requested. out.head('# general') out.flush_section() out.sep() maxlen = algs.maxlen + 1 output_security(out, banner, client_audit, maxlen, aconf.json) # Filled in by output_algorithms() with unidentified algs. unknown_algorithms: List[str] = [] if pkm is not None: adb = SSH1_KexDB.ALGORITHMS ciphers = pkm.supported_ciphers auths = pkm.supported_authentications title, atype = 'SSH1 host-key algorithms', 'key' program_retval = output_algorithms(out, title, adb, atype, ['ssh-rsa1'], unknown_algorithms, aconf.json, program_retval, maxlen) title, atype = 'SSH1 encryption algorithms (ciphers)', 'enc' program_retval = output_algorithms(out, title, adb, atype, ciphers, unknown_algorithms, aconf.json, program_retval, maxlen) title, atype = 'SSH1 authentication types', 'aut' program_retval = output_algorithms(out, title, adb, atype, auths, unknown_algorithms, aconf.json, program_retval, maxlen) if kex is not None: adb = SSH2_KexDB.ALGORITHMS title, atype = 'key exchange algorithms', 'kex' program_retval = output_algorithms(out, title, adb, atype, kex.kex_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.dh_modulus_sizes()) title, atype = 'host-key algorithms', 'key' program_retval = output_algorithms(out, title, adb, atype, kex.key_algorithms, unknown_algorithms, aconf.json, program_retval, maxlen, kex.rsa_key_sizes()) title, atype = 'encryption algorithms (ciphers)', 'enc' program_retval = output_algorithms(out, title, adb, atype, kex.server.encryption, unknown_algorithms, aconf.json, program_retval, maxlen) title, atype = 'message authentication code algorithms', 'mac' program_retval = output_algorithms(out, title, adb, atype, kex.server.mac, unknown_algorithms, aconf.json, program_retval, maxlen) output_fingerprints(out, algs, aconf.json) perfect_config = output_recommendations(out, algs, software, aconf.json, maxlen) output_info(out, software, client_audit, not perfect_config, aconf.json) if aconf.json: out.reset() # Build & write the JSON struct. out.info(json.dumps(build_struct(aconf.host + ":" + str(aconf.port), banner, kex=kex, client_host=client_host), indent=4 if aconf.json_print_indent else None, sort_keys=True)) elif len(unknown_algorithms) > 0: # If we encountered any unknown algorithms, ask the user to report them. out.warn("\n\n!!! WARNING: unknown algorithm(s) found!: %s. Please email the full output above to the maintainer ([email protected]), or create a Github issue at <https://github.com/jtesta/ssh-audit/issues>.\n" % ','.join(unknown_algorithms)) return program_retval