o [NSE] Added an EAP library and the script eap-info which discovers supported

EAP authentication methods. [Riccardo Cecolin]
This commit is contained in:
patrik 2012-03-08 18:00:35 +00:00
parent cde380ea2c
commit 1384a953a1
4 changed files with 485 additions and 0 deletions

View file

@ -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
View 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
View 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

View file

@ -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", } }