mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 21:21:31 +00:00
Use internal cipher/handshake scoring system instead of static datafile
This commit is contained in:
388
nselib/tls.lua
388
nselib/tls.lua
@@ -583,6 +583,18 @@ CIPHERS = {
|
|||||||
["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF,
|
["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function find_key(t, value)
|
||||||
|
local k, v
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if v == value then
|
||||||
|
return k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
-- Keep this local to enforce use of the cipher_info function
|
-- Keep this local to enforce use of the cipher_info function
|
||||||
local cipher_info_cache = {
|
local cipher_info_cache = {
|
||||||
-- pre-populate the special cases that break the parser below
|
-- pre-populate the special cases that break the parser below
|
||||||
@@ -637,13 +649,323 @@ local cipher_info_cache = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- A couple helpers for server_key_exchange parsing
|
||||||
|
local function unpack_dhparams (blob, pos)
|
||||||
|
local p, g, y
|
||||||
|
pos, p, g, y = bin.unpack(">PPP", blob)
|
||||||
|
return pos, {p=p, g=g, y=y}, rsa_equiv("dh", #p)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unpack_ecdhparams (blob, pos)
|
||||||
|
local eccurvetype
|
||||||
|
pos, eccurvetype = bin.unpack("C", blob, pos)
|
||||||
|
local ret = {}
|
||||||
|
local strength
|
||||||
|
if eccurvetype == 1 then
|
||||||
|
local p, a, b, base, order, cofactor
|
||||||
|
pos, p, a, b, base, order, cofactor = bin.unpack("pppppp", blob, pos)
|
||||||
|
strength = rsa_equiv("ec", #p)
|
||||||
|
ret.curve_params = {
|
||||||
|
ec_curve_type = "explicit_prime",
|
||||||
|
prime_p=p, curve={a=a, b=b}, base=base, order=order, cofactor=cofactor
|
||||||
|
}
|
||||||
|
elseif eccurvetype == 2 then
|
||||||
|
local p = {}
|
||||||
|
local m, basis
|
||||||
|
pos, m, basis = bin.unpack(">SC", blob, pos)
|
||||||
|
strength = rsa_equiv("ec", m)
|
||||||
|
if basis == 1 then -- ec_trinomial
|
||||||
|
pos, p.k = bin.unpack("p", blob, pos)
|
||||||
|
elseif basis == 2 then -- ec_pentanomial
|
||||||
|
pos, p.k1, p.k2, p.k3 = bin.unpack("ppp", blob, pos)
|
||||||
|
end
|
||||||
|
local a, b, base, order, cofactor
|
||||||
|
pos, a, b, base, order, cofactor = bin.unpack("ppppp", blob, pos)
|
||||||
|
ret.curve_params = {
|
||||||
|
ec_curve_type = "explicit_char2",
|
||||||
|
m=m, basis=basis, field=p, curve={a=a, b=b}, base=base, order=order, cofactor=cofactor
|
||||||
|
}
|
||||||
|
elseif eccurvetype == 3 then
|
||||||
|
local curve
|
||||||
|
pos, curve = bin.unpack(">S", blob, pos)
|
||||||
|
ret.curve_params = {
|
||||||
|
ec_curve_type = "namedcurve",
|
||||||
|
curve = find_key(ELLIPTIC_CURVES, curve)
|
||||||
|
}
|
||||||
|
local size = ret.curve_params.curve:match("(%d+)[rk]%d$")
|
||||||
|
if size then
|
||||||
|
strength = rsa_equiv("ec", tonumber(size))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pos, ret.public = bin.unpack("p", blob, pos)
|
||||||
|
return pos, ret, strength
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unpack_signed (blob, pos, protocol)
|
||||||
|
if pos > #blob then -- not-signed
|
||||||
|
return pos, nil
|
||||||
|
end
|
||||||
|
local hash_alg, sig_alg, sig
|
||||||
|
-- TLSv1.2 changed to allow arbitrary hash and sig algorithms
|
||||||
|
if protocol and PROTOCOLS[protocol] >= 0x0303 then
|
||||||
|
pos, hash_alg, sig_alg, sig = bin.unpack("CC>P", blob, pos)
|
||||||
|
else
|
||||||
|
pos, sig = bin.unpack(">P", blob, pos)
|
||||||
|
end
|
||||||
|
return pos, {hash_algorithm=hash_alg, signature_algorithm=sig_alg, signature=sig}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the strength-equivalent RSA key size
|
||||||
|
--
|
||||||
|
-- Based on NIST SP800-57 part 1 rev 3
|
||||||
|
-- @param ktype Key type ("dh", "ec", "rsa", "dsa")
|
||||||
|
-- @param bits Size of key in bits
|
||||||
|
-- @return Size in bits of RSA key with equivalent strength
|
||||||
|
function rsa_equiv (ktype, bits)
|
||||||
|
if ktype == "rsa" or ktype == "dsa" or ktype == "dh" then
|
||||||
|
return bits
|
||||||
|
elseif ktype == "ec" then
|
||||||
|
if bits < 160 then
|
||||||
|
return 512 -- Possibly down to 0, but details not published
|
||||||
|
elseif bits < 224 then
|
||||||
|
return 1024
|
||||||
|
elseif bits < 256 then
|
||||||
|
return 2048
|
||||||
|
elseif bits < 384 then
|
||||||
|
return 3072
|
||||||
|
elseif bits < 512 then
|
||||||
|
return 7680
|
||||||
|
else -- 512+
|
||||||
|
return 15360
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
KEX_ALGORITHMS = {}
|
||||||
|
|
||||||
|
-- RFC 5246
|
||||||
|
KEX_ALGORITHMS.NULL = { anon = true }
|
||||||
|
KEX_ALGORITHMS.DH_anon = {
|
||||||
|
anon = true,
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.dhparams, ret.strength = unpack_dhparams(blob)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DH_anon_EXPORT = {
|
||||||
|
anon=true,
|
||||||
|
export=true,
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.DH_anon.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECDH_anon = {
|
||||||
|
anon=true,
|
||||||
|
type = "ecdh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECDH_anon_EXPORT = {
|
||||||
|
anon=true,
|
||||||
|
export=true,
|
||||||
|
type = "ecdh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.ECDH_anon.server_key_exchange
|
||||||
|
}
|
||||||
|
|
||||||
|
KEX_ALGORITHMS.RSA = {
|
||||||
|
pubkey="rsa",
|
||||||
|
}
|
||||||
|
-- http://www-archive.mozilla.org/projects/security/pki/nss/ssl/fips-ssl-ciphersuites.html
|
||||||
|
KEX_ALGORITHMS.RSA_FIPS = KEX_ALGORITHMS.RSA
|
||||||
|
KEX_ALGORITHMS.RSA_EXPORT = {
|
||||||
|
export=true,
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "rsa",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {rsa={}}
|
||||||
|
pos, ret.rsa.modulus, ret.rsa.exponent = bin.unpack(">PP", blob)
|
||||||
|
pos, ret.signed = unpack_signed(blob, pos)
|
||||||
|
ret.strength = #ret.rsa.modulus
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.RSA_EXPORT1024 = KEX_ALGORITHMS.RSA_EXPORT
|
||||||
|
KEX_ALGORITHMS.DHE_RSA={
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.dhparams, ret.strength = unpack_dhparams(blob)
|
||||||
|
pos, ret.signed = unpack_signed(blob, pos)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DHE_RSA_EXPORT={
|
||||||
|
export=true,
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DHE_DSS={
|
||||||
|
pubkey="dsa",
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DHE_DSS_EXPORT={
|
||||||
|
export=true,
|
||||||
|
pubkey="dsa",
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DHE_DSS_EXPORT1024 = KEX_ALGORITHMS.DHE_DSS_EXPORT1024
|
||||||
|
|
||||||
|
KEX_ALGORITHMS.DH_DSS={
|
||||||
|
pubkey="dh",
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DH_DSS_EXPORT={
|
||||||
|
export=true,
|
||||||
|
pubkey="dh",
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DH_RSA={
|
||||||
|
pubkey="dh",
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DH_RSA_EXPORT={
|
||||||
|
export=true,
|
||||||
|
pubkey="dh",
|
||||||
|
}
|
||||||
|
|
||||||
|
KEX_ALGORITHMS.ECDHE_RSA={
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "ecdh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob)
|
||||||
|
pos, ret.signed = unpack_signed(blob, pos)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECDHE_ECDSA={
|
||||||
|
pubkey="ec",
|
||||||
|
type = "ecdh",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.ECDHE_RSA.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECDH_ECDSA={
|
||||||
|
pubkey="ec",
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECDH_RSA={
|
||||||
|
pubkey="ec",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- draft-ietf-tls-ecc-00
|
||||||
|
KEX_ALGORITHMS.ECDH_ECNRA={
|
||||||
|
pubkey="ec",
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECMQV_ECDSA={
|
||||||
|
pubkey="ec",
|
||||||
|
type = "ecmqv",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.mqvparams = bin.unpack("p", blob)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.ECMQV_ECNRA={
|
||||||
|
pubkey="ec",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- rfc4279
|
||||||
|
KEX_ALGORITHMS.PSK = {
|
||||||
|
type = "psk",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos, hint = bin.unpack(">P", blob)
|
||||||
|
return {psk_identity_hint=hint}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.RSA_PSK = {
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "psk",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.PSK.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.DHE_PSK = {
|
||||||
|
type = "dh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.psk_identity_hint = bin.unpack(">P", blob)
|
||||||
|
pos, ret.dhparams, ret.strength = unpack_dhparams(blob)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
--nomenclature change
|
||||||
|
KEX_ALGORITHMS.PSK_DHE = KEX_ALGORITHMS.DHE_PSK
|
||||||
|
|
||||||
|
--rfc5489
|
||||||
|
KEX_ALGORITHMS.ECDHE_PSK={
|
||||||
|
type = "ecdh",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {}
|
||||||
|
pos, ret.psk_identity_hint = bin.unpack(">P", blob)
|
||||||
|
pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob)
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- RFC 5054
|
||||||
|
KEX_ALGORITHMS.SRP_SHA = {
|
||||||
|
type = "srp",
|
||||||
|
server_key_exchange = function (blob, protocol)
|
||||||
|
local pos
|
||||||
|
local ret = {srp={}}
|
||||||
|
pos, ret.srp.N, ret.srp.g, ret.srp.s, ret.srp.B = bin.unpack(">PPpP", blob)
|
||||||
|
pos, ret.signed = unpack_signed(blob, pos)
|
||||||
|
ret.strength = #ret.srp.N
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.SRP_SHA_DSS = {
|
||||||
|
pubkey="dsa",
|
||||||
|
type = "srp",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.SRP_SHA.server_key_exchange
|
||||||
|
}
|
||||||
|
KEX_ALGORITHMS.SRP_SHA_RSA = {
|
||||||
|
pubkey="rsa",
|
||||||
|
type = "srp",
|
||||||
|
server_key_exchange = KEX_ALGORITHMS.SRP_SHA.server_key_exchange
|
||||||
|
}
|
||||||
|
|
||||||
|
-- RFC 6101
|
||||||
|
KEX_ALGORITHMS.FORTEZZA_KEA={}
|
||||||
|
|
||||||
|
-- RFC 4491
|
||||||
|
KEX_ALGORITHMS.GOSTR341001={}
|
||||||
|
KEX_ALGORITHMS.GOSTR341094={}
|
||||||
|
|
||||||
|
-- RFC 2712
|
||||||
|
KEX_ALGORITHMS.KRB5={}
|
||||||
|
KEX_ALGORITHMS.KRB5_EXPORT={
|
||||||
|
export=true,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
--- Get info about a cipher suite
|
--- Get info about a cipher suite
|
||||||
--
|
--
|
||||||
-- Returned table has "kex", "server_auth", "cipher", "mode", "size", and
|
-- Returned table has "kex", "cipher", "mode", "size", and
|
||||||
-- "hash" keys, as well as boolean flags "dh", "ec", and "draft". The "draft"
|
-- "hash" keys, as well as boolean flag "draft". The "draft"
|
||||||
-- flag is only supported for some suites that have different enumeration
|
-- flag is only supported for some suites that have different enumeration
|
||||||
-- values in draft versus final RFC. The "export" key may be present with
|
-- values in draft versus final RFC.
|
||||||
-- value either "EXPORT" or "EXPORT1024".
|
|
||||||
-- @param c The cipher suite name, e.g. TLS_RSA_WITH_AES_128_GCM_SHA256
|
-- @param c The cipher suite name, e.g. TLS_RSA_WITH_AES_128_GCM_SHA256
|
||||||
-- @return A table of info as described above.
|
-- @return A table of info as described above.
|
||||||
function cipher_info (c)
|
function cipher_info (c)
|
||||||
@@ -656,50 +978,12 @@ function cipher_info (c)
|
|||||||
stdnse.debug2("cipher_info: Not a TLS ciphersuite: %s", c)
|
stdnse.debug2("cipher_info: Not a TLS ciphersuite: %s", c)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
-- kex, server_auth, cipher, size, mode, hash
|
-- kex, cipher, size, mode, hash
|
||||||
-- flags: dh, ec, export
|
i = i + 1
|
||||||
while tokens[i] and tokens[i] ~= "WITH" do
|
while tokens[i] and tokens[i] ~= "WITH" do
|
||||||
i = i + 1
|
i = i + 1
|
||||||
local t = tokens[i]
|
|
||||||
if t == "RSA" or t == "DSS" then
|
|
||||||
info.server_auth = t
|
|
||||||
info.kex = info.kex or t
|
|
||||||
elseif t:sub(1,2) == "EC" then
|
|
||||||
info.ec = true
|
|
||||||
if t == "ECDH" or t == "ECDHE" then
|
|
||||||
info.dh = true
|
|
||||||
info.kex = t
|
|
||||||
elseif t == "ECDSA" or t == "ECNRA" then
|
|
||||||
info.server_auth = t
|
|
||||||
elseif t == "ECMQV" then
|
|
||||||
info.kex = t
|
|
||||||
end
|
|
||||||
elseif t == "DH" or t == "DHE" then
|
|
||||||
info.dh = true
|
|
||||||
info.kex = t
|
|
||||||
elseif t == "PSK" then
|
|
||||||
info.kex = (info.kex and info.kex .. "_" .. t) or t
|
|
||||||
info.server_auth = info.server_auth or t
|
|
||||||
elseif t == "EXPORT" or t == "EXPORT1024" then
|
|
||||||
info.export = t
|
|
||||||
elseif t == "SRP" then
|
|
||||||
info.kex = "SRP_SHA"
|
|
||||||
info.server_auth = "SRP_SHA"
|
|
||||||
i = i + 1 -- consume _SHA
|
|
||||||
elseif t == "FORTEZZA" then
|
|
||||||
info.kex = "FORTEZZA_KEA"
|
|
||||||
info.server_auth = "FORTEZZA"
|
|
||||||
i = i + 1 -- consume _KEA
|
|
||||||
elseif t == "NULL" then
|
|
||||||
info.kex = t
|
|
||||||
info.server_auth = "anon"
|
|
||||||
elseif t == "anon" then
|
|
||||||
info.server_auth = t
|
|
||||||
else
|
|
||||||
info.kex = info.kex or t
|
|
||||||
info.server_auth = info.server_auth or t
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
info.kex = table.concat(tokens, "_", 2, i-1)
|
||||||
|
|
||||||
if tokens[i] and tokens[i] ~= "WITH" then
|
if tokens[i] and tokens[i] ~= "WITH" then
|
||||||
stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c)
|
stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c)
|
||||||
@@ -777,18 +1061,6 @@ SCSVS = {
|
|||||||
["TLS_FALLBACK_SCSV"] = 0x5600, -- draft-ietf-tls-downgrade-scsv-00
|
["TLS_FALLBACK_SCSV"] = 0x5600, -- draft-ietf-tls-downgrade-scsv-00
|
||||||
}
|
}
|
||||||
|
|
||||||
local function find_key(t, value)
|
|
||||||
local k, v
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
if v == value then
|
|
||||||
return k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Helper function to unpack a 3-byte integer value
|
-- Helper function to unpack a 3-byte integer value
|
||||||
local function unpack_3byte (buffer, pos)
|
local function unpack_3byte (buffer, pos)
|
||||||
local low, high
|
local low, high
|
||||||
@@ -991,7 +1263,7 @@ function client_hello(t)
|
|||||||
if type(cipher) == "string" then
|
if type(cipher) == "string" then
|
||||||
cipher = CIPHERS[cipher] or SCSVS[cipher]
|
cipher = CIPHERS[cipher] or SCSVS[cipher]
|
||||||
end
|
end
|
||||||
if type(cipher) == "number" and cipher > 0 and cipher <= 0xffff then
|
if type(cipher) == "number" and cipher >= 0 and cipher <= 0xffff then
|
||||||
table.insert(ciphers, bin.pack(">S", cipher))
|
table.insert(ciphers, bin.pack(">S", cipher))
|
||||||
else
|
else
|
||||||
stdnse.debug1("Unknown cipher in client_hello: %s", cipher)
|
stdnse.debug1("Unknown cipher in client_hello: %s", cipher)
|
||||||
|
|||||||
@@ -12,17 +12,13 @@ local tls = require "tls"
|
|||||||
description = [[
|
description = [[
|
||||||
This script repeatedly initiates SSLv3/TLS connections, each time trying a new
|
This script repeatedly initiates SSLv3/TLS connections, each time trying a new
|
||||||
cipher or compressor while recording whether a host accepts or rejects it. The
|
cipher or compressor while recording whether a host accepts or rejects it. The
|
||||||
end result is a list of all the ciphers and compressors that a server accepts.
|
end result is a list of all the ciphersuites and compressors that a server accepts.
|
||||||
|
|
||||||
Each cipher is shown with a strength rating: one of <code>strong</code>,
|
Each ciphersuite is shown with a letter grade (A through F) indicating the
|
||||||
<code>weak</code>, or <code>unknown strength</code>. The output line
|
strength of the connection. The grade is based on the cryptographic strength of
|
||||||
beginning with <code>Least strength</code> shows the strength of the
|
the key exchange and of the stream cipher. The message integrity (hash)
|
||||||
weakest cipher offered. If you are auditing for weak ciphers, you would
|
algorithm choice is not a factor. The output line beginning with
|
||||||
want to look more closely at any port where <code>Least strength</code>
|
<code>Least strength</code> shows the strength of the weakest cipher offered.
|
||||||
is not <code>strong</code>. The cipher strength database is in the file
|
|
||||||
<code>nselib/data/ssl-ciphers</code>, or you can use a different file
|
|
||||||
through the script argument
|
|
||||||
<code>ssl-enum-ciphers.rankedcipherlist</code>.
|
|
||||||
|
|
||||||
SSLv3/TLSv1 requires more effort to determine which ciphers and compression
|
SSLv3/TLSv1 requires more effort to determine which ciphers and compression
|
||||||
methods a server supports than SSLv2. A client lists the ciphers and compressors
|
methods a server supports than SSLv2. A client lists the ciphers and compressors
|
||||||
@@ -44,46 +40,44 @@ and therefore is quite noisy.
|
|||||||
-- @usage
|
-- @usage
|
||||||
-- nmap --script ssl-enum-ciphers -p 443 <host>
|
-- nmap --script ssl-enum-ciphers -p 443 <host>
|
||||||
--
|
--
|
||||||
-- @args ssl-enum-ciphers.rankedcipherlist A path to a file of cipher names and strength ratings
|
|
||||||
--
|
|
||||||
-- @output
|
-- @output
|
||||||
-- PORT STATE SERVICE REASON
|
-- PORT STATE SERVICE REASON
|
||||||
-- 443/tcp open https syn-ack
|
-- 443/tcp open https syn-ack
|
||||||
-- | ssl-enum-ciphers:
|
-- | ssl-enum-ciphers:
|
||||||
-- | SSLv3:
|
-- | SSLv3:
|
||||||
-- | ciphers:
|
-- | ciphers:
|
||||||
-- | TLS_RSA_WITH_RC4_128_MD5 - strong
|
-- | TLS_RSA_WITH_RC4_128_MD5 - A
|
||||||
-- | TLS_RSA_WITH_RC4_128_SHA - strong
|
-- | TLS_RSA_WITH_RC4_128_SHA - A
|
||||||
-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong
|
-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - E
|
||||||
-- | compressors:
|
-- | compressors:
|
||||||
-- | NULL
|
-- | NULL
|
||||||
-- | cipher preference: server
|
-- | cipher preference: server
|
||||||
-- | TLSv1.0:
|
-- | TLSv1.0:
|
||||||
-- | ciphers:
|
-- | ciphers:
|
||||||
-- | TLS_RSA_WITH_RC4_128_MD5 - strong
|
-- | TLS_RSA_WITH_RC4_128_MD5 - A
|
||||||
-- | TLS_RSA_WITH_RC4_128_SHA - strong
|
-- | TLS_RSA_WITH_RC4_128_SHA - A
|
||||||
-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong
|
-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - E
|
||||||
-- | TLS_RSA_WITH_AES_256_CBC_SHA - strong
|
-- | TLS_RSA_WITH_AES_256_CBC_SHA - A
|
||||||
-- | TLS_RSA_WITH_AES_128_CBC_SHA - strong
|
-- | TLS_RSA_WITH_AES_128_CBC_SHA - A
|
||||||
-- | compressors:
|
-- | compressors:
|
||||||
-- | NULL
|
-- | NULL
|
||||||
-- | cipher preference: server
|
-- | cipher preference: server
|
||||||
-- |_ least strength: strong
|
-- |_ least strength: E
|
||||||
--
|
--
|
||||||
-- @xmloutput
|
-- @xmloutput
|
||||||
-- <table key="SSLv3">
|
-- <table key="SSLv3">
|
||||||
-- <table key="ciphers">
|
-- <table key="ciphers">
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_RC4_128_MD5</elem>
|
-- <elem key="name">TLS_RSA_WITH_RC4_128_MD5</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_RC4_128_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_RC4_128_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_3DES_EDE_CBC_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_3DES_EDE_CBC_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">E</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table key="compressors">
|
-- <table key="compressors">
|
||||||
@@ -95,23 +89,23 @@ and therefore is quite noisy.
|
|||||||
-- <table key="ciphers">
|
-- <table key="ciphers">
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_RC4_128_MD5</elem>
|
-- <elem key="name">TLS_RSA_WITH_RC4_128_MD5</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_RC4_128_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_RC4_128_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_3DES_EDE_CBC_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_3DES_EDE_CBC_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">E</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_AES_256_CBC_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_AES_256_CBC_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table>
|
-- <table>
|
||||||
-- <elem key="name">TLS_RSA_WITH_AES_128_CBC_SHA</elem>
|
-- <elem key="name">TLS_RSA_WITH_AES_128_CBC_SHA</elem>
|
||||||
-- <elem key="strength">strong</elem>
|
-- <elem key="strength">A</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <table key="compressors">
|
-- <table key="compressors">
|
||||||
@@ -119,7 +113,7 @@ and therefore is quite noisy.
|
|||||||
-- </table>
|
-- </table>
|
||||||
-- <elem key="cipher preference">server</elem>
|
-- <elem key="cipher preference">server</elem>
|
||||||
-- </table>
|
-- </table>
|
||||||
-- <elem key="least strength">strong</elem>
|
-- <elem key="least strength">E</elem>
|
||||||
|
|
||||||
author = "Mak Kolybabi <mak@kolybabi.com>, Gabriel Lawrence"
|
author = "Mak Kolybabi <mak@kolybabi.com>, Gabriel Lawrence"
|
||||||
|
|
||||||
@@ -133,18 +127,6 @@ categories = {"discovery", "intrusive"}
|
|||||||
-- http://seclists.org/nmap-dev/2010/q1/859
|
-- http://seclists.org/nmap-dev/2010/q1/859
|
||||||
local CHUNK_SIZE = 64
|
local CHUNK_SIZE = 64
|
||||||
|
|
||||||
|
|
||||||
cipherstrength = {
|
|
||||||
["broken"] = 0,
|
|
||||||
["weak"] = 1,
|
|
||||||
["unknown strength"] = 2,
|
|
||||||
["strong"] = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
local rankedciphers={}
|
|
||||||
local mincipherstrength=9999 --artificial "highest value"
|
|
||||||
local rankedciphersfilename=false
|
|
||||||
|
|
||||||
-- Add additional context (protocol) to debug output
|
-- Add additional context (protocol) to debug output
|
||||||
local function ctx_log(level, protocol, fmt, ...)
|
local function ctx_log(level, protocol, fmt, ...)
|
||||||
return stdnse.debug(level, "(%s) " .. fmt, protocol, ...)
|
return stdnse.debug(level, "(%s) " .. fmt, protocol, ...)
|
||||||
@@ -367,8 +349,59 @@ local function get_body(record, property, value)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Score a ciphersuite implementation (including key exchange info)
|
||||||
|
local function score_cipher (kex_strength, cipher_info)
|
||||||
|
local kex_score, cipher_score
|
||||||
|
if not kex_strength or not cipher_info.size then
|
||||||
|
return "unknown"
|
||||||
|
end
|
||||||
|
if kex_strength == 0 then
|
||||||
|
return 0
|
||||||
|
elseif kex_strength < 512 then
|
||||||
|
kex_score = 0.2
|
||||||
|
elseif kex_strength < 1024 then
|
||||||
|
kex_score = 0.4
|
||||||
|
elseif kex_strength < 2048 then
|
||||||
|
kex_score = 0.8
|
||||||
|
elseif kex_strength < 4096 then
|
||||||
|
kex_score = 0.9
|
||||||
|
else
|
||||||
|
kex_score = 1.0
|
||||||
|
end
|
||||||
|
|
||||||
|
if cipher_info.size == 0 then
|
||||||
|
return 0
|
||||||
|
elseif cipher_info.size < 128 then
|
||||||
|
cipher_score = 0.2
|
||||||
|
elseif cipher_info.size < 256 then
|
||||||
|
cipher_score = 0.8
|
||||||
|
else
|
||||||
|
cipher_score = 1.0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Based on SSL Labs' 30-30-40 rating without the first 30% (protocol support)
|
||||||
|
return 0.43 * kex_score + 0.57 * cipher_score
|
||||||
|
end
|
||||||
|
|
||||||
|
local function letter_grade (score)
|
||||||
|
if not tonumber(score) then return "unknown" end
|
||||||
|
if score >= 0.80 then
|
||||||
|
return "A"
|
||||||
|
elseif score >= 0.65 then
|
||||||
|
return "B"
|
||||||
|
elseif score >= 0.50 then
|
||||||
|
return "C"
|
||||||
|
elseif score >= 0.35 then
|
||||||
|
return "D"
|
||||||
|
elseif score >= 0.20 then
|
||||||
|
return "E"
|
||||||
|
else
|
||||||
|
return "F"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Find which ciphers out of group are supported by the server.
|
-- Find which ciphers out of group are supported by the server.
|
||||||
local function find_ciphers_group(host, port, protocol, group)
|
local function find_ciphers_group(host, port, protocol, group, scores)
|
||||||
local results = {}
|
local results = {}
|
||||||
local t = {
|
local t = {
|
||||||
["protocol"] = protocol,
|
["protocol"] = protocol,
|
||||||
@@ -435,6 +468,72 @@ local function find_ciphers_group(host, port, protocol, group)
|
|||||||
else
|
else
|
||||||
-- Add cipher to the list of accepted ciphers.
|
-- Add cipher to the list of accepted ciphers.
|
||||||
table.insert(results, name)
|
table.insert(results, name)
|
||||||
|
if scores then
|
||||||
|
local info = tls.cipher_info(name)
|
||||||
|
-- Some warnings:
|
||||||
|
if info.hash and info.hash == "MD5" then
|
||||||
|
scores.warnings["Ciphersuite uses MD5 for message integrity"] = true
|
||||||
|
end
|
||||||
|
if protocol == "SSLv3" and info.mode and info.mode == "CBC" then
|
||||||
|
scores.warnings["CBC-mode cipher in SSLv3 (CVE-2014-3566)"] = true
|
||||||
|
elseif info.cipher == "RC4" and tls.PROTOCOLS[protocol] >= 0x0302 then
|
||||||
|
scores.warnings["Weak cipher RC4 in TLSv1.1 or newer not needed for BEAST mitigation"] = true
|
||||||
|
end
|
||||||
|
local kex = tls.KEX_ALGORITHMS[info.kex]
|
||||||
|
local extra, kex_strength
|
||||||
|
if kex.anon then
|
||||||
|
kex_strength = 0
|
||||||
|
elseif kex.export then
|
||||||
|
if info.kex:find("1024$") then
|
||||||
|
kex_strength = 1024
|
||||||
|
else
|
||||||
|
kex_strength = 512
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if kex.pubkey then
|
||||||
|
local certs = get_body(handshake, "type", "certificate")
|
||||||
|
-- Assume RFC compliance:
|
||||||
|
-- "The sender's certificate MUST come first in the list."
|
||||||
|
-- This may not always be the case, so
|
||||||
|
-- TODO: reorder certificates and validate entire chain
|
||||||
|
-- TODO: certificate validation (date, self-signed, etc)
|
||||||
|
local c = sslcert.parse_ssl_certificate(certs.certificates[1])
|
||||||
|
if c.pubkey.type == kex.pubkey then
|
||||||
|
local sigalg = c.sig_algorithm:match("([mM][dD][245])")
|
||||||
|
if sigalg then
|
||||||
|
-- MD2 and MD5 are broken
|
||||||
|
kex_strength = 0
|
||||||
|
scores.warnings["Insecure certificate signature: " .. string.upper(sigalg)] = true
|
||||||
|
else
|
||||||
|
sigalg = c.sig_algorithm:match("([sS][hH][aA]1)")
|
||||||
|
-- TODO: Update this when SHA-1 is deprecated in 2016
|
||||||
|
-- kex_strength = 0
|
||||||
|
scores.warnings["Weak certificate signature: SHA1"] = true
|
||||||
|
kex_strength = tls.rsa_equiv(kex.pubkey, c.pubkey.bits)
|
||||||
|
extra = string.format("%s %d", kex.pubkey, c.pubkey.bits)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local ske = get_body(handshake, "type", "server_key_exchange")
|
||||||
|
if kex.server_key_exchange and ske then
|
||||||
|
local kex_info = kex.server_key_exchange(ske.data)
|
||||||
|
if kex_info.strength then
|
||||||
|
if kex_strength and kex_strength > kex_info.strength then
|
||||||
|
kex_strength = kex_info.strength
|
||||||
|
scores.warnings["Key exchange parameters of lower strength than certificate key"] = true
|
||||||
|
end
|
||||||
|
kex_strength = kex_strength or kex_info.strength
|
||||||
|
extra = string.format("%s %d", kex.type, kex_info.strength)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
scores[name] = {
|
||||||
|
cipher_strength=info.size,
|
||||||
|
kex_strength = kex_strength,
|
||||||
|
extra = extra,
|
||||||
|
letter_grade = letter_grade(score_cipher(kex_strength, info))
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -449,10 +548,11 @@ local function find_ciphers(host, port, protocol)
|
|||||||
local ciphers = in_chunks(sorted_keys(tls.CIPHERS), CHUNK_SIZE)
|
local ciphers = in_chunks(sorted_keys(tls.CIPHERS), CHUNK_SIZE)
|
||||||
|
|
||||||
local results = {}
|
local results = {}
|
||||||
|
local scores = {warnings={}}
|
||||||
|
|
||||||
-- Try every cipher.
|
-- Try every cipher.
|
||||||
for _, group in ipairs(ciphers) do
|
for _, group in ipairs(ciphers) do
|
||||||
local chunk, protocol_worked = find_ciphers_group(host, port, protocol, group)
|
local chunk, protocol_worked = find_ciphers_group(host, port, protocol, group, scores)
|
||||||
if protocol_worked == nil then return nil end
|
if protocol_worked == nil then return nil end
|
||||||
for _, name in ipairs(chunk) do
|
for _, name in ipairs(chunk) do
|
||||||
table.insert(results, name)
|
table.insert(results, name)
|
||||||
@@ -460,7 +560,7 @@ local function find_ciphers(host, port, protocol)
|
|||||||
end
|
end
|
||||||
if not next(results) then return nil end
|
if not next(results) then return nil end
|
||||||
|
|
||||||
return results
|
return results, scores
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_compressors(host, port, protocol, good_ciphers)
|
local function find_compressors(host, port, protocol, good_ciphers)
|
||||||
@@ -634,7 +734,7 @@ local function try_protocol(host, port, protocol, upresults)
|
|||||||
local results = stdnse.output_table()
|
local results = stdnse.output_table()
|
||||||
|
|
||||||
-- Find all valid ciphers.
|
-- Find all valid ciphers.
|
||||||
local ciphers = find_ciphers(host, port, protocol)
|
local ciphers, scores = find_ciphers(host, port, protocol)
|
||||||
if ciphers == nil then
|
if ciphers == nil then
|
||||||
condvar "signal"
|
condvar "signal"
|
||||||
return nil
|
return nil
|
||||||
@@ -682,19 +782,15 @@ local function try_protocol(host, port, protocol, upresults)
|
|||||||
-- Add rankings to ciphers
|
-- Add rankings to ciphers
|
||||||
local cipherstr
|
local cipherstr
|
||||||
for i, name in ipairs(ciphers) do
|
for i, name in ipairs(ciphers) do
|
||||||
if rankedciphersfilename and rankedciphers[name] then
|
local outcipher = {name=name, kex_info=scores[name].extra, strength=scores[name].letter_grade}
|
||||||
cipherstr=rankedciphers[name]
|
|
||||||
else
|
|
||||||
cipherstr="unknown strength"
|
|
||||||
end
|
|
||||||
stdnse.debug2("Strength of %s rated %d.",cipherstr,cipherstrength[cipherstr])
|
|
||||||
if mincipherstrength>cipherstrength[cipherstr] then
|
|
||||||
stdnse.debug2("Downgrading min cipher strength to %d.",cipherstrength[cipherstr])
|
|
||||||
mincipherstrength=cipherstrength[cipherstr]
|
|
||||||
end
|
|
||||||
local outcipher = {name=name, strength=cipherstr}
|
|
||||||
setmetatable(outcipher,{
|
setmetatable(outcipher,{
|
||||||
__tostring=function(t) return string.format("%s - %s", t.name, t.strength) end
|
__tostring=function(t)
|
||||||
|
if t.kex_info then
|
||||||
|
return string.format("%s (%s) - %s", t.name, t.kex_info, t.strength)
|
||||||
|
else
|
||||||
|
return string.format("%s - %s", t.name, t.strength)
|
||||||
|
end
|
||||||
|
end
|
||||||
})
|
})
|
||||||
ciphers[i]=outcipher
|
ciphers[i]=outcipher
|
||||||
end
|
end
|
||||||
@@ -707,47 +803,15 @@ local function try_protocol(host, port, protocol, upresults)
|
|||||||
|
|
||||||
results["cipher preference"] = cipher_pref
|
results["cipher preference"] = cipher_pref
|
||||||
results["cipher preference error"] = cipher_pref_err
|
results["cipher preference error"] = cipher_pref_err
|
||||||
|
if next(scores.warnings) then
|
||||||
|
results["warnings"] = sorted_keys(scores.warnings)
|
||||||
|
end
|
||||||
|
|
||||||
upresults[protocol] = results
|
upresults[protocol] = results
|
||||||
condvar "signal"
|
condvar "signal"
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Shamelessly stolen from nselib/unpwdb.lua and changed a bit. (Gabriel Lawrence)
|
|
||||||
local filltable = function(filename,table)
|
|
||||||
if #table ~= 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local file = io.open(filename, "r")
|
|
||||||
|
|
||||||
if not file then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local l = file:read()
|
|
||||||
|
|
||||||
if not l then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Comments takes up a whole line
|
|
||||||
if not l:match("#!comment:") then
|
|
||||||
local lsplit=stdnse.strsplit("%s+", l)
|
|
||||||
if cipherstrength[lsplit[2]] then
|
|
||||||
table[lsplit[1]] = lsplit[2]
|
|
||||||
else
|
|
||||||
stdnse.debug1("Strength not defined, ignoring: %s:%s",lsplit[1],lsplit[2])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
file:close()
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
portrule = function (host, port)
|
portrule = function (host, port)
|
||||||
return shortport.ssl(host, port) or sslcert.getPrepareTLSWithoutReconnect(port)
|
return shortport.ssl(host, port) or sslcert.getPrepareTLSWithoutReconnect(port)
|
||||||
end
|
end
|
||||||
@@ -774,15 +838,6 @@ end
|
|||||||
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
|
|
||||||
rankedciphersfilename=stdnse.get_script_args("ssl-enum-ciphers.rankedcipherlist")
|
|
||||||
if rankedciphersfilename then
|
|
||||||
filltable(rankedciphersfilename,rankedciphers)
|
|
||||||
else
|
|
||||||
rankedciphersfilename = nmap.fetchfile( "nselib/data/ssl-ciphers" )
|
|
||||||
stdnse.debug1("Ranked ciphers filename: %s", rankedciphersfilename)
|
|
||||||
filltable(rankedciphersfilename,rankedciphers)
|
|
||||||
end
|
|
||||||
|
|
||||||
local results = {}
|
local results = {}
|
||||||
|
|
||||||
local condvar = nmap.condvar(results)
|
local condvar = nmap.condvar(results)
|
||||||
@@ -807,14 +862,14 @@ action = function(host, port)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if rankedciphersfilename then
|
local least = "A"
|
||||||
for k, v in pairs(cipherstrength) do
|
for p, r in pairs(results) do
|
||||||
if v == mincipherstrength then
|
for i, c in ipairs(r.ciphers) do
|
||||||
-- Should sort before or after SSLv3, TLSv*
|
-- counter-intuitive: "A" < "B", so really looking for max
|
||||||
results["least strength"] = k
|
least = least < c.strength and c.strength or least
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
results["least strength"] = least
|
||||||
|
|
||||||
return sorted_by_key(results)
|
return sorted_by_key(results)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user