summaryrefslogtreecommitdiffstats
path: root/scripts/rsa-vuln-roca.nse
blob: bd3e6275c2dfdda2d885319fff79c68ce76170dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
local stdnse = require "stdnse"
local openssl = stdnse.silent_require "openssl"
local nmap = require "nmap"
local shortport = require "shortport"
local ssh2 = require "ssh2"
local sslcert = require "sslcert"
local math = require "math"
local string = require "string"
local vulns = require "vulns"

description = [[
Detects RSA keys vulnerable to Return Of Coppersmith Attack (ROCA) factorization.

SSH hostkeys and SSL/TLS certificates are checked. The checks require recent updates to the openssl NSE library.

References:
* https://crocs.fi.muni.cz/public/papers/rsa_ccs17
]]

---
-- @usage
-- nmap -p 22,443 --script rsa-vuln-roca <target>
--
-- @output
--
--@xmloutput
--
-- @see ssl-cert.nse
-- @see ssh-hostkey.nse

author = "Daniel Miller"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"vuln", "safe"}

-- only run this script if the target host is NOT a private (RFC1918) IP address)
-- and the port is an open SSL service
portrule = function(host, port)
  if not openssl.bignum_div then
    stdnse.verbose1("This script requires the latest update to NSE's openssl library bindings.")
    return false
  end
  -- SSH key check
  return shortport.port_or_service(22, "ssh")
  -- same criteria as ssl-cert.nse
  or shortport.ssl(host, port) or sslcert.isPortSupported(port) or sslcert.getPrepareTLSWithoutReconnect(port)
end

local function is_vulnerable (modulus)
  local dec2bn = openssl.bignum_dec2bn
  -- Prime tests used under MIT license from https://github.com/crocs-muni/roca
  local prime_tests = nmap.registry.roca_prime_tests or {
    {dec2bn("3"), dec2bn("6")},
    {dec2bn("5"), dec2bn("30")},
    {dec2bn("7"), dec2bn("126")},
    {dec2bn("11"), dec2bn("1026")},
    {dec2bn("13"), dec2bn("5658")},
    {dec2bn("17"), dec2bn("107286")},
    {dec2bn("19"), dec2bn("199410")},
    {dec2bn("23"), dec2bn("8388606")},
    {dec2bn("29"), dec2bn("536870910")},
    {dec2bn("31"), dec2bn("2147483646")},
    {dec2bn("37"), dec2bn("67109890")},
    {dec2bn("41"), dec2bn("2199023255550")},
    {dec2bn("43"), dec2bn("8796093022206")},
    {dec2bn("47"), dec2bn("140737488355326")},
    {dec2bn("53"), dec2bn("5310023542746834")},
    {dec2bn("59"), dec2bn("576460752303423486")},
    {dec2bn("61"), dec2bn("1455791217086302986")},
    {dec2bn("67"), dec2bn("147573952589676412926")},
    {dec2bn("71"), dec2bn("20052041432995567486")},
    {dec2bn("73"), dec2bn("6041388139249378920330")},
    {dec2bn("79"), dec2bn("207530445072488465666")},
    {dec2bn("83"), dec2bn("9671406556917033397649406")},
    {dec2bn("89"), dec2bn("618970019642690137449562110")},
    {dec2bn("97"), dec2bn("79228162521181866724264247298")},
    {dec2bn("101"), dec2bn("2535301200456458802993406410750")},
    {dec2bn("103"), dec2bn("1760368345969468176824550810518")},
    {dec2bn("107"), dec2bn("50079290986288516948354744811034")},
    {dec2bn("109"), dec2bn("473022961816146413042658758988474")},
    {dec2bn("113"), dec2bn("10384593717069655257060992658440190")},
    {dec2bn("127"), dec2bn("144390480366845522447407333004847678774")},
    {dec2bn("131"), dec2bn("2722258935367507707706996859454145691646")},
    {dec2bn("137"), dec2bn("174224571863520493293247799005065324265470")},
    {dec2bn("139"), dec2bn("696898287454081973172991196020261297061886")},
    {dec2bn("149"), dec2bn("713623846352979940529142984724747568191373310")},
    {dec2bn("151"), dec2bn("1800793591454480341970779146165214289059119882")},
    {dec2bn("157"), dec2bn("126304807362733370595828809000324029340048915994")},
    {dec2bn("163"), dec2bn("11692013098647223345629478661730264157247460343806")},
    {dec2bn("167"), dec2bn("187072209578355573530071658587684226515959365500926")},
  }
  nmap.registry.roca_prime_tests = prime_tests

  --stdnse.debug1("Testing %s", openssl.bignum_bn2dec(modulus))
  for _, test in ipairs(prime_tests) do
    local prime, fingerprint = test[1], test[2]
    local _, bnshift = openssl.bignum_div(modulus, prime)
    -- prime is small, so bnshift is small. Safe to convert to Lua integer
    local string_shift = openssl.bignum_bn2dec(bnshift)
    local shift = math.tointeger(string_shift)
    if not shift then
      stdnse.debug1("Unable to convert %s to integer", string_shift)
      return nil
    end
    --stdnse.debug1("Testing mod %s, shift is %s", openssl.bignum_bn2dec(prime), shift)
    if not openssl.bignum_is_bit_set(fingerprint, shift) then
      stdnse.debug1("Not vulnerable")
      return nil
    end
  end
      stdnse.debug1("VULNERABLE!!!!!!")

  return "Vulnerable to ROCA"
