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)
