mirror of
https://github.com/nmap/nmap.git
synced 2026-08-04 06:40:48 +00:00
o [NSE] Added an EAP library and the script eap-info which discovers supported
EAP authentication methods. [Riccardo Cecolin]
This commit is contained in:
parent
cde380ea2c
commit
1384a953a1
4 changed files with 485 additions and 0 deletions
|
|
@ -1,5 +1,8 @@
|
|||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added an EAP library and the script eap-info which discovers supported
|
||||
EAP authentication methods. [Riccardo Cecolin]
|
||||
|
||||
o [NSE] Added a Versant object database library and the scripts
|
||||
broadcast-versant-locate and versant-info. The first discovers Versant
|
||||
databases on the LAN and the second queries them for information. [Patrik]
|
||||
|
|
|
|||
287
nselib/eap.lua
Normal file
287
nselib/eap.lua
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
---
|
||||
-- EAP library supporting a limited subset of features.
|
||||
--
|
||||
-- The library was designed and tested against hostapd v0.6.10
|
||||
-- The EAP protocol names are the ones specified in:
|
||||
-- http://www.iana.org/assignments/eap-numbers/eap-numbers.xml
|
||||
--
|
||||
-- Scripts can use the library to start an eap session and then to
|
||||
-- send identity and nak responses to identity and authentication
|
||||
-- requests made by AP authenticators to analyze their behaviour.
|
||||
--
|
||||
-- The following sample code illustrates how to respond to an identity
|
||||
-- request:
|
||||
--
|
||||
-- <code>
|
||||
-- pcap:pcap_open(iface.device, 512, true, "ether proto 0x888e")
|
||||
-- ...
|
||||
-- local _, _, l2_data, l3_data, _ = pcap:pcap_receive()
|
||||
-- local packet = eap.parse(l2_data .. l3_data3)
|
||||
-- if packet then
|
||||
-- if packet.eap.type == eap.eap_t.IDENTITY and packet.eap.code == eap.code_t.REQUEST then
|
||||
-- eap.send_identity_response(iface, packet.eap.id, "anonymous")
|
||||
-- end
|
||||
-- end
|
||||
-- </code>
|
||||
--
|
||||
--
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
--
|
||||
-- @author "Riccardo Cecolin <n@rikiji.de>"
|
||||
--
|
||||
|
||||
module(... or "eap", package.seeall)
|
||||
|
||||
-- Created 02/23/2012 - v0.1
|
||||
|
||||
require 'nmap'
|
||||
require 'packet'
|
||||
require 'bin'
|
||||
|
||||
|
||||
local ETHER_BROADCAST = "01:80:c2:00:00:03"
|
||||
local ETHER_TYPE_EAPOL_N = 0x888E
|
||||
local ETHER_TYPE_EAPOL = bin.pack(">S",ETHER_TYPE_EAPOL_N)
|
||||
local ETHER_HEADER_SIZE = 14
|
||||
local EAPOL_HEADER_SIZE = 4
|
||||
local EAP_HEADER_SIZE = 5
|
||||
|
||||
eapol_t = {
|
||||
PACKET = 0,
|
||||
START = 1,
|
||||
LOGOFF = 2,
|
||||
KEY = 3,
|
||||
ASF = 4,
|
||||
}
|
||||
|
||||
eapol_str = {
|
||||
[0] = "EAP Packet",
|
||||
[1] = "EAPOL Start",
|
||||
[2] = "EAPOL Logoff",
|
||||
[3] = "EAPOL Key",
|
||||
[4] = "EAPOL Encapsulated ASF Alert",
|
||||
}
|
||||
|
||||
code_t = {
|
||||
REQUEST = 1,
|
||||
RESPONSE = 2,
|
||||
SUCCESS = 3,
|
||||
FAILURE = 4,
|
||||
INITIATE = 5,
|
||||
FINISH = 6,
|
||||
}
|
||||
|
||||
code_str = {
|
||||
[1] = "Request",
|
||||
[2] = "Response",
|
||||
[3] = "Success",
|
||||
[4] = "Failure",
|
||||
[5] = "Initiate",
|
||||
[6] = "Finish",
|
||||
}
|
||||
|
||||
eap_t = {
|
||||
IDENTITY = 1,
|
||||
NAK = 3,
|
||||
MD5 = 4,
|
||||
TLS = 13,
|
||||
TTLS = 21,
|
||||
PEAP = 25,
|
||||
MSCHAP = 29,
|
||||
}
|
||||
|
||||
eap_str = {
|
||||
[0] = "Reserved",
|
||||
[1] = "Identity",
|
||||
[2] = "Notification",
|
||||
[3] = "Legacy Nak",
|
||||
[4] = "MD5-Challenge",
|
||||
[5] = "One-Time Password (OTP)",
|
||||
[6] = "Generic Token Card (GTC)",
|
||||
[7] = "Allocated",
|
||||
[8] = "Allocated",
|
||||
[9] = "RSA Public Key Authentication",
|
||||
[10] = "DSS Unilateral",
|
||||
[11] = "KEA",
|
||||
[12] = "KEA-VALIDATE",
|
||||
[13] = "EAP-TLS",
|
||||
[14] = "Defender Token (AXENT)",
|
||||
[15] = "RSA Security SecurID EAP",
|
||||
[16] = "Arcot Systems EAP",
|
||||
[17] = "EAP-Cisco Wireless",
|
||||
[18] = "GSM Subscriber Identity Modules (EAP-SIM)",
|
||||
[19] = "SRP-SHA1",
|
||||
[20] = "Unassigned",
|
||||
[21] = "EAP-TTLS",
|
||||
[22] = "Remote Access Service",
|
||||
[23] = "EAP-AKA Authentication",
|
||||
[24] = "EAP-3Com Wireless",
|
||||
[25] = "PEAP",
|
||||
[26] = "MS-EAP-Authentication",
|
||||
[27] = "Mutual Authentication w/Key Exchange (MAKE)",
|
||||
[28] = "CRYPTOCard",
|
||||
[29] = "EAP-MSCHAP-V2",
|
||||
[30] = "DynamID",
|
||||
[31] = "Rob EAP",
|
||||
[32] = "Protected One-Time Password",
|
||||
[33] = "MS-Authentication-TLV",
|
||||
[34] = "SentriNET",
|
||||
[35] = "EAP-Actiontec Wireless",
|
||||
[36] = "Cogent Systems Biometrics Authentication EAP",
|
||||
[37] = "AirFortress EAP",
|
||||
[38] = "EAP-HTTP Digest",
|
||||
[39] = "SecureSuite EAP",
|
||||
[40] = "DeviceConnect EAP",
|
||||
[41] = "EAP-SPEKE",
|
||||
[42] = "EAP-MOBAC",
|
||||
[43] = "EAP-FAST",
|
||||
[44] = "ZoneLabs EAP (ZLXEAP)",
|
||||
[45] = "EAP-Link",
|
||||
[46] = "EAP-PAX",
|
||||
[47] = "EAP-PSK",
|
||||
[48] = "EAP-SAKE",
|
||||
[49] = "EAP-IKEv2",
|
||||
[50] = "EAP-AKA'",
|
||||
[51] = "EAP-GPSK",
|
||||
[52] = "EAP-pwd",
|
||||
[53] = "EAP-EKE Version 1",
|
||||
-- 54-253 Unassigned
|
||||
[254] = "Reserved for the Expanded Type",
|
||||
[255] = "Experimental",
|
||||
}
|
||||
|
||||
local make_eapol = function (arg)
|
||||
if not arg.type then arg.type = eapol_types.PACKET end
|
||||
if not arg.version then arg.version = 1 end
|
||||
if not arg.payload then arg.payload = "" end
|
||||
if not arg.src then return nil end
|
||||
|
||||
local p = packet.Frame:new()
|
||||
p.mac_src = arg.src
|
||||
p.mac_dst = packet.mactobin(ETHER_BROADCAST)
|
||||
p.ether_type = ETHER_TYPE_EAPOL
|
||||
|
||||
local bin_payload = bin.pack(">A",arg.payload)
|
||||
p.buf = bin.pack("C",arg.version) .. bin.pack("C",arg.type) .. bin.pack(">S",bin_payload:len()).. bin_payload
|
||||
p:build_ether_frame()
|
||||
return p.frame_buf
|
||||
end
|
||||
|
||||
local make_eap = function (arg)
|
||||
|
||||
if not arg.code then arg.code = code_t.REQUEST end
|
||||
if not arg.id then arg.id = math.random(0,255) end
|
||||
if not arg.type then arg.type = eap_t.IDENTITY end
|
||||
if not arg.payload then arg.payload = "" end
|
||||
if not arg.header then return nil end
|
||||
|
||||
local bin_payload = bin.pack(">A",arg.payload)
|
||||
arg.header.payload = bin.pack("C",arg.code) .. bin.pack("C",arg.id) .. bin.pack(">S",bin_payload:len() + EAP_HEADER_SIZE).. bin.pack("C",arg.type) .. bin_payload
|
||||
|
||||
local v = make_eapol(arg.header)
|
||||
stdnse.print_debug(2, "make eapol %s", arg.header.src)
|
||||
|
||||
return v
|
||||
end
|
||||
|
||||
parse = function (packet)
|
||||
local tb = {}
|
||||
local _
|
||||
|
||||
stdnse.print_debug(2, "packet size: 0x%x", #packet )
|
||||
|
||||
-- parsing ethernet header
|
||||
_, tb.mac_src, tb.mac_dst, tb.ether_type = bin.unpack(">A6A6S", packet)
|
||||
_, tb.mac_src_str, tb.mac_dst_str = bin.unpack(">H6H6", packet)
|
||||
|
||||
-- parsing eapol header
|
||||
_, tb.version, tb.type, tb.length = bin.unpack(">CCS", packet, ETHER_HEADER_SIZE + 1)
|
||||
|
||||
stdnse.print_debug(1, "mac_src: %s, mac_dest: %s, ether_type: 0x%X",
|
||||
tb.mac_src_str, tb.mac_dst_str, tb.ether_type)
|
||||
|
||||
if tb.ether_type ~= ETHER_TYPE_EAPOL_N then return nil, "not an eapol packet" end
|
||||
|
||||
stdnse.print_debug(2, "version: %X, type: %s, length: 0x%X",
|
||||
tb.version, eapol_str[tb.type] or "unknown",
|
||||
tb.length)
|
||||
|
||||
tb.eap = {}
|
||||
|
||||
if tb.length > 0 then
|
||||
-- parsing body
|
||||
|
||||
_, tb.eap.code, tb.eap.id, tb.eap.length, tb.eap.type = bin.unpack(">CCSC", packet,
|
||||
ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + 1)
|
||||
stdnse.print_debug(2, "code: %s, id: 0x%X, length: 0x%X, type: %s",
|
||||
code_str[tb.eap.code] or "unknown",
|
||||
tb.eap.id, tb.eap.length, eap_str[tb.eap.type] or "unknown" )
|
||||
if tb.length ~= tb.eap.length then
|
||||
stdnse.print_debug(1, "WARNING length mismatch: 0x%X and 0x%X", tb.length, tb.eap.length )
|
||||
end
|
||||
end
|
||||
|
||||
tb.eap.body = {}
|
||||
|
||||
-- parsing payload
|
||||
if tb.length > 5 and tb.eap.type == eap_t.IDENTITY then
|
||||
_, tb.eap.body.identity = bin.unpack("z", packet,
|
||||
ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1)
|
||||
stdnse.print_debug(1, "identity: %s", tb.eap.body.identity )
|
||||
end
|
||||
|
||||
if tb.length > 5 and tb.eap.type == eap_t.MD5 then
|
||||
_, tb.eap.body.challenge = bin.unpack("p", packet, ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1)
|
||||
end
|
||||
|
||||
return tb
|
||||
end
|
||||
|
||||
send_identity_response = function (iface, id, identity)
|
||||
|
||||
if not iface then
|
||||
stdnse.print_debug(1, "no interface given")
|
||||
return
|
||||
end
|
||||
|
||||
local dnet = nmap.new_dnet()
|
||||
local tb = {src = iface.mac, type = eap.eapol_t.PACKET}
|
||||
local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.IDENTITY, id = id, payload = identity}
|
||||
|
||||
dnet:ethernet_open(iface.device)
|
||||
dnet:ethernet_send(response)
|
||||
dnet:ethernet_close()
|
||||
end
|
||||
|
||||
send_nak_response = function (iface, id, auth)
|
||||
|
||||
if not iface then
|
||||
stdnse.print_debug(1, "no interface given")
|
||||
return
|
||||
end
|
||||
|
||||
local dnet = nmap.new_dnet()
|
||||
local tb = {src = iface.mac, type = eap.eapol_t.PACKET}
|
||||
local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.NAK, id = id, payload = bin.pack("C",auth)}
|
||||
|
||||
dnet:ethernet_open(iface.device)
|
||||
dnet:ethernet_send(response)
|
||||
dnet:ethernet_close()
|
||||
end
|
||||
|
||||
|
||||
send_start = function (iface)
|
||||
|
||||
if not iface then
|
||||
stdnse.print_debug(1, "no interface given")
|
||||
return
|
||||
end
|
||||
|
||||
local dnet = nmap.new_dnet()
|
||||
local start = make_eapol{type = eap.eapol_t.START, src = iface.mac}
|
||||
|
||||
dnet:ethernet_open(iface.device)
|
||||
dnet:ethernet_send(start)
|
||||
dnet:ethernet_close()
|
||||
|
||||
end
|
||||
194
scripts/eap-info.nse
Normal file
194
scripts/eap-info.nse
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
description = [[
|
||||
Enumerates the authentication methods offered by an EAP 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 identity Identity to use for the first step of the authentication methods (if omitted "anonymous" will be used).
|
||||
-- @args scan Table of authentication methods to test, e.g. { 4, 13, 25 } for MD5, TLS and PEAP. Default: TLS, TTLS, PEAP, MSCHAP.
|
||||
-- @args interface Network interface to use for the scan, overrides "-e".
|
||||
-- @args timeout Maximum time allowed for the scan, in seconds. Methods not tested because of timeout will be listed as "unknown".
|
||||
|
||||
author = "Riccardo Cecolin"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = { "broadcast", "safe" }
|
||||
|
||||
require 'nmap'
|
||||
require 'packet'
|
||||
require 'bin'
|
||||
require 'stdnse'
|
||||
require 'eap'
|
||||
|
||||
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.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.print_debug(1, "iface: %s", iface.device)
|
||||
|
||||
timeout = 10 * 1000
|
||||
if arg_timeout then
|
||||
timeout = arg_timeout * 1000
|
||||
end
|
||||
|
||||
stdnse.print_debug(2, "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.print_debug(1, "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.print_debug(2, "packet size: 0x%x", plen )
|
||||
local packet = eap.parse(l2_data .. l3_data)
|
||||
|
||||
if packet then
|
||||
stdnse.print_debug(2, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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) .. bin.pack("C",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.print_debug(1, "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.print_debug(1, "%s", tostring(v))
|
||||
end
|
||||
|
||||
return stdnse.format_output(true, results)
|
||||
end
|
||||
|
||||
|
|
@ -77,6 +77,7 @@ Entry { filename = "domino-enum-users.nse", categories = { "auth", "intrusive",
|
|||
Entry { filename = "dpap-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "drda-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "drda-info.nse", categories = { "discovery", "safe", "version", } }
|
||||
Entry { filename = "eap-info.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "epmd-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "finger.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "firewalk.nse", categories = { "discovery", "safe", } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue