1
0
mirror of https://github.com/nmap/nmap.git synced 2026-02-08 06:26:33 +00:00

Modified smbauth.lua to create ntlmv2 session response.

http.lua now allows NTLM authentication.
This commit is contained in:
gyani
2015-07-04 08:19:17 +00:00
parent 8d9f304fbd
commit 3d2a008cef
2 changed files with 180 additions and 7 deletions

View File

@@ -73,6 +73,7 @@
-- Only <code>name</code> and <code>value</code> fields are required.
-- * <code>auth</code>: A table containing the keys <code>username</code> and <code>password</code>, which will be used for HTTP Basic authentication.
-- If a server requires HTTP Digest authentication, then there must also be a key <code>digest</code>, with value <code>true</code>.
-- If a server requires NTLM authentication, then there must also be a key <code>ntlm</code>, with value <code>true</code>.
-- * <code>bypass_cache</code>: Do not perform a lookup in the local HTTP cache.
-- * <code>no_cache</code>: Do not save the result of this request to the local HTTP cache.
-- * <code>no_cache_body</code>: Do not save the body of the response to the local HTTP cache.
@@ -109,6 +110,8 @@
local base64 = require "base64"
local bin = require "bin"
local bit = require "bit"
local comm = require "comm"
local coroutine = require "coroutine"
local nmap = require "nmap"
@@ -118,6 +121,8 @@ local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local url = require "url"
local smbauth = require "smbauth"
local unicode = require "unicode"
_ENV = stdnse.module("http", stdnse.seeall)
---Use ssl if we have it
@@ -338,6 +343,8 @@ local function validate_options(options)
bad = true
stdnse.debug1("http: options.digestauth should be a table")
end
elseif (key == 'ntlmauth') then
stdnse.debug1("Proceeding with ntlm message")
elseif(key == 'bypass_cache' or key == 'no_cache' or key == 'no_cache_body') then
if(type(value) ~= 'boolean') then
stdnse.debug1("http: options.bypass_cache, options.no_cache, and options.no_cache_body must be boolean values")
@@ -1116,7 +1123,7 @@ local function build_request(host, port, method, path, options)
end
end
if options.auth and not options.auth.digest then
if options.auth and not (options.auth.digest or options.auth.ntlm) then
local username = options.auth.username
local password = options.auth.password
local credentials = "Basic " .. base64.enc(username .. ":" .. password)
@@ -1145,6 +1152,11 @@ local function build_request(host, port, method, path, options)
mod_options.header["Authorization"] = credentials
end
if options.ntlmauth then
mod_options.header["Authorization"] = "NTLM " .. base64.enc(options.ntlmauth)
end
local body
-- Build a form submission from a table, like "k1=v1&k2=v2".
if type(options.content) == "table" then
@@ -1255,8 +1267,9 @@ function generic_request(host, port, method, path, options)
end
local digest_auth = options and options.auth and options.auth.digest
local ntlm_auth = options and options.auth and options.auth.ntlm
if digest_auth and not have_ssl then
if (digest_auth or ntlm_auth) and not have_ssl then
stdnse.debug1("http: digest auth requires openssl.")
end
@@ -1277,6 +1290,148 @@ function generic_request(host, port, method, path, options)
options.digestauth = digest_table
end
if ntlm_auth and have_ssl then
local custom_options = tcopy(options) -- to be sent with the type 1 request
custom_options["auth"] = nil -- removing the auth options
-- let's check if the target supports ntlm with a simple get request.
-- Setting a timeout here other than nil messes up the authentication if this is the first device sending
-- a request to the server. Don't know why.
custom_options.timeout = nil
local response = generic_request(host, port, method, path, custom_options)
local authentication_header = response.header['www-authenticate']
-- get back the timeout option.
custom_options.timeout = options.timeout
custom_options.header = options.header or {}
custom_options.header["Connection"] = "Keep-Alive" -- Keep-Alive headers are needed for authentication.
if (not authentication_header) or (not response.status) or (not string.find(authentication_header:lower(), "ntlm")) then
stdnse.debug1("http: the target doesn't support NTLM or there was an error during request.")
return http_error("The target doesn't support NTLM or there was an error during request.")
end
-- ntlm works with three messages. we send a request, it sends
-- a challenge, we respond to the challenge.
local hostname = options.auth.hostname or "localhost" -- the hostname to be sent
local workstation_name = options.auth.workstation_name or "NMAP" -- the workstation name to be sent
local username = options.auth.username -- the username as specified
local auth_blob = "NTLMSSP\x00" .. -- NTLM signature
"\x01\x00\x00\x00" .. -- NTLM Type 1 message
bin.pack("<I", 0xa208b207) .. -- flags 56, 128, Version, Extended Security, Always Sign, Workstation supplied, Domain Supplied, NTLM Key, OEM, Unicode
bin.pack("<SSISSI",#workstation_name, #workstation_name, 40 + #hostname, #hostname, #hostname, 40) .. -- Supplied Domain and Workstation
bin.pack("CC<S", -- OS version info
5, 1, 2600) .. -- 5.1.2600
"\x00\x00\x00\x0f" .. -- OS version info end (static 0x0000000f)
hostname.. -- HOST NAME
workstation_name --WORKSTATION name
custom_options.ntlmauth = auth_blob
-- check if the protocol is tcp
if type(port) == 'table' then
if port.protocol and port.protocol ~= 'tcp' then
stdnse.debug1("NTLM authentication supports the TCP protocol only, your request to %s cannot be completed.", host)
return http_error("Unsupported protocol.")
end
end
-- tryssl uses ssl if needed. sends the type 1 message.
local socket, partial, opts = comm.tryssl(host, port, build_request(host, port, method, path, custom_options), { timeout = options.timeout })
if not socket then
return http_error("Could not create socket to send type 1 message.")
end
repeat
response, partial = next_response(socket, method, partial)
if not response then
return http_error("There was error in receiving response of type 1 message.")
end
until not (response.status >= 100 and response.status <= 199)
authentication_header = response.header['www-authenticate']
-- take out the challenge
local type2_response = authentication_header:sub(authentication_header:find(' ')+1, -1)
local _, _, message_type, _, _, _, flags_received, challenge= bin.unpack("<A8ISSIIA8", base64.dec(type2_response))
-- check if the response is a type 2 message.
if message_type ~= 0x02 then
stdnse.debug1("Expected type 2 message as response.")
return
end
local is_unicode = (bit.band(flags_received, 0x00000001) == 0x00000001) -- 0x00000001 UNICODE Flag
local is_extended = (bit.band(flags_received, 0x00080000) == 0x00080000) -- 0x00080000 Extended Security Flag
local type_3_flags = 0xa2888206 -- flags 56, 128, Version, Target Info, Extended Security, Always Sign, NTLM Key, OEM
local lanman, ntlm
if is_extended then
-- this essentially calls the new ntlmv2_session_response function in smbauth.lua and returns whatever it returns
lanman, ntlm = smbauth.get_password_response(nil, username, "", options.auth.password, nil, "ntlmv2_session", challenge, true)
else
lanman, ntlm = smbauth.get_password_response(nil, username, "", options.auth.password, nil, "ntlm", challenge, false)
type_3_flags = type_3_flags - 0x00080000 -- Removing the Extended Security Flag as server doesn't support it.
end
local domain = ""
local session_key = ""
-- if server supports unicode, then strings are sent in unicode format.
if is_unicode then
username = unicode.utf8to16(username)
hostname = unicode.utf8to16(hostname)
type_3_flags = type_3_flags - 0x00000001 -- OEM flag is 0x00000002. removing 0x00000001 results in UNICODE flag.
end
local BASE_OFFSET = 72 -- Version 3 -- The Session Key<empty in our case>, flags, and OS Version structure are all present.
auth_blob = bin.pack("<zISSISSISSISSISSISSIICCSAAAAA",
"NTLMSSP",
0x00000003,
#lanman,
#lanman,
BASE_OFFSET + #username + #hostname,
( #ntlm ),
( #ntlm ),
BASE_OFFSET + #username + #hostname + #lanman,
#domain,
#domain,
BASE_OFFSET,
#username,
#username,
BASE_OFFSET,
#hostname,
#hostname,
BASE_OFFSET + #username,
#session_key,
#session_key,
BASE_OFFSET + #username + #hostname + #lanman + #ntlm,
type_3_flags,
5,
1,
2600,
"\x00\x00\x00\x0f",
username,
hostname,
lanman,
ntlm)
custom_options.ntlmauth = auth_blob
socket:send(build_request(host, port, method, path, custom_options))
repeat
response, partial = next_response(socket, method, partial)
if not response then
return http_error("There was error in receiving response of type 3 message.")
end
until not (response.status >= 100 and response.status <= 199)
socket:close()
response.ssl = ( opts == 'ssl' )
return response
end
return request(host, port, build_request(host, port, method, path, options), options)
end