end

local function ssl_get_modulus(host, port)
  local ok, cert = sslcert.getCertificate(host, port)
  if not ok then
    stdnse.debug1("failed to obtain SSL certificate")
    return nil
  end

  if cert.pubkey.type ~= "rsa" then
    stdnse.debug1("Non-RSA certificate, not vulnerable to ROCA")
    return nil
  end

  local modulus = cert.pubkey.modulus
  if not modulus then
    stdnse.debug1("No modulus available; upgrade Nmap?")
    return nil
  end
  return modulus
end

local function ssh_get_modulus(host, port)
  local key = ssh2.fetch_host_key( host, port, "ssh-rsa" )
  if not key then
    stdnse.debug1("No RSA hostkey, not vulnerable to ROCA")
    return nil
  end
  local _, e, n = string.unpack(">s4s4s4", key.fp_input)
  return openssl.bignum_bin2bn(n)
end

action = function(host, port)
  local vuln_table = {
    title = "ROCA: Vulnerable RSA generation",
    state = vulns.STATE.NOT_VULN,
    -- TODO: Update when CVE is scored
    --risk_factor = "High",
    description = [[
    The Infineon RSA library 1.02.013 in Infineon Trusted Platform Module (TPM)
    firmware, such as versions before 0000000000000422 - 4.34, before
    000000000000062b - 6.43, and before 0000000000008521 - 133.33, mishandles
    RSA key generation, which makes it easier for attackers to defeat various
    cryptographic protection mechanisms via targeted attacks, aka ROCA.
    ]],
    IDS = {CVE = "CVE-2017-15361"},
    references = {
      "https://crocs.fi.muni.cz/public/papers/rsa_ccs17",
    }
  }

  local report = vulns.Report:new(SCRIPT_NAME, host, port)
  local modulus
  if shortport.ssl(host, port) or sslcert.isPortSupported(port) or sslcert.getPrepareTLSWithoutReconnect(port) then
    modulus = ssl_get_modulus(host, port)
  elseif shortport.port_or_service(22, "ssh")(host, port) then
    modulus = ssh_get_modulus(host, port)
  end

  if modulus and is_vulnerable(modulus) then
    vuln_table.state = vulns.STATE.VULN
  end
  return report:make_output(vuln_table)
end