diff --git a/scripts/ssl-enum-ciphers.nse b/scripts/ssl-enum-ciphers.nse index eeedcca..88d13b5 100644 --- a/scripts/ssl-enum-ciphers.nse +++ b/scripts/ssl-enum-ciphers.nse @@ -542,8 +542,11 @@ local function client_hello(t) if t["compressors"] ~= nil then -- Add specified compressors. for _, compressor in pairs(t["compressors"]) do - compressors = compressors .. bin.pack("C", COMPRESSORS[compressor]) + if compressor ~= "NULL" then + compressors = compressors .. bin.pack("C", COMPRESSORS[compressor]) + end end + compressors = compressors .. bin.pack("C", 0) -- Always include NULL as last choice else -- Add all known compressors. for _, compressor in pairs(COMPRESSORS) do @@ -569,9 +572,8 @@ local function client_hello(t) return record_write("handshake", t["protocol"], h .. b) end -local function try_params(host, port, t, name, records) +local function try_params(host, port, t) local buffer, err, i, record, req, resp, sock, status - local condvar = nmap.condvar(records) -- Create socket. sock = nmap.new_socket() @@ -580,7 +582,6 @@ local function try_params(host, port, t, name, records) if not status then stdnse.print_debug(1, "Can't connect: %s", err) sock:close() - condvar "signal" return nil end @@ -590,7 +591,6 @@ local function try_params(host, port, t, name, records) if not status then stdnse.print_debug(1, "Can't send: %s", err) sock:close() - condvar "signal" return nil end @@ -602,8 +602,7 @@ local function try_params(host, port, t, name, records) status, resp = sock:receive() if not status then sock:close() - condvar "signal" - return record + return nil end buffer = buffer .. resp @@ -612,159 +611,181 @@ local function try_params(host, port, t, name, records) i, record = record_read(buffer, i) if record ~= nil then sock:close() - record.name = name - table.insert(records, record) - condvar "signal" - return + return record end end end -local function try_protocol(host, port, protocol) - local ciphers, compressors, results +local function keys(t) + local ret = {} + for k, _ in pairs(t) do + ret[#ret+1] = k + end + return ret +end + +local function keys_in_chunks(t) + local ret = {{}} + local c = 0 + local b = 1 + for k, _ in pairs(t) do + c = c+1 + ret[b][c] = k + if c > 64 then + c = 0 + b = b + 1 + ret[b] = {} + end + end + return ret +end + +local function remove(t, e) + for i, v in ipairs(t) do + if v == e then + table.remove(t, i) + return i + end + end + return nil +end - local function find_ciphers() - local name, protocol_worked, record, results, t,cipherstr - local records, threads = {}, {} - local condvar = nmap.condvar(records) +local function find_ciphers(host, port, protocol) + local name, protocol_worked, record, results, t,cipherstr + local ciphers = keys_in_chunks(CIPHERS) - results = {} + results = {} - -- Try every cipher. - protocol_worked = false - for name, _ in pairs(CIPHERS) do + -- Try every cipher. + protocol_worked = false + for i, group in ipairs(ciphers) do + stdnse.print_debug(1, "Starting group %d", i) + while (next(group)) do + stdnse.print_debug(1, "%d ciphers left", #group) -- Create structure. t = { - ["ciphers"] = {name}, + ["ciphers"] = group, ["protocol"] = protocol } - -- Try connecting with cipher. - local co = stdnse.new_thread(try_params, host, port, t, name, records) - threads[co] = true - end + record = try_params(host, port, t) - repeat - for thread in pairs(threads) do - if coroutine.status(thread) == "dead" then threads[thread] = nil end - end - if ( next(threads) ) then - condvar "wait" - end - until next(threads) == nil - - - for _, record in ipairs(records) do - local name = record.name if record == nil then if protocol_worked then - stdnse.print_debug(2, "Cipher %s rejected.", name) + stdnse.print_debug(1, "%d ciphers rejected in group %d. (No handshake)", #group, i) else - stdnse.print_debug(2, "Cipher %s and/or protocol %s rejected.", name, protocol) + stdnse.print_debug(1, "%d ciphers in group %d and/or protocol %s rejected. (No handshake)", #group, i, protocol) end + break elseif record["protocol"] ~= protocol then stdnse.print_debug(1, "Protocol %s rejected.", protocol) + protocol_worked = nil break elseif record["type"] == "alert" and record["body"]["description"] == "handshake_failure" then protocol_worked = true - stdnse.print_debug(2, "Cipher %s rejected.", name) + stdnse.print_debug(2, "%d ciphers rejected in group %d.", #group, i) + break elseif record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then stdnse.print_debug(2, "Unexpected record received.") + break else protocol_worked = true + name = record["body"]["cipher"] stdnse.print_debug(2, "Cipher %s chosen.", name) + remove(group, name) -- Add cipher to the list of accepted ciphers. - name = record["body"]["cipher"] - if rankedciphersfilename and rankedciphers[name] then - cipherstr=rankedciphers[name] - else - cipherstr="unknown strength" - end - stdnse.print_debug(2, "Strength of %s rated %d.",cipherstr,cipherstrength[cipherstr]) - if mincipherstrength>cipherstrength[cipherstr] then - stdnse.print_debug(2, "Downgrading min cipher strength to %d.",cipherstrength[cipherstr]) - mincipherstrength=cipherstrength[cipherstr] - end - name=name.." - "..cipherstr table.insert(results, name) end end + if protocol_worked == nil then break end + end - return results - end - - local function find_compressors() - local name, protocol_worked, record, results, t - local records, threads = {}, {} - local condvar = nmap.condvar(records) - - results = {} - - -- Try every compressor. - protocol_worked = false - for name, _ in pairs(COMPRESSORS) do - -- Create structure. - t = { - ["compressors"] = {name}, - ["protocol"] = protocol - } - - -- Try connecting with compressor. - local co = stdnse.new_thread(try_params, host, port, t, name, records) - threads[co] = true - end - - repeat - for thread in pairs(threads) do - if coroutine.status(thread) == "dead" then threads[thread] = nil end - end - if ( next(threads) ) then - condvar "wait" - end - until next(threads) == nil - - for _, record in ipairs(records) do - local name = record.name - if record == nil then - 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.") - elseif record["body"]["compressor"] ~= name then - protocol_worked = true - stdnse.print_debug(2, "Compressor %s rejected.", name) - else - protocol_worked = true - stdnse.print_debug(2, "Compressor %s chosen.", name) + return results +end - -- Add compressor to the list of accepted compressors. - table.insert(results, name) - end - end +local function find_compressors(host, port, protocol, good_cipher) + local name, protocol_worked, record, results, t + local compressors = keys(COMPRESSORS) + + results = {} + + -- Try every compressor. + protocol_worked = false + while (next(compressors)) do + -- Create structure. + t = { + ["compressors"] = compressors, + ["ciphers"] = {good_cipher}, + ["protocol"] = protocol + } + + -- Try connecting with compressor. + record = try_params(host, port, t) + + if record == nil then + if protocol_worked then + stdnse.print_debug(1, "%d compressors rejected. (No handshake)", #compressors) + else + stdnse.print_debug(1, "%d compressors and/or protocol %s rejected. (No handshake)", #compressors, protocol) + end + break + 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, "%d compressors rejected.", #compressors) + break + elseif record["type"] ~= "handshake" or record["body"]["type"] ~= "server_hello" then + stdnse.print_debug(2, "Unexpected record received.") + break + else + protocol_worked = true + name = record["body"]["compressor"] + stdnse.print_debug(2, "Compressor %s chosen.", name) + remove(compressors, name) + + -- Add compressor to the list of accepted compressors. + table.insert(results, name) + if name == "NULL" then + break -- NULL is always last choice, and must be included + end + end + end + + return results +end - return results - end +local function try_protocol(host, port, protocol, upresults) + local ciphers, compressors, results + local condvar = nmap.condvar(upresults) results = {} -- Find all valid ciphers. - ciphers = find_ciphers() + ciphers = find_ciphers(host, port, protocol) if #ciphers == 0 then - return {} + condvar "signal" + return nil end - -- Find all valid compression methods. - compressors = find_compressors() + compressors = find_compressors(host, port, protocol, ciphers[1]) + + -- Add rankings to ciphers + for i, name in ipairs(ciphers) do + if rankedciphersfilename and rankedciphers[name] then + cipherstr=rankedciphers[name] + else + cipherstr="unknown strength" + end + stdnse.print_debug(2, "Strength of %s rated %d.",cipherstr,cipherstrength[cipherstr]) + if mincipherstrength>cipherstrength[cipherstr] then + stdnse.print_debug(2, "Downgrading min cipher strength to %d.",cipherstrength[cipherstr]) + mincipherstrength=cipherstrength[cipherstr] + end + ciphers[i]=name.." - "..cipherstr + end -- Format the cipher table. table.sort(ciphers) @@ -776,7 +797,12 @@ local function try_protocol(host, port, protocol) compressors["name"] = "Compressors (" .. #compressors .. ")" table.insert(results, compressors) - return results + if #results > 0 then + results["name"] = protocol + table.insert(upresults, results) + end + condvar "signal" + return nil end -- Shamelessly stolen from nselib/unpwdb.lua and changed a bit. (Gabriel Lawrence) @@ -830,15 +856,24 @@ action = function(host, port) results = {} + local condvar = nmap.condvar(results) + local threads = {} + for name, _ in pairs(PROTOCOLS) do stdnse.print_debug(1, "Trying protocol %s.", name) - result = try_protocol(host.ip, port.number, name) - if #result > 0 then - result["name"] = name - table.insert(results, result) - end + local co = stdnse.new_thread(try_protocol, host.ip, port.number, name, results) + threads[co] = true end + repeat + for thread in pairs(threads) do + if coroutine.status(thread) == "dead" then threads[thread] = nil end + end + if ( next(threads) ) then + condvar "wait" + end + until next(threads) == nil + -- Sort protocol results by name. table.sort(results, function(a, b) return a["name"] < b["name"] end) if rankedciphersfilename then