mirror of
https://github.com/nmap/nmap.git
synced 2025-12-15 20:29:03 +00:00
Make the http NSE module better handle different delimiters in the headers of
messages (and in the body when chunked encoding is used). The patch is from jah.
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o The http NSE module tries to deal with non-standards-compliant HTTP
|
||||||
|
traffic, particularly responses in which the header fields are
|
||||||
|
separated by plain LF rather than CRLF. [Jah]
|
||||||
|
|
||||||
o [Zenmap] The help function now properly converts the pathname of the
|
o [Zenmap] The help function now properly converts the pathname of the
|
||||||
local help file to a URL, for better compatibility with different
|
local help file to a URL, for better compatibility with different
|
||||||
web browsers. [David]
|
web browsers. [David]
|
||||||
|
|||||||
@@ -1587,16 +1587,19 @@ if(s) code_to_be_done_on_match end
|
|||||||
The <literal>http</literal> module provides functions for dealing with the client side of the http protocol.
|
The <literal>http</literal> module provides functions for dealing with the client side of the http protocol.
|
||||||
The functions reside inside the <literal>http</literal> namespace.
|
The functions reside inside the <literal>http</literal> namespace.
|
||||||
The return value of each function in this module is a table with the following keys:
|
The return value of each function in this module is a table with the following keys:
|
||||||
<literal>status</literal>, <literal>header</literal> and <literal>body</literal>.
|
<literal>status</literal>, <literal>status-line</literal>, <literal>header</literal>
|
||||||
|
and <literal>body</literal>.
|
||||||
|
|
||||||
<literal>status</literal> is a number representing the HTTP
|
<literal>status</literal> is a number representing the HTTP
|
||||||
status code returned in response to the HTTP request. In case
|
status code returned in response to the HTTP request. In case
|
||||||
of an unhandled error, <literal>status</literal>
|
of an unhandled error, <literal>status</literal>
|
||||||
is <literal>nil</literal>. The <literal>header</literal> value
|
is <literal>nil</literal>. <literal>status-line</literal> is
|
||||||
is a table containing key-value pairs of HTTP headers received
|
the entire status message which includes the HTTP version,
|
||||||
in response to the request. The header names are in lower-case
|
status code and reason phrase. The <literal>header</literal>
|
||||||
and are the keys to their corresponding header values
|
value is a table containing key-value pairs of HTTP headers
|
||||||
(e.g. <literal>header.location =
|
received in response to the request. The header names are in
|
||||||
|
lower-case and are the keys to their corresponding header
|
||||||
|
values (e.g. <literal>header.location =
|
||||||
"http://nmap.org/"</literal>). Multiple headers of the same
|
"http://nmap.org/"</literal>). Multiple headers of the same
|
||||||
name are concatenated and separated by
|
name are concatenated and separated by
|
||||||
commas. The <literal>body</literal> value is a string
|
commas. The <literal>body</literal> value is a string
|
||||||
|
|||||||
137
nselib/http.lua
137
nselib/http.lua
@@ -1,12 +1,14 @@
|
|||||||
--- The http module provides functions for dealing with the client side
|
--- The http module provides functions for dealing with the client side
|
||||||
-- of the http protocol. The functions reside inside the http namespace.
|
-- of the http protocol. The functions reside inside the http namespace.
|
||||||
-- The return value of each function in this module is a table with the
|
-- The return value of each function in this module is a table with the
|
||||||
-- following keys: status, header and body. status is a number representing
|
-- following keys: status, status-line, header and body. status is a number
|
||||||
-- the HTTP status code returned in response to the HTTP request. In case
|
-- representing the HTTP status code returned in response to the HTTP
|
||||||
-- of an unhandled error, status is nil. The header value is a table
|
-- request. In case of an unhandled error, status is nil. status-line is
|
||||||
-- containing key-value pairs of HTTP headers received in response to the
|
-- the entire status message which includes the HTTP version, status code
|
||||||
-- request. The header names are in lower-case and are the keys to their
|
-- and reason phrase. The header value is a table containing key-value
|
||||||
-- corresponding header values (e.g. header.location = "http://nmap.org/").
|
-- pairs of HTTP headers received in response to the request. The header
|
||||||
|
-- names are in lower-case and are the keys to their corresponding header
|
||||||
|
-- values (e.g. header.location = "http://nmap.org/").
|
||||||
-- Multiple headers of the same name are concatenated and separated by
|
-- Multiple headers of the same name are concatenated and separated by
|
||||||
-- commas. The body value is a string containing the body of the HTTP
|
-- commas. The body value is a string containing the body of the HTTP
|
||||||
-- response.
|
-- response.
|
||||||
@@ -14,8 +16,8 @@
|
|||||||
|
|
||||||
module(... or "http",package.seeall)
|
module(... or "http",package.seeall)
|
||||||
|
|
||||||
require 'stdnse'
|
local url = require 'url'
|
||||||
require 'url'
|
local stdnse = require 'stdnse'
|
||||||
|
|
||||||
--
|
--
|
||||||
-- http.get( host, port, path, options )
|
-- http.get( host, port, path, options )
|
||||||
@@ -26,7 +28,7 @@ require 'url'
|
|||||||
-- port may either be a number or a table
|
-- port may either be a number or a table
|
||||||
--
|
--
|
||||||
-- the format of the return value is a table with the following structure:
|
-- the format of the return value is a table with the following structure:
|
||||||
-- {status = 200, header = {}, body ="<html>...</html>"}
|
-- {status = 200, status-line = "HTTP/1.1 200 OK", header = {}, body ="<html>...</html>"}
|
||||||
-- the header table has an entry for each received header with the header name being the key
|
-- the header table has an entry for each received header with the header name being the key
|
||||||
-- the table also has an entry named "status" which contains the http status code of the request
|
-- the table also has an entry named "status" which contains the http status code of the request
|
||||||
-- in case of an error status is nil
|
-- in case of an error status is nil
|
||||||
@@ -43,7 +45,7 @@ require 'url'
|
|||||||
-- @param host The host to query.
|
-- @param host The host to query.
|
||||||
-- @param port The port for the host.
|
-- @param port The port for the host.
|
||||||
-- @param path The path of the resource.
|
-- @param path The path of the resource.
|
||||||
-- @param options A table of optoins. See function description.
|
-- @param options A table of options. See function description.
|
||||||
-- @return table
|
-- @return table
|
||||||
get = function( host, port, path, options )
|
get = function( host, port, path, options )
|
||||||
options = options or {}
|
options = options or {}
|
||||||
@@ -113,7 +115,7 @@ request = function( host, port, data, options )
|
|||||||
options = options or {}
|
options = options or {}
|
||||||
|
|
||||||
if type(host) == 'table' then
|
if type(host) == 'table' then
|
||||||
host = host.ip
|
host = host.targetname or host.ip
|
||||||
end
|
end
|
||||||
|
|
||||||
local protocol = 'tcp'
|
local protocol = 'tcp'
|
||||||
@@ -124,7 +126,7 @@ request = function( host, port, data, options )
|
|||||||
port = port.number
|
port = port.number
|
||||||
end
|
end
|
||||||
|
|
||||||
local result = {status=nil,header={},body=""}
|
local result = {status=nil,["status-line"]=nil,header={},body=""}
|
||||||
local socket = nmap.new_socket()
|
local socket = nmap.new_socket()
|
||||||
local default_timeout = {}
|
local default_timeout = {}
|
||||||
if options.timeout then
|
if options.timeout then
|
||||||
@@ -133,36 +135,76 @@ request = function( host, port, data, options )
|
|||||||
default_timeout = get_default_timeout( nmap.timing_level() )
|
default_timeout = get_default_timeout( nmap.timing_level() )
|
||||||
socket:set_timeout( default_timeout.connect )
|
socket:set_timeout( default_timeout.connect )
|
||||||
end
|
end
|
||||||
|
|
||||||
if not socket:connect( host, port, protocol ) then
|
if not socket:connect( host, port, protocol ) then
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
if not options.timeout then
|
if not options.timeout then
|
||||||
socket:set_timeout( default_timeout.request )
|
socket:set_timeout( default_timeout.request )
|
||||||
end
|
end
|
||||||
|
|
||||||
if not socket:send( data ) then
|
if not socket:send( data ) then
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local buffer = stdnse.make_buffer( socket, "\r\n" )
|
-- no buffer - we want everything now!
|
||||||
|
local response = {}
|
||||||
|
while true do
|
||||||
|
local status, part = socket:receive()
|
||||||
|
if not status then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
response[#response+1] = part
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
socket:close()
|
||||||
|
|
||||||
|
response = table.concat( response )
|
||||||
|
|
||||||
|
-- try and separate the head from the body
|
||||||
|
local header, body, h1, h2, b1, b2
|
||||||
|
if response:match( "\r\n\r\n" ) and response:match( "\n\n" ) then
|
||||||
|
h1, b1 = response:match( "^(.-)\r\n\r\n(.*)$" )
|
||||||
|
h2, b2 = response:match( "^(.-)\n\n(.*)$" )
|
||||||
|
if h1 and h2 and h1:len() <= h2:len() then
|
||||||
|
header, body = h1, b1
|
||||||
|
else
|
||||||
|
header, body = h2, b2
|
||||||
|
end
|
||||||
|
elseif response:match( "\r\n\r\n" ) then
|
||||||
|
header, body = response:match( "^(.-)\r\n\r\n(.*)$" )
|
||||||
|
elseif response:match( "\n\r\n" ) then
|
||||||
|
header, body = response:match( "^(.-)\n\r\n(.*)$" )
|
||||||
|
elseif response:match( "\n\n" ) then
|
||||||
|
header, body = response:match( "^(.-)\n\n(.*)$" )
|
||||||
|
else
|
||||||
|
body = response
|
||||||
|
end
|
||||||
|
|
||||||
|
local head_delim, body_delim
|
||||||
|
if type( header ) == "string" then
|
||||||
|
head_delim = ( header:match( "\r\n" ) and "\r\n" ) or
|
||||||
|
( header:match( "\n" ) and "\n" ) or nil
|
||||||
|
header = ( head_delim and stdnse.strsplit( head_delim, header ) ) or { header }
|
||||||
|
end
|
||||||
|
|
||||||
|
if type( body ) == "string" then
|
||||||
|
body_delim = ( body:match( "\r\n" ) and "\r\n" ) or
|
||||||
|
( body:match( "\n" ) and "\n" ) or nil
|
||||||
|
end
|
||||||
|
|
||||||
local line, _
|
local line, _
|
||||||
local header, body = {}, {}
|
|
||||||
|
|
||||||
-- header loop
|
|
||||||
while true do
|
|
||||||
line = buffer()
|
|
||||||
if (not line or line == "") then break end
|
|
||||||
table.insert(header,line)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- build nicer table for header
|
-- build nicer table for header
|
||||||
local last_header, match
|
local last_header, match
|
||||||
for number, line in ipairs( header ) do
|
for number, line in ipairs( header or {} ) do
|
||||||
if number == 1 then
|
if number == 1 then
|
||||||
local code
|
local code
|
||||||
_, _, code = string.find( line, "HTTP/%d\.%d (%d+)")
|
_, _, code = string.find( line, "HTTP/%d\.%d (%d+)")
|
||||||
result.status = tonumber(code)
|
result.status = tonumber(code)
|
||||||
if not result.status then table.insert(body,line) end
|
if code then result["status-line"] = line end
|
||||||
else
|
else
|
||||||
match, _, key, value = string.find( line, "(.+): (.*)" )
|
match, _, key, value = string.find( line, "(.+): (.*)" )
|
||||||
if match and key and value then
|
if match and key and value then
|
||||||
@@ -177,39 +219,42 @@ request = function( host, port, data, options )
|
|||||||
match, _, value = string.find( line, " +(.*)" )
|
match, _, value = string.find( line, " +(.*)" )
|
||||||
if match and value and last_header then
|
if match and value and last_header then
|
||||||
result.header[last_header] = result.header[last_header] .. ',' .. value
|
result.header[last_header] = result.header[last_header] .. ',' .. value
|
||||||
elseif match and value then
|
|
||||||
table.insert(body,line)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle body
|
-- handle chunked encoding
|
||||||
if result.header['transfer-encoding'] == 'chunked' then
|
if type( result.header ) == "table" and result.header['transfer-encoding'] == 'chunked' and type( body_delim ) == "string" then
|
||||||
-- if the server used chunked encoding we have to 'dechunk' the answer
|
body = body_delim .. body
|
||||||
local counter, chunk_size
|
local b = {}
|
||||||
counter = 0; chunk_size = 0
|
local start, ptr = 1, 1
|
||||||
while true do
|
local chunk_len
|
||||||
if counter >= chunk_size then
|
local pattern = ("%s([^%s]+)%s"):format( body_delim, body_delim, body_delim )
|
||||||
counter = 0
|
while ( ptr < ( type( body ) == "string" and body:len() ) or 1 ) do
|
||||||
chunk_size = tonumber( buffer(), 16 )
|
local hex = body:match( pattern, ptr )
|
||||||
if chunk_size == 0 or not chunk_size then break end
|
if not hex then break end
|
||||||
|
chunk_len = tonumber( hex or 0, 16 ) or nil
|
||||||
|
if chunk_len then
|
||||||
|
start = ptr + hex:len() + 2*body_delim:len()
|
||||||
|
ptr = start + chunk_len
|
||||||
|
b[#b+1] = body:sub( start, ptr-1 )
|
||||||
end
|
end
|
||||||
line = buffer()
|
|
||||||
if not line then break end
|
|
||||||
counter = counter + #line + 2
|
|
||||||
table.insert(body,line)
|
|
||||||
end
|
end
|
||||||
else
|
body = table.concat( b )
|
||||||
while true do
|
end
|
||||||
line = buffer()
|
|
||||||
if not line then break end
|
-- special case for conjoined header and body
|
||||||
table.insert(body,line)
|
if type( result.status ) ~= "number" and type( body ) == "string" then
|
||||||
|
local code, remainder = body:match( "HTTP/%d\.%d (%d+)(.*)") -- The Reason-Phrase will be prepended to the body :(
|
||||||
|
if code then
|
||||||
|
stdnse.print_debug( "Interesting variation on the HTTP standard. Please submit a --script-trace output for this host (%s) to nmap-dev[at]insecure.org.", host )
|
||||||
|
result.status = tonumber(code)
|
||||||
|
body = remainder or body
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
socket:close()
|
result.body = body
|
||||||
result.body = table.concat( body, "\r\n" )
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user