Nmap Development mailing list archives
Re: [NSE script] SSH Hostkey(s) SSH1 and SSH2
From: Sven Klemm <sven () c3d2 de>
Date: Sat, 16 Aug 2008 17:27:15 +0200
Hi,I've attached an updated version which fixes a few bugs and adds the found keys to the registry so other scripts can pick them up. I also
added documentation to the script. 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. -- --@output -- 22/tcp open ssh -- | SSH Hostkey: rsa1 1024 89:7c:8b:2e:ee:5c:3d:ab:20:bd:d7:b3:a4:5a:a8:80 -- | ssh-dss 1024 23:23:8c:73:26:22:4a:63:d8:5d:41:eb:86:cf:a0:58 -- |_ ssh-rsa 2048 f0:58:ce:f4:aa:a4:59:1c:8e:dd:4d:07:44:c8:25:11 require("stdnse") require("shortport") require("openssl") require("bin") 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") --- format fingerprint nicely for displaying --@param fp fingerprint as hexencoded string --@return Formated fingerprint local format_fingerprint = function( fp ) local s = fp:sub( 1, 2 ) for i = 3, #fp, 2 do s = s .. ':' .. fp:sub( i, i + 1 ) end return s end --- format key nicely for displaying depending on key type --@param key table as returned by fetch_host_key --@return Formated key local format_key = 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 = base64.enc( key.key ) else stdnse.print_debug( "Unsupported key type: " .. 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',fingerprint=hash.md5(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 }, --- 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.payload( kex_init ) -- check for proper msg code if kex_init:byte(1) ~= 20 then 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, p, x ) packet = ssh2.transport.build( ssh2.transport.kexdh_init( e ) ) try(socket:send( packet )) local 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: " .. key_type ) end return {key=public_host_key,key_type=key_type,fingerprint=hash.md5(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,out 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 ) out = ("%s %d %s"):format(key.key_type, key.bits, format_fingerprint( key.fingerprint )) if nmap.verbosity() > 1 then out = ("%s %s"):format( out, format_key( key ) ) end table.insert( output, out ) 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] SSH Hostkey(s) SSH1 and SSH2 Sven Klemm (Aug 05)
- Re: [NSE script] SSH Hostkey(s) SSH1 and SSH2 Sven Klemm (Aug 16)