diff --git a/scripts/ssl-enum-ciphers.nse b/scripts/ssl-enum-ciphers.nse index bbdaef7f7..b72ecb49e 100644 --- a/scripts/ssl-enum-ciphers.nse +++ b/scripts/ssl-enum-ciphers.nse @@ -1,31 +1,20 @@ description = [[ -This script repeatedly initiates SSL/TLS connections, each time removing -whichever cipher or compressor was chosen by the server when making the previous -connection. The end result is a list of all the ciphers and compressors that a -server accepts. +This script repeatedly initiates SSL/TLS connections, each time trying a new +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. SSLv3/TLSv1 requires more effort to determine which ciphers and compression methods a server supports than SSLv2. A client lists the ciphers and compressors that it is capable of supporting, and the server will respond with a single cipher and compressor chosen, or a rejection notice. -By default, the ciphers and compressors are presented in the order the server -prefers them. Passing ssl-enum-ciphers.sort=name as an argument to -this script will instead order them alphabetically, which is more useful for -comparing cipher lists between servers. - -This script is intrusive since it must initiate many connections so a server, -and therefore is quite noisy. For a server that supports 10 ciphers and 2 -compressors, this script will need to initiate 14 connections (10 successful -ciphers, 1 failed cipher, 2 accepted compressors, and 1 failed compressor). +This script is intrusive since it must initiate many connections to a server, +and therefore is quite noisy. ]] --- -- @usage --- nmap --script ssl-enum-algorithms -p 443 --- --- @args ssl-enum-ciphers.sort If set to name, sort results --- alphabetically instead of in the order in which they were accepted. +-- nmap --script ssl-enum-ciphers -p 443 -- -- @output -- PORT STATE SERVICE REASON @@ -33,46 +22,46 @@ ciphers, 1 failed cipher, 2 accepted compressors, and 1 failed compressor). -- | sslv3-enum: -- | SSLv3 -- | Ciphers (18) +-- | TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA +-- | TLS_DHE_RSA_WITH_AES_128_CBC_SHA +-- | TLS_DHE_RSA_WITH_AES_256_CBC_SHA +-- | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA +-- | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA +-- | TLS_DHE_RSA_WITH_DES_CBC_SHA +-- | TLS_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 +-- | TLS_RSA_EXPORT_WITH_RC4_40_MD5 +-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA -- | TLS_RSA_WITH_AES_128_CBC_SHA -- | TLS_RSA_WITH_AES_256_CBC_SHA --- | TLS_RSA_WITH_3DES_EDE_CBC_SHA --- | TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA --- | TLS_RSA_WITH_DES_CBC_SHA --- | TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 --- | TLS_RSA_WITH_RC4_128_MD5 --- | TLS_RSA_EXPORT_WITH_RC4_40_MD5 --- | TLS_DHE_RSA_WITH_DES_CBC_SHA --- | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA --- | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA --- | TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA --- | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA --- | TLS_DHE_RSA_WITH_AES_128_CBC_SHA --- | TLS_RSA_WITH_RC4_128_SHA --- | TLS_DHE_RSA_WITH_AES_256_CBC_SHA -- | TLS_RSA_WITH_CAMELLIA_128_CBC_SHA --- | TLS_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA +-- | TLS_RSA_WITH_DES_CBC_SHA +-- | TLS_RSA_WITH_RC4_128_MD5 +-- | TLS_RSA_WITH_RC4_128_SHA -- | Compressors (1) -- | uncompressed -- | TLSv1.0 -- | Ciphers (18) +-- | TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA +-- | TLS_DHE_RSA_WITH_AES_128_CBC_SHA +-- | TLS_DHE_RSA_WITH_AES_256_CBC_SHA +-- | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA +-- | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA +-- | TLS_DHE_RSA_WITH_DES_CBC_SHA +-- | TLS_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 +-- | TLS_RSA_EXPORT_WITH_RC4_40_MD5 +-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA -- | TLS_RSA_WITH_AES_128_CBC_SHA -- | TLS_RSA_WITH_AES_256_CBC_SHA --- | TLS_RSA_WITH_3DES_EDE_CBC_SHA --- | TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA --- | TLS_RSA_WITH_DES_CBC_SHA --- | TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 --- | TLS_RSA_WITH_RC4_128_MD5 --- | TLS_RSA_EXPORT_WITH_RC4_40_MD5 --- | TLS_DHE_RSA_WITH_DES_CBC_SHA --- | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA --- | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA --- | TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA --- | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA --- | TLS_DHE_RSA_WITH_AES_128_CBC_SHA --- | TLS_RSA_WITH_RC4_128_SHA --- | TLS_DHE_RSA_WITH_AES_256_CBC_SHA -- | TLS_RSA_WITH_CAMELLIA_128_CBC_SHA --- | TLS_RSA_EXPORT_WITH_DES40_CBC_SHA +-- | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA +-- | TLS_RSA_WITH_DES_CBC_SHA +-- | TLS_RSA_WITH_RC4_128_MD5 +-- | TLS_RSA_WITH_RC4_128_SHA -- | Compressors (1) -- |_ uncompressed @@ -118,7 +107,7 @@ local SSL_SERVICES = { "telnets" } --- All of the values in the tables below are from: +-- Most of the values in the tables below are from: -- http://www.iana.org/assignments/tls-parameters/ PROTOCOLS = { ["SSLv3"] = 0x0300, @@ -286,6 +275,13 @@ CIPHERS = { ["TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA"] = 0x0044, ["TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA"] = 0x0045, ["TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA"] = 0x0046, + ["TLS_RSA_EXPORT1024_WITH_RC4_56_MD5"] = 0x0060, + ["TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5"] = 0x0061, + ["TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA"] = 0x0062, + ["TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA"] = 0x0063, + ["TLS_RSA_EXPORT1024_WITH_RC4_56_SHA"] = 0x0064, + ["TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA"] = 0x0065, + ["TLS_DHE_DSS_WITH_RC4_128_SHA"] = 0x0066, ["TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"] = 0x0067, ["TLS_DH_DSS_WITH_AES_256_CBC_SHA256"] = 0x0068, ["TLS_DH_RSA_WITH_AES_256_CBC_SHA256"] = 0x0069, @@ -406,11 +402,13 @@ CIPHERS = { ["TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384"] = 0xC038, ["TLS_ECDHE_PSK_WITH_NULL_SHA"] = 0xC039, ["TLS_ECDHE_PSK_WITH_NULL_SHA256"] = 0xC03A, - ["TLS_ECDHE_PSK_WITH_NULL_SHA384"] = 0xC03B + ["TLS_ECDHE_PSK_WITH_NULL_SHA384"] = 0xC03B, + ["SSL_RSA_FIPS_WITH_DES_CBC_SHA"] = 0xFEFE, + ["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF } local function record_read(buffer, i) - local _, b, h, j, len + local b, h, j, len local function find_key(t, value) local k, v @@ -440,7 +438,7 @@ local function record_read(buffer, i) j, h["length"] = bin.unpack(">S", buffer, j) -- Ensure we have enough data for the body. - len = j + h["length"] + len = j + h["length"] - 1 if #buffer < len then return i, nil end @@ -488,17 +486,8 @@ local function record_read(buffer, i) end end - -- Check for overflow beyond length. - if j > len then - stdnse.print_debug(2, "SSL: Parsing error, body %d bytes larger than header declares.", j - len) - j = len - end - - -- Consume any unparsed bytes in record body. - if j < len then - stdnse.print_debug(2, "SSL: Parsing error, ignoring %d unused bytes in body.", len - j) - j = len - end + -- Ignore unparsed bytes. + j = len return j, h end @@ -529,6 +518,9 @@ local function client_hello(t) b = "" + -- Set the protocol. + b = b .. bin.pack(">S", PROTOCOLS[t["protocol"]]) + -- Set the random data. b = b .. bin.pack(">I", os.time()) @@ -541,10 +533,12 @@ local function client_hello(t) -- Cipher suites. ciphers = "" if t["ciphers"] ~= nil then + -- Add specified ciphers. for _, cipher in pairs(t["ciphers"]) do ciphers = ciphers .. bin.pack(">S", CIPHERS[cipher]) end else + -- Add all known ciphers. for _, cipher in pairs(CIPHERS) do ciphers = ciphers .. bin.pack(">S", cipher) end @@ -555,11 +549,12 @@ local function client_hello(t) -- Compression methods. compressors = "" if t["compressors"] ~= nil then + -- Add specified compressors. for _, compressor in pairs(t["compressors"]) do compressors = compressors .. bin.pack("C", COMPRESSORS[compressor]) end else - ciphers = b .. bin.pack("C", #COMPRESSORS) + -- Add all known compressors. for _, compressor in pairs(COMPRESSORS) do compressors = compressors .. bin.pack("C", compressor) end @@ -567,9 +562,6 @@ local function client_hello(t) b = b .. bin.pack("C", #compressors) b = b .. compressors - -- Extensions. - b = b .. bin.pack(">S", 0) - ------------ -- Header -- ------------ @@ -583,14 +575,11 @@ local function client_hello(t) len = bin.pack(">I", #b) h = h .. bin.pack("CCC", len:byte(2), len:byte(3), len:byte(4)) - -- Set the protocol. - h = h .. bin.pack(">S", PROTOCOLS[t["protocol"]]) - return record_write("handshake", t["protocol"], h .. b) end local function try_params(host, port, t) - local buffer, err, i, kind, length, record, req, resp, sock, status + local buffer, err, i, record, req, resp, sock, status -- Create socket. sock = nmap.new_socket() @@ -614,14 +603,15 @@ local function try_params(host, port, t) -- Read response. i = 0 buffer = "" + record = nil while true do - status, chunk = sock:receive() + status, resp = sock:receive() if not status then sock:close() - return nil + return record end - buffer = buffer .. chunk + buffer = buffer .. resp -- Parse response. i, record = record_read(buffer, i) @@ -633,56 +623,45 @@ local function try_params(host, port, t) end local function try_protocol(host, port, protocol) - local ciphers, compressors, result + local ciphers, compressors, results local function find_ciphers() - local k, name, record, results, t, v + local name, protocol_worked, record, results, t results = {} - -- Create list of ciphers. - t = { - ["ciphers"] = {}, - ["protocol"] = protocol - } - for name, id in pairs(CIPHERS) do - table.insert(t["ciphers"], name) - end + -- Try every cipher. + protocol_worked = false + for name, _ in pairs(CIPHERS) do + -- Create structure. + t = { + ["ciphers"] = {name}, + ["protocol"] = protocol + } - -- Create connections while removing previously accepted ciphers. - while true do - -- Try connecting with remaining ciphers. + -- Try connecting with cipher. record = try_params(host, port, t) if record == nil then - break - end - - if record["protocol"] ~= protocol then - stdnse.print_debug(1, "Protocol not supported, server using %s.", record["protocol"]) - break - end - - if record["type"] == "alert" and record["body"]["description"] == "handshake_failure" then - stdnse.print_debug(2, "No ciphers chosen.") - break - end - - if record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then - stdnse.print_debug(2, "Unexpected record received.") - break - end - - -- Add cipher to the list of accepted ciphers. - name = record["body"]["cipher"] - table.insert(results, name) - stdnse.print_debug(2, "Cipher %s chosen.", name) - - -- Remove cipher from list for next attempt. - for k, v in pairs(t["ciphers"]) do - if v == name then - table.remove(t["ciphers"], k) - break + if protocol_worked then + stdnse.print_debug(2, "Cipher %s rejected.", name) + else + stdnse.print_debug(2, "Cipher %s and/or protocol %s rejected.", name, protocol) end + elseif record["protocol"] ~= protocol then + stdnse.print_debug(1, "Protocol %s rejected.", protocol) + break + elseif record["type"] == "alert" and record["body"]["description"] == "handshake_failure" then + protocol_worked = true + stdnse.print_debug(2, "Cipher %s rejected.", name) + elseif record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then + stdnse.print_debug(2, "Unexpected record received.") + else + protocol_worked = true + stdnse.print_debug(2, "Cipher %s chosen.", name) + + -- Add cipher to the list of accepted ciphers. + name = record["body"]["cipher"] + table.insert(results, name) end end @@ -690,53 +669,42 @@ local function try_protocol(host, port, protocol) end local function find_compressors() - local compressors, k, name, record, results, t, v + local name, protocol_worked, record, results, t results = {} - -- Create list of compressors. - t = { - ["compressors"] = {}, - ["protocol"] = protocol - } - for name, id in pairs(COMPRESSORS) do - table.insert(t["compressors"], name) - end + -- Try every compressor. + protocol_worked = false + for name, _ in pairs(COMPRESSORS) do + -- Create structure. + t = { + ["compressors"] = {name}, + ["protocol"] = protocol + } - -- Create connections while removing previously accepted compressors. - while true do - -- Try connecting with remaining ciphers. + -- Try connecting with compressor. record = try_params(host, port, t) if record == nil then - break - end - - if record["protocol"] ~= protocol then - stdnse.print_debug(1, "Protocol not supported, server using %s.", record["protocol"]) - break - end - - if record["type"] == "alert" and record["body"]["description"] == "handshake_failure" then - stdnse.print_debug(2, "No compressors chosen.") - break - end - - if record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then - stdnse.print_debug(2, "Unexpected record received.") - break - end - - -- Add compressor to the list of accepted compressors. - name = record["body"]["compressor"] - table.insert(results, name) - stdnse.print_debug(2, "Compressor %s chosen.", name) - - -- Remove compressor from list for next attempt. - for k, v in pairs(t["compressors"]) do - if v == name then - table.remove(t["compressors"], k) - break + if protocol_worked then + stdnse.print_debug(2, "Compressor %s rejected.", name) + else + stdnse.print_debug(2, "Compressor %s and/or protocol %s rejected.", name, protocol) end + elseif record["protocol"] ~= protocol then + stdnse.print_debug(1, "Protocol %s rejected.", protocol) + break + elseif record["type"] == "alert" and record["body"]["description"] == "handshake_failure" then + protocol_worked = true + stdnse.print_debug(2, "Compressor %s rejected.", name) + elseif record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then + stdnse.print_debug(2, "Unexpected record received.") + else + protocol_worked = true + stdnse.print_debug(2, "Compressor %s chosen.", name) + + -- Add compressor to the list of accepted compressors. + name = record["body"]["compressor"] + table.insert(results, name) end end @@ -755,16 +723,12 @@ local function try_protocol(host, port, protocol) compressors = find_compressors() -- Format the cipher table. - if nmap.registry.args["ssl-enum-ciphers.sort"] == "name" then - table.sort(ciphers) - end + table.sort(ciphers) ciphers["name"] = "Ciphers (" .. #ciphers .. ")" table.insert(results, ciphers) -- Format the compressor table. - if nmap.registry.args["ssl-enum-ciphers.sort"] == "name" then - table.sort(compressors) - end + table.sort(compressors) compressors["name"] = "Compressors (" .. #compressors .. ")" table.insert(results, compressors) @@ -791,7 +755,7 @@ portrule = function(host, port) end action = function(host, port) - local result, results + local name, result, results results = {}