Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/nmap/scripts/ |
Upload File : |
local ipOps = require "ipOps" local nmap = require "nmap" local shortport = require "shortport" local ssh1 = require "ssh1" local ssh2 = require "ssh2" local stdnse = require "stdnse" local string = require "string" local stringaux = require "stringaux" local table = require "table" local tableaux = require "tableaux" local base64 = require "base64" local comm = require "comm" local openssl = stdnse.silent_require "openssl" description = [[ Shows SSH hostkeys. Shows the target SSH server's key fingerprint and (with high enough verbosity level) the public key itself. It records the discovered host keys in <code>nmap.registry</code> for use by other scripts. Output can be controlled with the <code>ssh_hostkey</code> script argument. You may also compare the retrieved key with the keys in your known-hosts file using the <code>known-hosts</code> argument. The script also includes a postrule that check for duplicate hosts using the gathered keys. ]] --- --@usage -- nmap host --script ssh-hostkey --script-args ssh_hostkey=full -- nmap host --script ssh-hostkey --script-args ssh_hostkey=all -- nmap host --script ssh-hostkey --script-args ssh_hostkey='visual bubble' -- --@args ssh_hostkey Controls the output format of keys. Multiple values may be -- given, separated by spaces. Possible values are -- * <code>"full"</code>: The entire key, not just the fingerprint. -- * <code>"sha256"</code>: Base64-encoded SHA256 fingerprint. -- * <code>"md5"</code>: hex-encoded MD5 fingerprint (the default). -- * <code>"bubble"</code>: Bubble Babble output, -- * <code>"visual"</code>: Visual ASCII art representation. -- * <code>"all"</code>: All of the above. -- @args ssh-hostkey.known-hosts If this is set, the script will check if the -- known hosts file contains a key for the host being scanned and will compare -- it with the keys that have been found by the script. The script will try to -- detect your known-hosts file but you can, optionally, pass the path of the -- file to this option. -- -- @args ssh-hostkey.known-hosts-path. Path to a known_hosts file. --@output -- 22/tcp open ssh -- | ssh-hostkey: 2048 f0:58:ce:f4:aa:a4:59:1c:8e:dd:4d:07:44:c8:25:11 (RSA) -- 22/tcp open ssh -- | ssh-hostkey: 2048 f0:58:ce:f4:aa:a4:59:1c:8e:dd:4d:07:44:c8:25:11 (RSA) -- | +--[ RSA 2048]----+ -- | | .E*+ | -- | | oo | -- | | . o . | -- | | O . . | -- | | o S o . | -- | | = o + . | -- | | . * o . | -- | | = . | -- | | o . | -- |_ +-----------------+ -- 22/tcp open ssh syn-ack -- | ssh-hostkey: Key comparison with known_hosts file: -- | GOOD Matches in known_hosts file: -- | L7: 199.19.117.60 -- | L11: foo -- | L15: bar -- | L19: <unknown> -- | WRONG Matches in known_hosts file: -- | L3: 199.19.117.60 -- | ssh-hostkey: 2048 xuvah-degyp-nabus-zegah-hebur-nopig-bubig-difeg-hisym-rumef-cuxex (RSA) -- |_ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwVuv2gcr0maaKQ69VVIEv2ob4OxnuI64fkeOnCXD1lUx5tTA+vefXUWEMxgMuA7iX4irJHy2zer0NQ3Z3yJvr5scPgTYIaEOp5Uo/eGFG9Agpk5wE8CoF0e47iCAPHqzlmP2V7aNURLMODb3jVZuI07A2ZRrMGrD8d888E2ORVORv1rYeTYCqcMMoVFmX9l3gWEdk4yx3w5sD8v501Iuyd1v19mPfyhrI5E1E1nl/Xjp5N0/xP2GUBrdkDMxKaxqTPMie/f0dXBUPQQN697a5q+5lBRPhKYOtn6yQKCd9s1Q22nxn72Jmi1RzbMyYJ52FosDT755Qmb46GLrDMaZMQ== -- --@output -- Post-scan script results: -- | ssh-hostkey: Possible duplicate hosts -- | Key 1024 60:ac:4d:51:b1:cd:85:09:12:16:92:76:1d:5d:27:6e (DSA) used by: -- | 192.168.1.1 -- | 192.168.1.2 -- | Key 2048 2c:22:75:60:4b:c3:3b:18:a2:97:2c:96:7e:28:dc:dd (RSA) used by: -- | 192.168.1.1 -- |_ 192.168.1.2 -- --@xmloutput -- <table> -- <elem key="key">ssh-dss AAAAB3NzaC1kc3MAAACBANraqxAILTygMTgFu/0snrJck8BkhOpBbN61DAZENgeulLMaJdmNFWZpvhLOJVXSqHt2TCrspbMyvpBH4Fnv7Kb+QBAhXyzeCNnOQ7OVBfqNzkfezoFrQJgOQZSEenP6sCVDqcW2j0KVumnYdPU7FGa8SLfNqA+hUOR2HSSluynFAAAAFQDWKNq4PVbpDA7UExE8JSHnWxv4AwAAAIAWEDdNu5mWfTz52IdxELNjsmn5FvKRmnhPqq/PrTkYqAADL5WYazg7POQZ4yI2nqTq++47ONDK87Wke3qbeIhMrV13Mrgf2JuCUSNqrfEmvzZ2l9x3QyZrj+bJRPRuhwYq8rFup01qaANJ0p4WS/7voNbRhh+l57FkJF+XAJRRTAAAAIEAts1Se+u+hV9ZedXopzfXv1I5ZOSONxZanM10wjM2GRWygCYsHqDM315swBPkzhmB73oBesnhDW3bq0dmW3wvk4gzQZ2E2SHhzVGjlgDpjEahlQ+XGpDZsvqqFGGGx8lvKYFUxBR+UkqMRGmjkHw5sK5ydO1n4R3XJ4FfQFqmoyU=</elem> -- <elem key="bits">1024</elem> -- <elem key="fingerprint">18782fd3be7178a38e584b5a83bd60a8</elem> -- <elem key="type">ssh-dss</elem> -- </table> -- <table> -- <elem key="key">ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwVuv2gcr0maaKQ69VVIEv2ob4OxnuI64fkeOnCXD1lUx5tTA+vefXUWEMxgMuA7iX4irJHy2zer0NQ3Z3yJvr5scPgTYIaEOp5Uo/eGFG9Agpk5wE8CoF0e47iCAPHqzlmP2V7aNURLMODb3jVZuI07A2ZRrMGrD8d888E2ORVORv1rYeTYCqcMMoVFmX9l3gWEdk4yx3w5sD8v501Iuyd1v19mPfyhrI5E1E1nl/Xjp5N0/xP2GUBrdkDMxKaxqTPMie/f0dXBUPQQN697a5q+5lBRPhKYOtn6yQKCd9s1Q22nxn72Jmi1RzbMyYJ52FosDT755Qmb46GLrDMaZMQ==</elem> -- <elem key="bits">2048</elem> -- <elem key="fingerprint">f058cef4aaa4591c8edd4d0744c82511</elem> -- <elem key="type">ssh-rsa</elem> -- </table> -- <table key="Key comparison with known_hosts file"> -- <table key="GOOD Matches in known_hosts file"> -- <table> -- <elem key="lnumber">5</elem> -- <elem key="name">localhost</elem> -- <elem key="key">ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwVuv2gcr0maaKQ69VVIEv2ob4OxnuI64fkeOnCXD1lUx5tTA+vefXUWEMxgMuA7iX4irJHy2zer0NQ3Z3yJvr5scPgTYIaEOp5Uo/eGFG9Agpk5wE8CoF0e47iCAPHqzlmP2V7aNURLMODb3jVZuI07A2ZRrMGrD8d888E2ORVORv1rYeTYCqcMMoVFmX9l3gWEdk4yx3w5sD8v501Iuyd1v19mPfyhrI5E1E1nl/Xjp5N0/xP2GUBrdkDMxKaxqTPMie/f0dXBUPQQN697a5q+5lBRPhKYOtn6yQKCd9s1Q22nxn72Jmi1RzbMyYJ52FosDT755Qmb46GLrDMaZMQ==</elem> -- </table> -- </table> -- </table> -- --@xmloutput -- <table> -- <table key="hosts"> -- <elem>192.168.1.1</elem> -- <elem>192.168.1.2</elem> -- </table> -- <table key="key"> -- <elem key="fingerprint">2c2275604bc33b18a2972c967e28dcdd</elem> -- <elem key="bits">2048</elem> -- <elem key="type">ssh-rsa</elem> -- </table> -- </table> -- <table> -- <table key="hosts"> -- <elem>192.168.1.1</elem> -- <elem>192.168.1.2</elem> -- </table> -- <table key="key"> -- <elem key="fingerprint">60ac4d51b1cd8509121692761d5d276e</elem> -- <elem key="bits">1024</elem> -- <elem key="type">ssh-dss</elem> -- </table> -- </table> author = {"Sven Klemm", "Piotr Olma", "George Chatzisofroniou"} license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"safe","default","discovery"} portrule = shortport.ssh postrule = function() return (nmap.registry.sshhostkey ~= nil) end --- put hostkey in the nmap registry for usage by other scripts --@param host nmap host table --@param key host key table local add_key_to_registry = function( host, key ) nmap.registry.sshhostkey = nmap.registry.sshhostkey or {} nmap.registry.sshhostkey[host.ip] = nmap.registry.sshhostkey[host.ip] or {} table.insert( nmap.registry.sshhostkey[host.ip], key ) end --- check if there is a key in known_hosts file for the host that's being scanned --- and if there is, compare the keys local function check_keys(host, keys, f) local keys_found = {} for _,k in ipairs(keys) do table.insert(keys_found, k.full_key) end local keys_from_file = {} local same_key, same_key_hashed = {}, {} local hostname = host.name == "" and nil or host.name local possible_host_names = {hostname or nil, host.ip or nil, (hostname and host.ip) and ("%s,%s"):format(hostname, host.ip) or nil} for _p, parts in ipairs(f) do local lnumber = parts.linenumber parts = parts.entry local foundhostname = false if #parts >= 3 then -- the line might be hashed if string.match(parts[1], "^|") then -- split the first part of the line - it contains base64'ed salt and hashed hostname local parts_hostname = stringaux.strsplit("|", parts[1]) if #parts_hostname == 4 then -- check if the hash corresponds to the host being scanned local salt = base64.dec(parts_hostname[3]) for _,name in ipairs(possible_host_names) do local hash = base64.enc(openssl.hmac("SHA1", salt, name)) if parts_hostname[4] == hash then stdnse.debug2("found a hash that matches: %s for hostname: %s", hash, name) foundhostname = true table.insert(keys_from_file, {name=name, key=("%s %s"):format(parts[2], parts[3]), lnumber=lnumber}) end end -- Is the key the same but the hashed hostname isn't? if not foundhostname then for _, k in ipairs(keys_found) do if ("%s %s"):format(parts[2], parts[3]) == k then table.insert(same_key_hashed, {name="<unknown>", key=k, lnumber = lnumber}) end end end end else if tableaux.contains(possible_host_names, parts[1]) then stdnse.debug2("Found an entry that matches: %s", parts[1]) table.insert(keys_from_file, ("%s %s"):format(parts[2], parts[3])) else -- Is the key the same but the clear text hostname isn't? for _, k in ipairs(keys_found) do if ("%s %s"):format(parts[2], parts[3]) == k then table.insert(same_key, {name=parts[1], key=("%s %s"):format(parts[2], parts[3]), lnumber=lnumber}) end end end end end end local matched_keys, different_keys = {}, {} local matched -- Compare the keys found for this hostname and update the counts. for _,k in ipairs(keys_from_file) do matched = false for __,l in ipairs(keys_found) do if l == k.key then table.insert(matched_keys, k) matched = true end end if not matched then table.insert(different_keys, k) end end -- Start making output. local out if #keys_from_file == 0 then out = "No entry for scanned host found in known_hosts file." else out = stdnse.output_table() local match_mt = { __tostring = function(self) return string.format("L%d: %s", self.lnumber, self.name) end } local good = {} for __, gm in ipairs(matched_keys) do setmetatable(gm, match_mt) good[#good+1] = gm end for __, gm in ipairs(same_key) do setmetatable(gm, match_mt) good[#good+1] = gm end for __, gm in ipairs(same_key_hashed) do setmetatable(gm, match_mt) good[#good+1] = gm end if #good > 0 then out["GOOD Matches in known_hosts file"] = good end local wrong = {} for __, gm in ipairs(different_keys) do setmetatable(gm, match_mt) wrong[#wrong+1] = gm end if #wrong > 0 then out["WRONG Matches in known_hosts file"] = wrong end end return out end --- gather host keys --@param host nmap host table --@param port nmap port table of the currently probed port local function portaction(host, port) if port.version.name_confidence < 8 or port.version.name ~= "ssh" then -- additional check if version scan was not done or if it doesn't think it's SSH. -- Since the fetch_host_key functions don't indicate what failed, we could -- waste a lot of time on e.g. tcpwrapped port 22 -- Using opencon instead of get_banner to avoid trying SSL first in some cases local status, banner = comm.opencon(host, port, nil, {recv_before=true}) if not string.match(banner, "^SSH") then stdnse.debug1("Service does not appear to be SSH: quitting.") return nil end end local output_tab = {} local keys = {} local key local format = nmap.registry.args.ssh_hostkey or "md5" local format_bits = { md5 = 1, hex = 1, -- compatibility alias for md5 sha256 = 1 << 1, bubble = 1 << 2, visual = 1 << 3, full = 1 << 4, all = 0xffff, } local format_mask = 0 for word in format:gmatch("%w+") do format_mask = format_mask | (format_bits[word] or 0) end key = ssh1.fetch_host_key( host, port ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ssh-dss" ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ssh-rsa" ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ecdsa-sha2-nistp256" ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ecdsa-sha2-nistp384" ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ecdsa-sha2-nistp521" ) if key then table.insert( keys, key ) end key = ssh2.fetch_host_key( host, port, "ssh-ed25519" ) if key then table.insert( keys, key ) end if #keys == 0 then return nil end for _, key in ipairs( keys ) do add_key_to_registry( host, key ) local output = {} local out = { fingerprint=stdnse.tohex(key.fingerprint), type=key.key_type, bits=key.bits, key=key.key, } if format_mask & format_bits.md5 ~= 0 then table.insert( output, ssh1.fingerprint_hex( key.fingerprint, key.algorithm, key.bits ) ) end if format_mask & format_bits.sha256 ~= 0 then table.insert( output, ssh1.fingerprint_base64( key.fp_sha256, "SHA256", key.algorithm, key.bits ) ) end if format_mask & format_bits.bubble ~= 0 then table.insert( output, ssh1.fingerprint_bubblebabble( openssl.sha1(key.fp_input), key.algorithm, key.bits ) ) end if format_mask & format_bits.visual ~= 0 then table.insert( output, ssh1.fingerprint_visual( key.fingerprint, key.algorithm, key.bits ) ) end if nmap.verbosity() > 1 or format_mask & format_bits.full ~= 0 then table.insert( output, key.full_key ) end setmetatable(out, { __tostring = function(self) return table.concat(output, "\n") end }) table.insert(output_tab, out) end -- if a known_hosts file was given, then check if it contains a key for the host being scanned local known_hosts = stdnse.get_script_args("ssh-hostkey.known-hosts") or false if known_hosts then known_hosts = ssh1.parse_known_hosts_file(known_hosts) output_tab["Key comparison with known_hosts file"] = check_keys( host, keys, known_hosts) end return output_tab end --- iterate over the list of gathered keys and look for duplicate hosts (sharing the same hostkeys) local function postaction() local hostkeys = {} local output = {} local output_tab = {} local revmap = {} -- create a reverse mapping key_fingerprint -> host(s) for ip, keys in pairs(nmap.registry.sshhostkey) do for _, key in ipairs(keys) do local fp = ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits) if not hostkeys[fp] then hostkeys[fp] = {} revmap[fp] = { fingerprint=stdnse.tohex(key.fingerprint,{separator=":"}), type=key.key_type, bits=key.bits } end -- discard duplicate IPs if not tableaux.contains(hostkeys[fp], ip) then table.insert(hostkeys[fp], ip) end end end -- look for hosts using the same hostkey for key, hosts in pairs(hostkeys) do if #hostkeys[key] > 1 then table.sort(hostkeys[key], function(a, b) return ipOps.compare_ip(a, "lt", b) end) local str = {'Key ' .. key .. ' used by:'} local tab = {key=revmap[key], hosts={}} for _, host in ipairs(hostkeys[key]) do str[#str+1] = host table.insert(tab.hosts, host) end table.insert(output, table.concat(str, "\n ")) table.insert(output_tab, tab) end end if #output > 0 then return output_tab, 'Possible duplicate hosts\n' .. table.concat(output, '\n') end end local ActionsTable = { -- portrule: retrieve ssh hostkey portrule = portaction, -- postrule: look for duplicate hosts (same hostkey) postrule = postaction } -- execute the action function corresponding to the current rule action = function(...) return ActionsTable[SCRIPT_TYPE](...) end