diff --git a/CHANGELOG b/CHANGELOG index 5cc64dc92..97af1c00a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o Added the ssl-cert.nse script, which retrieves and prints the server + SSL certificate. [David] + o [Ncat] The --idle-timeout option now exits when *both* stdin and the socket have been idle for the given time. Previously it would exit when *either* of them had been idle, meaning that the program would diff --git a/scripts/script.db b/scripts/script.db index cb154481f..8d8924aac 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -57,6 +57,7 @@ Entry { filename = "socks-open-proxy.nse", categories = { "default", "discovery" Entry { filename = "sql-injection.nse", categories = { "intrusive", "vuln", } } Entry { filename = "ssh-hostkey.nse", categories = { "default", "intrusive", "safe", } } Entry { filename = "sshv1.nse", categories = { "default", "safe", } } +Entry { filename = "ssl-cert.nse", categories = { "discovery", "safe", } } Entry { filename = "sslv2.nse", categories = { "default", "safe", } } Entry { filename = "telnet-brute.nse", categories = { "auth", "intrusive", } } Entry { filename = "upnp-info.nse", categories = { "default", "safe", } } diff --git a/scripts/ssl-cert.nse b/scripts/ssl-cert.nse new file mode 100644 index 000000000..f489078c1 --- /dev/null +++ b/scripts/ssl-cert.nse @@ -0,0 +1,168 @@ +description = [[ +Retrieves a server's SSL certificate. The amount of information printed +about the certificate depends on the verbosity level. With no extra +verbosity, the script prints the validity period and the commonName, +organizationName, stateOrProvinceName, and countryName of the subject. + + +443/tcp open https +| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\ +/stateOrProvinceName=California/countryName=US +| Not valid before: 2009-05-28 00:00:00 +|_ Not valid after: 2010-05-01 23:59:59 + + +With -v it adds the issuer name and fingerprints. + + +443/tcp open https +| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\ +/stateOrProvinceName=California/countryName=US +| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\ +/organizationName=VeriSign, Inc./countryName=US +| Not valid before: 2009-05-28 00:00:00 +| Not valid after: 2010-05-01 23:59:59 +| MD5: c5b8 7ddd ccc7 537f 8861 b476 078d e8fd +|_ SHA-1: dc5a cb8b 9eb9 b5de 7117 c536 8c15 0e75 ba88 702e + + +With -vv it adds the PEM-encoded contents of the entire +certificate. + + +443/tcp open https +| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\ +/stateOrProvinceName=California/countryName=US/serialNumber=3014267\ +/1.3.6.1.4.1.311.60.2.1.3=US/streetAddress=2211 N 1st St\ +/1.3.6.1.4.1.311.60.2.1.2=Delaware/postalCode=95131-2021\ +/localityName=San Jose/organizationalUnitName=Information Systems\ +/2.5.4.15=V1.0, Clause 5.(b) +| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\ +/organizationName=VeriSign, Inc./countryName=US\ +/organizationalUnitName=Terms of use at https://www.verisign.com/rpa (c)06 +| Not valid before: 2009-05-28 00:00:00 +| Not valid after: 2010-05-01 23:59:59 +| MD5: c5b8 7ddd ccc7 537f 8861 b476 078d e8fd +| SHA-1: dc5a cb8b 9eb9 b5de 7117 c536 8c15 0e75 ba88 702e +| -----BEGIN CERTIFICATE----- +| MIIFxzCCBK+gAwIBAgIQX02QuADDB7CVjZdooVge+zANBgkqhkiG9w0BAQUFADCB +... + +]] + +--- +-- @output +-- 443/tcp open https +-- | ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\ +-- /stateOrProvinceName=California/countryName=US +-- | Not valid before: 2009-05-28 00:00:00 +-- |_ Not valid after: 2010-05-01 23:59:59 + +author = "David Fifield " + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +categories = { "safe", "discovery" } + +require("nmap") +require("nsedebug") +require("stdnse") + +local stringify_name +local date_to_string +local table_find + +local LIKELY_SSL_PORTS = { 443, 465, 989, 990, 992, 993, 994, 995, 587, 8443 } + +portrule = function(host, port) + return port.version.service_tunnel == "ssl" + or table_find(LIKELY_SSL_PORTS, port.number) +end + +action = function(host, port) + local s = nmap.new_socket() + local status, error = s:connect(host.ip, port.number, "ssl") + if not status then + if nmap.verbosity() > 0 then + return error + else + return nil + end + end + local cert = s:get_ssl_certificate() + s:close() + + local lines = {} + lines[#lines + 1] = "Subject: " .. stringify_name(cert.subject) + + if nmap.verbosity() > 0 then + lines[#lines + 1] = "Issuer: " .. stringify_name(cert.issuer) + end + + lines[#lines + 1] = "Not valid before: " .. + date_to_string(cert.validity.notBefore) + lines[#lines + 1] = "Not valid after: " .. + date_to_string(cert.validity.notAfter) + + lines[#lines + 1] = "MD5: " .. stdnse.tohex(cert:digest("md5"), { separator = " ", group = 4 }) + lines[#lines + 1] = "SHA-1: " .. stdnse.tohex(cert:digest("sha1"), { separator = " ", group = 4 }) + + if nmap.verbosity() > 1 then + lines[#lines + 1] = cert.pem + end + + return stdnse.strjoin("\n", lines) +end + +-- Find the index of a value in an array. +function table_find(t, value) + local i, v + for i, v in ipairs(t) do + if v == value then + return i + end + end + return nil +end + +-- These are the subject/issuer name fields that will be shown, in this order, +-- without a high verbosity. +local NON_VERBOSE_FIELDS = { "commonName", "organizationName", + "stateOrProvinceName", "countryName" } + +function stringify_name(name) + local fields = {} + local _, k, v + if not name then + return nil + end + for _, k in ipairs(NON_VERBOSE_FIELDS) do + v = name[k] + if v then + fields[#fields + 1] = string.format("%s=%s", k, v) + end + end + if nmap.verbosity() > 1 then + for k, v in pairs(name) do + -- Don't include a field twice. + if not table_find(NON_VERBOSE_FIELDS, k) then + if type(k) == "table" then + k = stdnse.strjoin(".", k) + end + fields[#fields + 1] = string.format("%s=%s", k, v) + end + end + end + return stdnse.strjoin("/", fields) +end + +function date_to_string(date) + if not date then + return "MISSING" + end + if type(date) == "string" then + return string.format("Can't parse; string is \"%s\"", date) + else + return os.date("%Y-%m-%d %H:%M:%S", os.time(date)) + end +end