summaryrefslogtreecommitdiffstats
path: root/scripts/eap-info.nse
blob: 5741f4cac86c6204c3f0221e0e327ad94f2da22a (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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
local eap = require "eap"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"

description = [[
Enumerates the authentication methods offered by an EAP (Extensible
Authentication Protocol) authenticator for a given identity or for the
anonymous identity if no argument is passed.
]]

---
-- @usage
-- nmap -e interface --script eap-info [--script-args="eap-info.identity=0-user,eap-info.scan={13,50}"] <target>
--
-- @output
-- Pre-scan script results:
-- | eap-info:
-- | Available authentication methods with identity="anonymous" on interface eth2
-- |   true     PEAP
-- |   true     EAP-TTLS
-- |   false    EAP-TLS
-- |_  false    EAP-MSCHAP-V2
--
-- @args eap-info.identity Identity to use for the first step of the authentication methods (if omitted "anonymous" will be used).
-- @args eap-info.scan Table of authentication methods to test, e.g. { 4, 13, 25 } for MD5, TLS and PEAP. Default: TLS, TTLS, PEAP, MSCHAP.
-- @args eap-info.interface Network interface to use for the scan, overrides "-e".
-- @args eap-info.timeout Maximum time allowed for the scan (default 10s). Methods not tested because of timeout will be listed as "unknown".

author = "Riccardo Cecolin"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"

categories = { "broadcast", "safe" }


prerule = function()
  return nmap.is_privileged()
end

local default_scan = {
  eap.eap_t.TLS,
  eap.eap_t.TTLS,
  eap.eap_t.PEAP,
  eap.eap_t.MSCHAP,
}

local UNKNOWN = "unknown"

action = function()

  local arg_interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
  local arg_identity = stdnse.get_script_args(SCRIPT_NAME .. ".identity")
  local arg_scan = stdnse.get_script_args(SCRIPT_NAME .. ".scan")
  local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
  local iface

  -- trying with provided interface name
  if arg_interface then
    iface = nmap.get_interface_info(arg_interface)
  end

  -- trying with default nmap interface
  if not iface then
    local iname = nmap.get_interface()
    if iname then
      iface = nmap.get_interface_info(iname)
    end
  end

  -- failed
  if not iface then
    return "please specify an interface with -e"
  end
  stdnse.debug1("iface: %s", iface.device)

  local timeout = (arg_timeout or 10) * 1000

  stdnse.debug2("timeout: %s", timeout)

  local pcap = nmap.new_socket()
  pcap:pcap_open(iface.device, 512, true, "ether proto 0x888e")


  local identity = { name="anonymous", auth = {}, probe = -1 }

  if arg_identity then
    identity.name = tostring(arg_identity)
  end

  local scan
  if arg_scan == nil or type(arg_scan) ~= "table" or #arg_scan == 0 then
    scan = default_scan
  else
    scan = arg_scan
  end

  local valid = false
  for i,v in ipairs(scan) do
    v = tonumber(v)
    if v ~= nil and v < 256 and v > 3 then
      stdnse.debug1("selected: %s", eap.eap_str[v] or "unassigned" )
      identity.auth[v] = UNKNOWN
      valid = true
    end
  end

  if not valid then
    return "no valid scan methods provided"
  end

  local tried_all = false

  local start_time = nmap.clock_ms()
  eap.send_start(iface)

  while(nmap.clock_ms() - start_time < timeout) and not tried_all do
    local status, plen, l2_data, l3_data, time = pcap:pcap_receive()
    if (status) then
      stdnse.debug2("packet size: 0x%x", plen )
      local packet = eap.parse(l2_data .. l3_data)

      if packet then
        stdnse.debug2("packet valid")

        -- respond to identity requests, using the same session id
        if packet.eap.type == eap.eap_t.IDENTITY and  packet.eap.code == eap.code_t.REQUEST then
          stdnse.debug1("server identity: %s",packet.eap.body.identity)
          eap.send_identity_response(iface, packet.eap.id, identity.name)
        end

        -- respond with NAK to every auth request to enumerate them until we get a failure
        if packet.eap.type ~= eap.eap_t.IDENTITY and  packet.eap.code == eap.code_t.REQUEST then
          stdnse.debug1("auth request: %s",eap.eap_str[packet.eap.type])
          identity.auth[packet.eap.type] = true

          identity.probe = -1
          for i,v in pairs(identity.auth) do
            stdnse.debug1("identity.auth: %d %s",i,tostring(v))
            if v == UNKNOWN then
              identity.probe = i
              eap.send_nak_response(iface, packet.eap.id, i)
              break
            end
          end
          if identity.probe == -1 then tried_all = true end
        end

        -- retry on failure
        if packet.eap.code == eap.code_t.FAILURE then
          stdnse.debug1("auth failure")
          identity.auth[identity.probe] = false

          -- don't give up at the first failure!
          -- mac spoofing to avoid to wait too much
          local d = string.byte(iface.mac,6)
          d = (d + 1) % 256
          iface.mac = iface.mac:sub(1,5) .. string.pack("B",d)

          tried_all = true
          for i,v in pairs(identity.auth) do
            if v == UNKNOWN then
              tried_all = false
              break
            end
          end
          if not tried_all then
            eap.send_start(iface)
          end
        end

      else
        stdnse.debug1("packet invalid! wrong filter?")
      end
    end
  end

  local results = { ["name"] = ("Available authentication methods with identity=\"%s\" on interface %s"):format(identity.name, iface.device) }
  for i,v in pairs(identity.auth) do
    if v== true then
      table.insert(results, 1, ("%-8s %s"):format(tostring(v), eap.eap_str[i] or "unassigned" ))
    else
      table.insert(results, ("%-8s %s"):format(tostring(v), eap.eap_str[i] or "unassigned" ))
    end
  end

  for i,v in ipairs(results) do
    stdnse.debug1("%s", tostring(v))
  end

  return stdnse.format_output(true, results)
end