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:
159
nselib/http.lua
159
nselib/http.lua
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user