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)
