Nmap Development mailing list archives
Re: [NSE script] SSH1 Hostkey
From: Sven Klemm <sven () c3d2 de>
Date: Mon, 01 Sep 2008 14:57:18 +0200
doug () hcsw org wrote:
On Tue, Aug 05, 2008 at 05:57:04PM +0000 or thereabouts, Brandon Enright wrote:Also, since you seem to be a NSE ninja ;-), you might think about adding a bubblebabble output option for the fingerprints. Around here all the Solaris guys still use that output...Or, if you're feeling really ambitious, there's the new "visual" format included with OpenSSH 5.1:
I've added the visual and bubblebabble fingerprint format to the script. You can now specify which format you want with the ssh_hostkey script argument. Possible values are hex,bubble,visual,full and all. If you specify no output format the hex fingerprint will be shown.
Here is an example:./nmap --script SSH-hostkey -p22 localhost --script-args ssh_hostkey='hex bubble visual'
Starting Nmap 4.68 ( http://nmap.org ) at 2008-09-01 14:53 CEST Interesting ports on localhost (127.0.0.1): PORT STATE SERVICE 22/tcp open ssh | SSH Hostkey: 2048 f0:58:ce:f4:aa:a4:59:1c:8e:dd:4d:07:44:c8:25:11 (RSA)| 2048 xuvah-degyp-nabus-zegah-hebur-nopig-bubig-difeg-hisym-rumef-cuxex (RSA)
| +--[ RSA 2048]----+ | | .E*+ | | | oo | | | . o . | | | O . . | | | o S o . | | | = o + . | | | . * o . | | | = . | | | o . | |_ +-----------------+ Nmap done: 1 IP address (1 host up) scanned in 0.06 seconds Cheers, Sven -- Sven Klemm http://cthulhu.c3d2.de/~sven/
--- Shows SSH Hostkeys -- -- Shows fingerprint or fingerprint and key depending on verbosity level. -- Puts the found hostkeys in nmap.registry for other scripts to use them. -- You can control the output with the ssh_hostkey script argument. Possible -- values are bubble,visual,full and all. -- -- 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' -- --@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 -- | SSH Hostkey: 2048 xuvah-degyp-nabus-zegah-hebur-nopig-bubig-difeg-hisym-rumef-cuxex (RSA) -- |_ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAwVuv2gcr0maaKQ69VVIEv2ob4OxnuI64fkeOnCXD1lUx5tTA+vefXUWEMxgMuA7iX4irJHy2zer0NQ3Z3yJvr5scPgTYIaEOp5Uo/eGFG9Agpk5wE8CoF0e47iCAPHqzlmP2V7aNURLMODb3jVZuI07A2ZRrMGrD8d888E2ORVORv1rYeTYCqcMMoVFmX9l3gWEdk4yx3w5sD8v501Iuyd1v19mPfyhrI5E1E1nl/Xjp5N0/xP2GUBrdkDMxKaxqTPMie/f0dXBUPQQN697a5q+5lBRPhKYOtn6yQKCd9s1Q22nxn72Jmi1RzbMyYJ52FosDT755Qmb46GLrDMaZMQ== require("stdnse") require("shortport") require("openssl") require("bin") require("bit") require("base64") require("hash") id = "SSH Hostkey" author = "Sven Klemm <sven () c3d2 de>" description = "Show SSH Hostkeys" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"safe","default","intrusive"} portrule = shortport.port_or_service(22, "ssh") local key_type_algorithm = {rsa1="RSA1",['ssh-rsa']="RSA",['ssh-dss']="DSA"} --- format key as hexadecimal fingerprint local format_key_hex_fingerprint = function( key ) local fingerprint = hash.md5( key.fp_input ) local s = fingerprint:sub( 1, 2 ) for i = 3, #fingerprint, 2 do s = s .. ':' .. fingerprint:sub( i, i + 1 ) end return ("%d %s (%s)"):format( key.bits, s, key_type_algorithm[key.key_type] ) end --- format key as bubblebabble fingerprint local format_key_bubble_fingerprint = function( key ) local vowels = {'a','e','i','o','u','y'} local consonants = {'b','c','d','f','g','h','k','l','m','n','p','r','s','t','v','z','x'} local s = "x" local seed = 1 local fingerprint = hash.sha1( key.fp_input ) for i=1,#fingerprint+4,4 do local in1,in2,idx1,idx2,idx3,idx4,idx5 if i < #fingerprint or #fingerprint / 2 % 2 ~= 0 then in1 = tonumber( fingerprint:sub(i,i+1), 16 ) idx1 = (bit.band(bit.rshift(in1,6),3) + seed) % 6 + 1 idx2 = bit.band(bit.rshift(in1,2),15) + 1 idx3 = (bit.band(in1,3) + math.floor(seed/6)) % 6 + 1 s = s .. vowels[idx1] .. consonants[idx2] .. vowels[idx3] if i < #fingerprint then in2 = tonumber( fingerprint:sub(i+2,i+3), 16 ) idx4 = bit.band(bit.rshift(in2,4),15) + 1 idx5 = bit.band(in2,15) + 1 s = s .. consonants[idx4] .. '-' .. consonants[idx5] seed = (seed * 5 + in1 * 7 + in2) % 36 end else idx1 = seed % 6 + 1 idx2 = 16 + 1 idx3 = math.floor(seed/6) + 1 s = s .. vowels[idx1] .. consonants[idx2] .. vowels[idx3] end end s = s .. 'x' return ("%d %s (%s)"):format( key.bits, s, key_type_algorithm[key.key_type] ) end --- format key as visual fingerprint -- ported from http://www.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/key.c local format_key_visual_fingerprint = function( key ) local i,j,field,characters,input,fieldsize_x,fieldsize_y,s,fingerprint fingerprint = hash.md5( key.fp_input ) fieldsize_x, fieldsize_y = 17, 9 characters = {' ','.','o','+','=','*','B','O','X','@','%','&','#','/','^','S','E'} -- initialize drawing area field = {} for i=1,fieldsize_x do field[i]={} for j=1,fieldsize_y do field[i][j]=1 end end -- we start in the center and mark it x, y = math.ceil(fieldsize_x/2), math.ceil(fieldsize_y/2) field[x][y] = #characters - 1; -- iterate over fingerprint for i=1,#fingerprint,2 do input = tonumber( fingerprint:sub(i,i+1), 16 ) -- each byte conveys four 2-bit move commands for j=1,4 do if bit.band( input, 1) == 1 then x = x + 1 else x = x - 1 end if bit.band( input, 2) == 2 then y = y + 1 else y = y - 1 end x = math.max(x,1); x = math.min(x,fieldsize_x) y = math.max(y,1); y = math.min(y,fieldsize_y) if field[x][y] < #characters - 2 then field[x][y] = field[x][y] + 1 end input = bit.rshift( input, 2 ) end end -- mark end point field[x][y] = #characters; -- build output s = ('\n+--[%4s %4d]----+\n'):format( key_type_algorithm[key.key_type], key.bits ) for i=1,fieldsize_y do s = s .. '|' for j=1,fieldsize_x do s = s .. characters[ field[j][i] ] end s = s .. '|\n' end s = s .. '+-----------------+\n' return s end --- format full key for displaying --@param key table as returned by fetch_host_key --@return Formated key local format_key_full = function( key ) local full_key = "" if key.key_type == 'rsa1' then full_key = key.exp:to_dec() .. ' ' .. key.mod:to_dec() elseif key.key_type == 'ssh-dss' or key.key_type == 'ssh-rsa' then full_key = ('%s %s'):format( key.key_type, base64.enc( key.key ) ) else stdnse.print_debug( "Unsupported key type: %s", key.key_type ) end return full_key end --- SSH1 functions local ssh1 = { --- fetch SSH1 host key --@param host nmap host table --@param port nmap port table fetch_host_key = function(host, port) local socket = nmap.new_socket() local catch = function() socket:close() end local try = nmap.new_try(catch) try(socket:connect(host.ip, port.number)) -- fetch banner try(socket:receive_lines(1)) -- send our banner try(socket:send("SSH-1.5-Nmap-SSH1-Hostkey\r\n")) local data, packet_length, padding, offset data = try(socket:receive()) socket:close() offset, packet_length = bin.unpack( ">i", data ) padding = 8 - packet_length % 8 offset = offset + padding if padding + packet_length + 4 == data:len() then -- seems to be a proper SSH1 packet local msg_code,host_key_bits,exp,mod,length offset, msg_code = bin.unpack( ">c", data, offset ) if msg_code == 2 then -- 2 => SSH_SMSG_PUBLIC_KEY -- ignore cookie and server key bits offset, _, _ = bin.unpack( ">A8i", data, offset ) -- skip server key exponent and modulus offset, length = bin.unpack( ">S", data, offset ) offset = offset + math.ceil( length / 8 ) offset, length = bin.unpack( ">S", data, offset ) offset = offset + math.ceil( length / 8 ) offset, host_key_bits = bin.unpack( ">i", data, offset ) offset, length = bin.unpack( ">S", data, offset ) offset, exp = bin.unpack( ">A" .. math.ceil( length / 8 ), data, offset ) exp = openssl.bignum_bin2bn( exp ) offset, length = bin.unpack( ">S", data, offset ) offset, mod = bin.unpack( ">A" .. math.ceil( length / 8 ), data, offset ) mod = openssl.bignum_bin2bn( mod ) return {exp=exp,mod=mod,bits=host_key_bits,key_type='rsa1',fp_input=mod:to_bin()..exp:to_bin()} end end end } --- SSH2 functions local ssh2 ssh2 = { transport = { --- pack multiprecision integer for sending --@param bn openssl bignum --@return packed multiprecision integer pack_mpint = function( bn ) local bytes, packed bytes = bn:num_bytes() packed = bn:to_bin() if bytes % 8 == 0 then bytes = bytes + 1 packed = string.char(0) .. packed end return bin.pack( ">IA", bytes, packed ) end, --- build a ssh2 packet --@param payload payload of the packet --@return packet to send on the wire build = function( payload ) local packet_length, padding_length padding_length = 8 - ( (payload:len() + 1 + 4 ) % 8 ) packet_length = payload:len() + padding_length + 1 return bin.pack( ">IcAA", packet_length, padding_length, payload, openssl.rand_pseudo_bytes( padding_length ) ) end, --- extract the payload from a received SSH2 packet --@param received SSH2 packet --@return payload of the SSH2 packet payload = function( packet ) local packet_length, padding_length, payload_length, payload, offset offset, packet_length, padding_length = bin.unpack( ">Ic", packet ) payload_length = packet_length - padding_length - 1 offset, payload = bin.unpack( ">A" .. payload_length, packet, offset ) return payload end, --- build dh_gex_request packet dh_gex_request = function( min, opt, max ) return bin.pack( ">cIII", 34, min, opt, max ) end, --- build kexdh_init packet kexdh_init = function( e ) return bin.pack( ">cA", 30, ssh2.transport.pack_mpint( e ) ) end, --- build kex_init packet kex_init = function( cookie, options ) options = options or {} kex_algorithms = "diffie-hellman-group1-sha1" host_key_algorithms = options['host_key_algorithms'] or "ssh-dss,ssh-rsa" encryption_algorithms = "aes128-cbc,3des-cbc,blowfish-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr" mac_algorithms = "hmac-md5,hmac-sha1,hmac-ripemd160" compression_algorithms = "none" languages = "" local payload = bin.pack( ">cAaa", 20, cookie, kex_algorithms, host_key_algorithms ) payload = payload .. bin.pack( ">aa", encryption_algorithms, encryption_algorithms ) payload = payload .. bin.pack( ">aa", mac_algorithms, mac_algorithms ) payload = payload .. bin.pack( ">aa", compression_algorithms, compression_algorithms ) payload = payload .. bin.pack( ">aa", languages, languages ) payload = payload .. bin.pack( ">cI", 0, 0 ) return payload end, --- parse kexinit package -- returns an empty table in case of an error parse_kex_init = function( payload ) local _, offset, msg_code, parsed, fields, fieldname parsed = {} -- check for proper msg code offset, msg_code = bin.unpack( ">c", payload ) if msg_code ~= 20 then return {} end offset, parsed.cookie = bin.unpack( ">A16", payload, offset ) fields = {'kex_algorithms','server_host_key_algorithms', 'encryption_algorithms_client_to_server','encryption_algorithms_server_to_client', 'mac_algorithms_client_to_server','mac_algorithms_server_to_client', 'compression_algorithms_client_to_server','compression_algorithms_server_to_client', 'languages_client_to_server','languages_server_to_client'} for _, fieldname in pairs( fields ) do offset, parsed[fieldname] = bin.unpack( ">a", payload, offset ) end return parsed end }, --- fetch SSH2 host key --@param host nmap host table --@param port nmap port table --@param key_type key type to fetch --@return table containing the key and fingerprint fetch_host_key = function( host, port, key_type ) local socket = nmap.new_socket() local catch = function() socket:close() end local try = nmap.new_try(catch) -- oakley group 2 prime taken from rfc 2409 local prime = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" try(socket:connect(host.ip, port.number)) -- fetch banner try(socket:receive_lines(1)) -- send our banner try(socket:send("SSH-2.0-Nmap-SSH2-Hostkey\r\n")) local cookie = openssl.rand_bytes( 16 ) local packet = ssh2.transport.build( ssh2.transport.kex_init( cookie, {host_key_algorithms=key_type} ) ) try(socket:send( packet )) local kex_init = try(socket:receive_bytes(1)) kex_init = ssh2.transport.parse_kex_init( ssh2.transport.payload( kex_init ) ) if not tostring(kex_init.server_host_key_algorithms):find( key_type, 1, true ) then -- server does not support host key type stdnse.print_debug( 2, "Hostkey type '%s' not supported by server.", key_type ) return end local e, g, x, p -- e = g^x mod p g = openssl.bignum_dec2bn( "2" ) p = openssl.bignum_hex2bn( prime ) x = openssl.bignum_pseudo_rand( 1024 ) e = openssl.bignum_mod_exp( g, x, p ) packet = ssh2.transport.build( ssh2.transport.kexdh_init( e ) ) try(socket:send( packet )) kexdh_reply = try(socket:receive_bytes(1)) kexdh_reply = ssh2.transport.payload( kexdh_reply ) -- check for proper msg code if kexdh_reply:byte(1) ~= 31 then return end local _,public_host_key,bits _, _, public_host_key = bin.unpack( ">ca", kexdh_reply ) if key_type == 'ssh-dss' then local p _, _, p = bin.unpack( ">aa", public_host_key ) bits = openssl.bignum_bin2bn( p ):num_bits() elseif key_type == 'ssh-rsa' then local n _, _, _, n = bin.unpack( ">aaa", public_host_key ) bits = openssl.bignum_bin2bn( n ):num_bits() else stdnse.print_debug( "Unsupported key type: %s", key_type ) end return {key=public_host_key,key_type=key_type,fp_input=public_host_key,bits=bits} 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[id] = nmap.registry[id] or {} nmap.registry[id][host.ip] = nmap.registry[id][host.ip] or {} local registry = nmap.registry[id][host.ip] table.insert( registry, key ) end action = function(host, port) local output = {} local keys = {} local _,key local format = nmap.registry.args.ssh_hostkey or "hex" local all_formats = format:find( 'all', 1, true ) 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 for _, key in ipairs( keys ) do add_key_to_registry( host, key ) if format:find( 'hex', 1, true ) or all_formats then table.insert( output, format_key_hex_fingerprint( key ) ) end if format:find( 'bubble', 1, true ) or all_formats then table.insert( output, format_key_bubble_fingerprint( key ) ) end if format:find( 'visual', 1, true ) or all_formats then table.insert( output, format_key_visual_fingerprint( key ) ) end if nmap.verbosity() > 1 or format:find( 'full', 1, true ) or all_formats then table.insert( output, format_key_full( key ) ) end end if #output > 0 then return table.concat( output, '\n' ) else return nil end end
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- [NSE script] SSH1 Hostkey Sven Klemm (Aug 03)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Aug 05)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Aug 05)
- Re: [NSE script] SSH1 Hostkey Brandon Enright (Aug 05)
- Re: [NSE script] SSH1 Hostkey doug (Aug 05)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Sep 01)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Aug 05)
- Re: [NSE script] SSH1 Hostkey Brandon Enright (Aug 05)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Aug 05)
- Re: [NSE script] SSH1 Hostkey Sven Klemm (Aug 05)