mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
o [NSE] Added a new iSCSI library and the two scripts iscsi-info and
iscsi-brute. [Patrik]
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added a new iSCSI library and the two scripts iscsi-info and
|
||||
iscsi-brute. [Patrik]
|
||||
|
||||
o [NSE] Add new script broadcast-ms-sql-discover and removed broadcast
|
||||
support from ms-sql-info. [Patrik]
|
||||
|
||||
|
||||
790
nselib/iscsi.lua
Normal file
790
nselib/iscsi.lua
Normal file
@@ -0,0 +1,790 @@
|
||||
--- An iSCSI library implementing written by Patrik Karlsson <patrik@cqure.net>
|
||||
-- The library currently supports target discovery and login.
|
||||
--
|
||||
-- The implementation is based on packetdumps and the iSCSI RFC
|
||||
-- * http://tools.ietf.org/html/rfc3720
|
||||
--
|
||||
-- The library contains the protocol message pairs in <code>Packet</code>
|
||||
-- E.g. <code>LoginRequest</code> and <code>LoginResponse</code>
|
||||
--
|
||||
-- Each request can be "serialized" to a string using:
|
||||
-- <code>tostring(request)</code>.
|
||||
-- All responses can be read and instantiated from the socket by calling:
|
||||
-- <code>local status,resp = Response.fromSocket(sock)</code>
|
||||
--
|
||||
-- In addition the library has the following classes:
|
||||
-- * <code>Packet</code>
|
||||
-- ** A class containing the request and response packets
|
||||
-- * <code>Comm</code>
|
||||
-- ** A class used to send and receive packet between the library and server
|
||||
-- ** The class handles some of the packet "counting" and value updating
|
||||
-- * <code>Socket</code>
|
||||
-- ** A buffered socket class that allows reading of exakt number of bytes
|
||||
-- * <code>KVP</code>
|
||||
-- ** A key/value pair class that holds key value pairs
|
||||
-- * <code>Helper</code>
|
||||
-- ** A class that wraps the <code>Comm</code> and <code>Packet</code> classes
|
||||
-- ** The purpose of the class is to provide easy access to common iSCSI task
|
||||
--
|
||||
--
|
||||
-- @author "Patrik Karlsson <patrik@cqure.net>"
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
|
||||
-- Version 0.2
|
||||
-- Created 2010/11/18 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
-- Revised 2010/11/28 - v0.2 - improved error handling, fixed discovery issues
|
||||
-- with multiple addresses <patrik@cqure.net>
|
||||
|
||||
module(... or "iscsi",package.seeall)
|
||||
|
||||
require("bin")
|
||||
require("ipOps")
|
||||
|
||||
Packet = {
|
||||
|
||||
Opcode = {
|
||||
LOGIN = 0x03,
|
||||
TEXT = 0x04,
|
||||
LOGOUT = 0x06,
|
||||
},
|
||||
|
||||
LoginRequest = {
|
||||
|
||||
CSG = {
|
||||
SecurityNegotiation = 0,
|
||||
LoginOperationalNegotiation = 1,
|
||||
FullFeaturePhase = 3,
|
||||
},
|
||||
|
||||
NSG = {
|
||||
SecurityNegotiation = 0,
|
||||
LoginOperationalNegotiation = 1,
|
||||
FullFeaturePhase = 3,
|
||||
},
|
||||
|
||||
--- Creates a new instance of LoginRequest
|
||||
--
|
||||
-- @return instance of LoginRequest
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.immediate = 0
|
||||
o.opcode = Packet.Opcode.LOGIN
|
||||
o.flags = {}
|
||||
o.ver_max = 0
|
||||
o.ver_min = 0
|
||||
o.total_ahs_len = 0
|
||||
o.data_seg_len = 0
|
||||
o.isid = { t=0x01, a=0x00, b=0x0001, c=0x37, d=0 }
|
||||
o.tsih = 0
|
||||
o.initiator_task_tag = 1
|
||||
o.cid = 1
|
||||
o.cmdsn = 0
|
||||
o.expstatsn = 1
|
||||
o.kvp = KVP:new()
|
||||
return o
|
||||
end,
|
||||
|
||||
setImmediate = function(self, b) self.immediate = ( b and 1 or 0 ) end,
|
||||
|
||||
--- Sets the transit bit
|
||||
--
|
||||
-- @param b boolean containing the new transit value
|
||||
setTransit = function(self, b) self.flags.transit = ( b and 1 or 0 ) end,
|
||||
|
||||
--- Sets the continue bit
|
||||
--
|
||||
-- @param b boolean containing the new continue value
|
||||
setContinue = function(self, b) self.flags.continue = ( b and 1 or 0 ) end,
|
||||
|
||||
--- Sets the CSG values
|
||||
--
|
||||
-- @param csg number containing the new NSG value
|
||||
setCSG = function(self, csg) self.flags.csg = csg end,
|
||||
|
||||
--- Sets the NSG values
|
||||
--
|
||||
-- @param nsg number containing the new NSG value
|
||||
setNSG = function(self, nsg) self.flags.nsg = nsg end,
|
||||
|
||||
--- Converts the class instance to string
|
||||
--
|
||||
-- @return string containing the converted instance
|
||||
__tostring = function( self )
|
||||
local reserved = 0
|
||||
local kvps = tostring(self.kvp)
|
||||
|
||||
self.data_seg_len = #kvps
|
||||
|
||||
local pad = 4 - ((#kvps + 48) % 4)
|
||||
pad = ( pad == 4 ) and 0 or pad
|
||||
|
||||
for i=1, pad do kvps = kvps .. "\0" end
|
||||
|
||||
local len = bit.lshift( self.total_ahs_len, 24 ) + self.data_seg_len
|
||||
local flags = bit.lshift( ( self.flags.transit or 0 ), 7 )
|
||||
flags = flags + bit.lshift( ( self.flags.continue or 0 ), 6)
|
||||
flags = flags + ( self.flags.nsg or 0 )
|
||||
flags = flags + bit.lshift( ( self.flags.csg or 0 ), 2 )
|
||||
|
||||
local opcode = self.opcode + bit.lshift((self.immediate or 0), 6)
|
||||
|
||||
local data = bin.pack(">CCCCICSCSSISSIILLA", opcode,
|
||||
flags, self.ver_max, self.ver_min, len,
|
||||
bit.lshift( self.isid.t, 6 ) + bit.band( self.isid.a, 0x3f),
|
||||
self.isid.b, self.isid.c, self.isid.d, self.tsih,
|
||||
self.initiator_task_tag, self.cid, reserved, self.cmdsn,
|
||||
self.expstatsn, reserved, reserved, kvps )
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
},
|
||||
|
||||
LoginResponse = {
|
||||
|
||||
-- Error messages
|
||||
ErrorMsgs = {
|
||||
[0x0000] = "Success",
|
||||
[0x0101] = "Target moved temporarily",
|
||||
[0x0102] = "Target moved permanently",
|
||||
[0x0200] = "Initiator error",
|
||||
[0x0201] = "Authentication failure",
|
||||
[0x0202] = "Authorization failure",
|
||||
[0x0203] = "Target not found",
|
||||
[0x0204] = "Target removed",
|
||||
[0x0205] = "Unsupported version",
|
||||
[0x0206] = "Too many connections",
|
||||
[0x0207] = "Missing parameter",
|
||||
[0x0208] = "Can't include in session",
|
||||
[0x0209] = "Session type not supported",
|
||||
[0x020a] = "Session does not exist",
|
||||
[0x020b] = "Invalid request during login",
|
||||
[0x0300] = "Target error",
|
||||
[0x0301] = "Service unavailable",
|
||||
[0x0302] = "Out of resources",
|
||||
},
|
||||
|
||||
-- Error constants
|
||||
Errors = {
|
||||
SUCCESS = 0,
|
||||
AUTH_FAILED = 0x0201,
|
||||
},
|
||||
|
||||
--- Creates a new instance of LoginResponse
|
||||
--
|
||||
-- @return instance of LoginResponse
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Returns the error message
|
||||
getErrorMessage = function( self )
|
||||
return Packet.LoginResponse.ErrorMsgs[self.status_code] or "Unknown error"
|
||||
end,
|
||||
|
||||
--- Returns the error code
|
||||
getErrorCode = function( self ) return self.status_code or 0 end,
|
||||
|
||||
--- Creates a LoginResponse with data read from the socket
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return resp instance of LoginResponse
|
||||
fromSocket = function( s )
|
||||
local status, header = s:recv(48)
|
||||
|
||||
if ( not(status) ) then
|
||||
return false, "Failed to read header from socket"
|
||||
end
|
||||
|
||||
local resp = Packet.LoginResponse:new()
|
||||
local pos, len = bin.unpack(">I", header, 5)
|
||||
|
||||
resp.total_ahs_len = bit.rshift(len, 24)
|
||||
resp.data_seg_len = bit.band(len, 0x00ffffff)
|
||||
pos, resp.status_code = bin.unpack(">S", header, 37)
|
||||
|
||||
local pad = ( 4 - ( resp.data_seg_len % 4 ) )
|
||||
pad = ( pad == 4 ) and 0 or pad
|
||||
|
||||
local status, data = s:recv( resp.data_seg_len + pad )
|
||||
if ( not(status) ) then
|
||||
return false, "Failed to read data from socket"
|
||||
end
|
||||
|
||||
resp.kvp = KVP:new()
|
||||
for _, kvp in ipairs(stdnse.strsplit( "\0", data )) do
|
||||
local k, v = kvp:match("(.*)=(.*)")
|
||||
if ( v ) then resp.kvp:add( k, v ) end
|
||||
end
|
||||
|
||||
return true, resp
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
TextRequest = {
|
||||
|
||||
--- Creates a new instance of TextRequest
|
||||
--
|
||||
-- @return instance of TextRequest
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.opcode = Packet.Opcode.TEXT
|
||||
o.flags = {}
|
||||
o.flags.final = 0
|
||||
o.flags.continue = 0
|
||||
o.total_ahs_len = 0
|
||||
o.data_seg_len = 0
|
||||
o.lun = 0
|
||||
o.initiator_task_tag = 1
|
||||
o.target_trans_tag = 0xffffffff
|
||||
o.cmdsn = 2
|
||||
o.expstatsn = 1
|
||||
o.kvp = KVP:new()
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Sets the final bit of the TextRequest
|
||||
setFinal = function( self, b ) self.flags.final = ( b and 1 or 0 ) end,
|
||||
|
||||
--- Sets the continue bit of the TextRequest
|
||||
setContinue = function( self, b ) self.flags.continue = ( b and 1 or 0 ) end,
|
||||
|
||||
--- Converts the class instance to string
|
||||
--
|
||||
-- @return string containing the converted instance
|
||||
__tostring = function(self)
|
||||
local flags = bit.lshift( ( self.flags.final or 0 ), 7 )
|
||||
flags = flags + bit.lshift( (self.flags.continue or 0), 6 )
|
||||
|
||||
local kvps = tostring(self.kvp)
|
||||
for i=1, (#kvps % 2) do kvps = kvps .. "\0" end
|
||||
self.data_seg_len = #kvps
|
||||
|
||||
local len = bit.lshift( self.total_ahs_len, 24 ) + self.data_seg_len
|
||||
local reserved = 0
|
||||
local data = bin.pack(">CCSILIIIILLA", self.opcode, flags, reserved,
|
||||
len, self.lun, self.initiator_task_tag, self.target_trans_tag,
|
||||
self.cmdsn, self.expstatsn, reserved, reserved, kvps)
|
||||
|
||||
return data
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
TextResponse = {
|
||||
|
||||
--- Creates a new instance of TextResponse
|
||||
--
|
||||
-- @return instance of TextResponse
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Creates a TextResponse with data read from the socket
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return instance of TextResponse
|
||||
-- err string containing error message
|
||||
fromSocket = function( s )
|
||||
local resp = Packet.TextResponse:new()
|
||||
local textdata = ""
|
||||
|
||||
repeat
|
||||
local status, header = s:recv(48)
|
||||
local pos, _, flags, _, _, len = bin.unpack(">CCCCI", header)
|
||||
local cont = ( bit.band(flags, 0x40) == 0x40 )
|
||||
|
||||
resp.total_ahs_len = bit.rshift(len, 24)
|
||||
resp.data_seg_len = bit.band(len, 0x00ffffff)
|
||||
|
||||
local data
|
||||
status, data = s:recv( resp.data_seg_len )
|
||||
|
||||
textdata = textdata .. data
|
||||
|
||||
until( not(cont) )
|
||||
|
||||
resp.records = {}
|
||||
|
||||
local kvps = stdnse.strsplit( "\0", textdata )
|
||||
local record
|
||||
|
||||
-- Each target record starts with one text key of the form:
|
||||
-- TargetName=<target-name-goes-here>
|
||||
-- Followed by zero or more address keys of the form:
|
||||
-- TargetAddress=<hostname-or-ipaddress>[:<tcp-port>],
|
||||
-- <portal-group-tag>
|
||||
for _, kvp in ipairs(kvps) do
|
||||
local k, v = kvp:match("(.*)%=(.*)")
|
||||
if ( k == "TargetName" ) then
|
||||
if ( record ) then
|
||||
table.insert(resp.records, record)
|
||||
record = {}
|
||||
end
|
||||
if ( #resp.records == 0 ) then record = {} end
|
||||
record.name = v
|
||||
elseif ( k == "TargetAddress" ) then
|
||||
record.addr = record.addr or {}
|
||||
table.insert( record.addr, v )
|
||||
elseif ( not(k) ) then
|
||||
-- this should be the ending empty kvp
|
||||
table.insert(resp.records, record)
|
||||
break
|
||||
else
|
||||
stdnse.print_debug("ERROR: iscsi.TextResponse: Unknown target record (%s)", k)
|
||||
end
|
||||
end
|
||||
|
||||
return true, resp
|
||||
end,
|
||||
},
|
||||
|
||||
--- Class handling a login request
|
||||
LogoutRequest = {
|
||||
|
||||
--- Creates a new instance of LogoutRequest
|
||||
--
|
||||
-- @return instance of LogoutRequest
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.opcode = Packet.Opcode.LOGOUT
|
||||
o.immediate = 1
|
||||
o.reasoncode = 0
|
||||
o.total_ahs_len = 0
|
||||
o.data_seg_len = 0
|
||||
o.initiator_task_tag = 2
|
||||
o.cid = 1
|
||||
o.cmdsn = 0
|
||||
o.expstatsn = 1
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Converts the class instance to string
|
||||
--
|
||||
-- @return string containing the converted instance
|
||||
__tostring = function(self)
|
||||
local opcode = self.opcode + bit.lshift((self.immediate or 0), 6)
|
||||
local reserved = 0
|
||||
local len = bit.lshift( self.total_ahs_len, 24 ) + self.data_seg_len
|
||||
local data = bin.pack(">CCSILISSIILL", opcode, (0x80 + self.reasoncode),
|
||||
reserved, len, reserved,self.initiator_task_tag, self.cid,
|
||||
reserved, self.cmdsn, self.expstatsn, reserved, reserved )
|
||||
|
||||
return data
|
||||
end,
|
||||
},
|
||||
|
||||
|
||||
--- Class handling the Logout response
|
||||
LogoutResponse = {
|
||||
|
||||
--- Creates a new instance of LogoutResponse
|
||||
--
|
||||
-- @return instance of LogoutResponse
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Creates a LogoutResponse with data read from the socket
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return instance of LogoutResponse
|
||||
-- err string containing error message
|
||||
fromSocket = function( s )
|
||||
local resp = Packet.LogoutResponse:new()
|
||||
local status, header = s:recv(48)
|
||||
if ( not(status) ) then return status, header end
|
||||
return true, resp
|
||||
end
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
--- The communication class handles socket reads and writes
|
||||
-- In addition it keeps track of both immediate packets and the amount of read
|
||||
-- packets and updates cmdsn and expstatsn accordingly.
|
||||
Comm = {
|
||||
|
||||
--- Creates a new instance of Comm
|
||||
--
|
||||
-- @return instance of Comm
|
||||
new = function(self, socket)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.expstatsn = 0
|
||||
o.cmdsn = 1
|
||||
o.socket = socket
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Sends a packet and retrieves the response
|
||||
--
|
||||
-- @param out_packet instance of a packet to send
|
||||
-- @param in_class class of the packet to read
|
||||
-- @return status true on success, false on failure
|
||||
-- @return r decoded instance of in_class
|
||||
exchange = function( self, out_packet, in_class )
|
||||
|
||||
local expstatsn = ( self.expstatsn == 0 ) and 1 or self.expstatsn
|
||||
|
||||
if ( out_packet.immediate and out_packet.immediate == 1 ) then
|
||||
self.cmdsn = self.cmdsn + 1
|
||||
end
|
||||
|
||||
out_packet.expstatsn = expstatsn
|
||||
out_packet.cmdsn = self.cmdsn
|
||||
|
||||
self.socket:send( tostring( out_packet ) )
|
||||
|
||||
local status, r = in_class.fromSocket( self.socket )
|
||||
self.expstatsn = self.expstatsn + 1
|
||||
|
||||
return status, r
|
||||
end,
|
||||
|
||||
|
||||
}
|
||||
|
||||
--- A buffered socket implementation
|
||||
Socket =
|
||||
{
|
||||
|
||||
--- Creates a new instance of Socket
|
||||
--
|
||||
-- @return instance of Socket
|
||||
new = function(self)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.Socket = nmap.new_socket()
|
||||
o.Buffer = nil
|
||||
return o
|
||||
end,
|
||||
|
||||
|
||||
--- Establishes a connection.
|
||||
--
|
||||
-- @param hostid Hostname or IP address.
|
||||
-- @param port Port number.
|
||||
-- @param protocol <code>"tcp"</code>, <code>"udp"</code>, or
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
connect = function( self, hostid, port, protocol )
|
||||
self.Socket:set_timeout(10000)
|
||||
return self.Socket:connect( hostid, port, protocol )
|
||||
end,
|
||||
|
||||
--- Closes an open connection.
|
||||
--
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
close = function( self )
|
||||
return self.Socket:close()
|
||||
end,
|
||||
|
||||
--- Opposed to the <code>socket:receive_bytes</code> function, that returns
|
||||
-- at least x bytes, this function returns the amount of bytes requested.
|
||||
--
|
||||
-- @param count of bytes to read
|
||||
-- @return true on success, false on failure
|
||||
-- @return data containing bytes read from the socket
|
||||
-- err containing error message if status is false
|
||||
recv = function( self, count )
|
||||
local status, data
|
||||
|
||||
self.Buffer = self.Buffer or ""
|
||||
|
||||
if ( #self.Buffer < count ) then
|
||||
status, data = self.Socket:receive_bytes( count - #self.Buffer )
|
||||
if ( not(status) ) then
|
||||
return false, data
|
||||
end
|
||||
self.Buffer = self.Buffer .. data
|
||||
end
|
||||
|
||||
data = self.Buffer:sub( 1, count )
|
||||
self.Buffer = self.Buffer:sub( count + 1)
|
||||
|
||||
return true, data
|
||||
end,
|
||||
|
||||
--- Sends data over the socket
|
||||
--
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
send = function( self, data )
|
||||
return self.Socket:send( data )
|
||||
end,
|
||||
}
|
||||
|
||||
--- Key/Value pairs class
|
||||
KVP = {
|
||||
|
||||
--- Creates a new instance of KVP
|
||||
--
|
||||
-- @return instance of KVP
|
||||
new = function( self )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.kvp = {}
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Adds a key/value pair
|
||||
--
|
||||
-- @param key string containing the key name
|
||||
-- @param value string containing the value
|
||||
add = function( self, key, value )
|
||||
table.insert( self.kvp, {[key]=value} )
|
||||
end,
|
||||
|
||||
--- Gets all values for a specific key
|
||||
--
|
||||
-- @param key string containing the name of the key to retrieve
|
||||
-- @return values table containing all values for the specified key
|
||||
get = function( self, key )
|
||||
local values = {}
|
||||
for _, kvp in ipairs(self.kvp) do
|
||||
for k, v in pairs( kvp ) do
|
||||
if ( key == k ) then
|
||||
table.insert( values, v )
|
||||
end
|
||||
end
|
||||
end
|
||||
return values
|
||||
end,
|
||||
|
||||
--- Returns all key value pairs as string delimited by \0
|
||||
-- eg. "key1=val1\0key2=val2\0"
|
||||
--
|
||||
-- @return string containing all key/value pairs
|
||||
__tostring = function( self )
|
||||
local ret = ""
|
||||
for _, kvp in ipairs(self.kvp) do
|
||||
for k, v in pairs( kvp ) do
|
||||
ret = ret .. ("%s=%s\0"):format(k,v)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
--- CHAP authentication class
|
||||
CHAP = {
|
||||
|
||||
--- Calculate a CHAP - response
|
||||
--
|
||||
-- @param identifier number containing the CHAP identifier
|
||||
-- @param challenge string containing the challenge
|
||||
-- @param secret string containing the users password
|
||||
-- @return response string containing the CHAP response
|
||||
calcResponse = function( identifier, challenge, secret )
|
||||
return openssl.md5( identifier .. secret .. challenge )
|
||||
end,
|
||||
|
||||
}
|
||||
|
||||
--- The helper class contains functions with more descriptive names
|
||||
Helper = {
|
||||
|
||||
--- Creates a new instance of the Helper class
|
||||
--
|
||||
-- @param host table as received by the script action function
|
||||
-- @param port table as received by the script action function
|
||||
-- @return o instance of Helper
|
||||
new = function( self, host, port )
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.host, o.port = host, port
|
||||
o.socket = Socket:new()
|
||||
return o
|
||||
end,
|
||||
|
||||
--- Connects to the iSCSI target
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return err string containing error message is status is false
|
||||
connect = function( self )
|
||||
local status, err = self.socket:connect(self.host, self.port, "tcp")
|
||||
if ( not(status) ) then return false, err end
|
||||
|
||||
self.comm = Comm:new( self.socket )
|
||||
return true
|
||||
end,
|
||||
|
||||
--- Attempts to discover accessible iSCSI targets on the remote server
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
-- @return targets table containing discovered targets
|
||||
-- each table entry is a target table with <code>name</code>
|
||||
-- and <code>addr</code>.
|
||||
-- err string containing an error message is status is false
|
||||
discoverTargets = function( self )
|
||||
local p = Packet.LoginRequest:new()
|
||||
|
||||
p:setTransit(true)
|
||||
p:setNSG(Packet.LoginRequest.NSG.LoginOperationalNegotiation)
|
||||
p.kvp:add( "InitiatorName", "iqn.1991-05.com.microsoft:nmap_iscsi_probe" )
|
||||
p.kvp:add( "SessionType", "Discovery" )
|
||||
p.kvp:add( "AuthMethod", "None" )
|
||||
|
||||
local status, resp = self.comm:exchange( p, Packet.LoginResponse )
|
||||
if ( not(status) ) then
|
||||
return false, ("ERROR: iscsi.Helper.discoverTargets: %s"):format(resp)
|
||||
end
|
||||
|
||||
local auth_method = resp.kvp:get("AuthMethod")[1]
|
||||
if ( auth_method:upper() ~= "NONE" ) then
|
||||
return false, "ERROR: iscsi.Helper.discoverTargets: Unsupported authentication method"
|
||||
end
|
||||
|
||||
p = Packet.LoginRequest:new()
|
||||
p:setTransit(true)
|
||||
p:setNSG(Packet.LoginRequest.NSG.FullFeaturePhase)
|
||||
p:setCSG(Packet.LoginRequest.CSG.LoginOperationalNegotiation)
|
||||
p.kvp:add( "HeaderDigest", "None")
|
||||
p.kvp:add( "DataDigest", "None")
|
||||
p.kvp:add( "MaxRecvDataSegmentLength", "65536")
|
||||
p.kvp:add( "DefaultTime2Wait", "0")
|
||||
p.kvp:add( "DefaultTime2Retain", "60")
|
||||
|
||||
status, resp = self.comm:exchange( p, Packet.LoginResponse )
|
||||
|
||||
p = Packet.TextRequest:new()
|
||||
p:setFinal(true)
|
||||
p.kvp:add( "SendTargets", "All" )
|
||||
status, resp = self.comm:exchange( p, Packet.TextResponse )
|
||||
|
||||
if ( not(resp.records) ) then
|
||||
return false, "iscsi.discoverTargets: response returned no targets"
|
||||
end
|
||||
|
||||
for _, record in ipairs(resp.records) do
|
||||
table.sort( record.addr, function(a, b) local c = ipOps.compare_ip(a:match("(.-):"), "le", b:match("(.-):")); return c end )
|
||||
end
|
||||
return true, resp.records
|
||||
end,
|
||||
|
||||
--- Logs out from the iSCSI target
|
||||
--
|
||||
-- @return status true on success, false on failure
|
||||
logout = function(self)
|
||||
local p = Packet.LogoutRequest:new()
|
||||
local status, resp = self.comm:exchange( p, Packet.LogoutResponse )
|
||||
return status
|
||||
end,
|
||||
|
||||
--- Authenticate to the iSCSI service
|
||||
--
|
||||
-- @param target_name string containing the name of the iSCSI target
|
||||
-- @param username string containing the username
|
||||
-- @param password string containing the password
|
||||
-- @param auth_method string containing either "None" or "Chap"
|
||||
-- @return status true on success false on failure
|
||||
-- @return response containing the loginresponse or
|
||||
-- err string containing an error message if status is false
|
||||
login = function( self, target_name, username, password, auth_method )
|
||||
|
||||
local auth_method = auth_method or "None"
|
||||
|
||||
if ( not(target_name) ) then
|
||||
return false, "No target name specified"
|
||||
end
|
||||
|
||||
if ( auth_method:upper()~= "NONE" and
|
||||
auth_method:upper()~= "CHAP" ) then
|
||||
return false, "Unknown authentication method"
|
||||
end
|
||||
|
||||
local p = Packet.LoginRequest:new()
|
||||
|
||||
p:setTransit(true)
|
||||
p:setNSG(Packet.LoginRequest.NSG.LoginOperationalNegotiation)
|
||||
p.kvp:add( "InitiatorName", "iqn.1991-05.com.microsoft:nmap_iscsi_probe" )
|
||||
p.kvp:add( "SessionType", "Normal" )
|
||||
p.kvp:add( "TargetName", target_name )
|
||||
p.kvp:add( "AuthMethod", auth_method )
|
||||
|
||||
if ( not(self.comm) ) then
|
||||
return false, "ERROR: iscsi.Helper.login: Not connected"
|
||||
end
|
||||
local status, resp = self.comm:exchange( p, Packet.LoginResponse )
|
||||
if ( not(status) ) then
|
||||
return false, ("ERROR: iscsi.Helper.login: %s"):format(resp)
|
||||
end
|
||||
|
||||
if ( resp.status_code ~= 0 ) then
|
||||
stdnse.print_debug(3, "ERROR: iscsi.Helper.login: Authentication failed (error code: %d)", resp.status_code)
|
||||
return false, resp
|
||||
elseif ( auth_method:upper()=="NONE" ) then
|
||||
return true, resp
|
||||
end
|
||||
|
||||
p = Packet.LoginRequest:new()
|
||||
p.kvp:add( "CHAP_A", "5" )
|
||||
status, resp = self.comm:exchange( p, Packet.LoginResponse )
|
||||
if ( not(status) ) then
|
||||
return false, ("ERROR: iscsi.Helper.login: %s"):format(resp)
|
||||
end
|
||||
|
||||
local alg = resp.kvp:get("CHAP_A")[1]
|
||||
if ( alg ~= "5" ) then return false, "Unsupported authentication algorithm" end
|
||||
|
||||
local chall = resp.kvp:get("CHAP_C")[1]
|
||||
if ( not(chall) ) then return false, "Failed to decode challenge" end
|
||||
chall = bin.pack("H", chall:sub(3))
|
||||
|
||||
local ident = resp.kvp:get("CHAP_I")[1]
|
||||
if (not(ident)) then return false, "Failed to decoded identifier" end
|
||||
ident = string.char(tonumber(ident))
|
||||
|
||||
local resp = CHAP.calcResponse( ident, chall, password )
|
||||
resp = "0x" .. select(2, bin.unpack("H16", resp))
|
||||
|
||||
p = Packet.LoginRequest:new()
|
||||
p:setImmediate(true)
|
||||
p:setTransit(true)
|
||||
p:setNSG(Packet.LoginRequest.NSG.LoginOperationalNegotiation)
|
||||
p.kvp:add("CHAP_N", username)
|
||||
p.kvp:add("CHAP_R", resp)
|
||||
|
||||
status, resp = self.comm:exchange( p, Packet.LoginResponse )
|
||||
if ( not(status) ) then
|
||||
return false, ("ERROR: iscsi.Helper.login: %s"):format(resp)
|
||||
end
|
||||
|
||||
if ( resp:getErrorCode() ~= Packet.LoginResponse.Errors.SUCCESS ) then
|
||||
return false, "Login failed"
|
||||
end
|
||||
|
||||
return true, resp
|
||||
end,
|
||||
|
||||
--- Disconnects the socket from the server
|
||||
close = function(self) self.socket:close() end
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
84
scripts/iscsi-brute.nse
Normal file
84
scripts/iscsi-brute.nse
Normal file
@@ -0,0 +1,84 @@
|
||||
description = [[
|
||||
Performs password guessing against iSCSI targets
|
||||
]]
|
||||
|
||||
---
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 3260/tcp open iscsi syn-ack
|
||||
-- | iscsi-brute:
|
||||
-- | Accounts
|
||||
-- | user:password123456 => Login correct
|
||||
-- | Statistics
|
||||
-- |_ Perfomed 5000 guesses in 7 seconds, average tps: 714
|
||||
|
||||
-- Version 0.1
|
||||
-- Created 2010/11/18 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
-- Revised 2010/11/27 - v0.2 - detect if no password is needed <patrik@cqure.net>
|
||||
|
||||
require 'shortport'
|
||||
require 'brute'
|
||||
require 'iscsi'
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"intrusive", "auth"}
|
||||
|
||||
portrule = shortport.portnumber(3260, "tcp", {"open", "open|filtered"})
|
||||
|
||||
Driver = {
|
||||
|
||||
new = function(self, host, port)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.host = host
|
||||
o.port = port
|
||||
o.target = stdnse.get_script_args('iscsi-brute.target')
|
||||
return o
|
||||
end,
|
||||
|
||||
connect = function( self )
|
||||
self.helper = iscsi.Helper:new( self.host, self.port )
|
||||
return self.helper:connect()
|
||||
end,
|
||||
|
||||
login = function( self, username, password )
|
||||
local status = self.helper:login( self.target, username, password, "CHAP")
|
||||
|
||||
if ( status ) then
|
||||
return true, brute.Account:new(username, password, "OPEN")
|
||||
end
|
||||
|
||||
return false, brute.Error:new( "Incorrect password" )
|
||||
end,
|
||||
|
||||
disconnect = function( self )
|
||||
self.helper:close()
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
action = function( host, port )
|
||||
|
||||
local target = stdnse.get_script_args('iscsi-brute.target')
|
||||
if ( not(target) ) then
|
||||
return "ERROR: No target specified (see iscsi-brute.target)"
|
||||
end
|
||||
|
||||
local helper = iscsi.Helper:new( host, port )
|
||||
local status, err = helper:connect()
|
||||
if ( not(status) ) then return false, "Failed to connect" end
|
||||
|
||||
local response
|
||||
status, response = helper:login( target )
|
||||
helper:logout()
|
||||
helper:close()
|
||||
|
||||
if ( status ) then return "No authentication required" end
|
||||
|
||||
local accounts
|
||||
status, accounts = brute.Engine:new(Driver, host, port):start()
|
||||
|
||||
if ( status ) then return accounts end
|
||||
end
|
||||
93
scripts/iscsi-info.nse
Normal file
93
scripts/iscsi-info.nse
Normal file
@@ -0,0 +1,93 @@
|
||||
description = [[
|
||||
Retrieves information from the remote iSCSI target.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 3260/tcp open iscsi
|
||||
-- | iscsi-info:
|
||||
-- | iqn.2006-01.com.openfiler:tsn.c8c08cad469d
|
||||
-- | Target address: 192.168.56.5:3260,1
|
||||
-- | Authentication: NOT required
|
||||
-- | iqn.2006-01.com.openfiler:tsn.6aea7e052952
|
||||
-- | Target address: 192.168.56.5:3260,1
|
||||
-- |_ Authentication: required
|
||||
--
|
||||
|
||||
-- Version 0.2
|
||||
-- Created 2010/11/18 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
-- Revised 2010/11/28 - v0.2 - improved error handling <patrik@cqure.net>
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery"}
|
||||
|
||||
require("shortport")
|
||||
require("iscsi")
|
||||
|
||||
portrule = shortport.portnumber(3260, "tcp", {"open", "open|filtered"})
|
||||
|
||||
-- Attempts to determine whether authentication is required or not
|
||||
--
|
||||
-- @return status true on success false on failure
|
||||
-- @return result true if auth is required false if not
|
||||
-- err string containing error message
|
||||
local function requiresAuth( host, port, target )
|
||||
local helper = iscsi.Helper:new( host, port )
|
||||
local errors = iscsi.Packet.LoginResponse.Errors
|
||||
|
||||
local status, err = helper:connect()
|
||||
if ( not(status) ) then return false, "Failed to connect" end
|
||||
|
||||
local response
|
||||
status, response = helper:login( target )
|
||||
if ( not(status) ) then return false, response:getErrorMessage() end
|
||||
|
||||
if ( status and response:getErrorCode() == errors.SUCCESS) then
|
||||
-- try to logout
|
||||
status = helper:logout()
|
||||
end
|
||||
|
||||
status = helper:close()
|
||||
|
||||
return true, "Authentication successful"
|
||||
end
|
||||
|
||||
action = function( host, port )
|
||||
|
||||
local helper = iscsi.Helper:new( host, port )
|
||||
|
||||
local status = helper:connect()
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug("%s: failed to connect to server", SCRIPT_NAME )
|
||||
return
|
||||
end
|
||||
|
||||
local records
|
||||
status, records = helper:discoverTargets()
|
||||
if ( not(status) ) then
|
||||
stdnse.print_debug("%s: failed to discover targets", SCRIPT_NAME )
|
||||
return
|
||||
end
|
||||
status = helper:logout()
|
||||
status = helper:close()
|
||||
|
||||
local result = {}
|
||||
for _, record in ipairs(records) do
|
||||
local result_part = {}
|
||||
result_part.name = ("Target: %s"):format(record.name)
|
||||
for _, addr in ipairs( record.addr ) do
|
||||
table.insert(result_part, ("Address: %s"):format(addr) )
|
||||
end
|
||||
|
||||
local status, err = requiresAuth( host, port, record.name )
|
||||
if ( not(status) ) then
|
||||
table.insert(result_part, "Authentication: " .. err )
|
||||
else
|
||||
table.insert(result_part, "Authentication: No authentication required")
|
||||
end
|
||||
table.insert(result, result_part)
|
||||
end
|
||||
return stdnse.format_output( true, result )
|
||||
end
|
||||
@@ -72,6 +72,8 @@ Entry { filename = "informix-tables.nse", categories = { "auth", "intrusive", }
|
||||
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "irc-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "irc-unrealircd-backdoor.nse", categories = { "safe", "vuln", } }
|
||||
Entry { filename = "iscsi-brute.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "iscsi-info.nse", categories = { "discovery", } }
|
||||
Entry { filename = "jdwp-version.nse", categories = { "version", } }
|
||||
Entry { filename = "ldap-brute.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "ldap-rootdse.nse", categories = { "discovery", "safe", } }
|
||||
|
||||
Reference in New Issue
Block a user