1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 12:41:29 +00:00

Add afp.lua and afp-showmount.nse from Patrik Karlsson. See

http://seclists.org/nmap-dev/2010/q1/97.
This commit is contained in:
david
2010-01-20 21:49:30 +00:00
parent b14044e832
commit 69000c63f0
3 changed files with 747 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added a new library, afp.lua, and a script that uses it,
afp-showmount. The library is for the Apple Filing Protocol and the
script shows shares and their permissions. [Patrik Karlsson]
o Fixed a bug with the decoding of NMAP OID component values greater o Fixed a bug with the decoding of NMAP OID component values greater
than 127. [Patrik Karlsson, David] than 127. [Patrik Karlsson, David]

549
nselib/afp.lua Normal file
View File

@@ -0,0 +1,549 @@
---
-- This module was written by Patrik Karlsson and facilitates communication
-- with the Apple AFP Service. It is not feature complete and is missing several
-- functions and parameters.
--
-- The library currently has enough functionality to query share names and access controls.
-- More functionality will be added once more scripts that depend on it are developed.
--
--
-- Version 0.1
-- Created 01/03/2010 - v0.1 - created by Patrik Karlsson
module(... or "afp",package.seeall)
-- Table of valid REQUESTs
local REQUEST = {
OpenSession = 0x04,
Command = 0x02
}
-- Table of headers flags to be set accordingly in requests and responses
local FLAGS = {
Request = 0,
Response = 1
}
-- Table of possible AFP_COMMANDs
COMMAND = {
FPCloseVol = 0x02,
FPLogin = 0x12,
FPGetUserInfo = 0x25,
FPGetSrvParms = 0x10,
FPOpenVol = 0x18,
FPOpenFork = 0x1a,
FPGetFileDirParams = 0x22,
FPReadExt = 0x3c,
FPEnumerateExt2 = 0x44
}
USER_BITMAP = {
UserId = 1,
PrimaryGroupId = 2,
UUID = 4
}
VOL_BITMAP = {
Attributes = 1,
Signature = 2,
CreationDate = 4,
ModificationDate = 8,
BackupDate = 16,
ID = 32,
BytesFree = 64,
BytesTotal = 128,
Name = 256,
ExtendedBytesFree = 512,
ExtendedBytesTotal = 1024,
BlockSize = 2048
}
FILE_BITMAP = {
Attributes = 1,
DID = 2,
CreationDate = 4,
ModificationDate = 8,
BackupDate = 16,
FinderInfo = 32,
LongName = 64,
ShortName = 128,
FileId = 256,
DataForkSize = 512,
ResourceForkSize = 1024,
ExtendedDataForkSize = 2048,
LaunchLimit = 4096,
UTF8Name = 8192,
ExtendedResourceForkSize = 16384,
UnixPrivileges = 32768
}
DIR_BITMAP = {
Attributes = 1,
DID = 2,
CreationDate = 4,
ModificationDate = 8,
BackupDate = 16,
FinderInfo = 32,
LongName = 64,
ShortName = 128,
FileId = 256,
OffspringCount = 512,
OwnerId = 1024,
GroupId = 2048,
AccessRights = 4096,
UTF8Name = 8192,
UnixPrivileges = 32768
}
PATH_TYPE = {
LongNames = 2,
UnicodeNames = 3
}
ACCESS_MODE = {
Read = 1,
Write = 2,
DenyRead = 16,
DenyWrite = 32
}
ACLS = {
OwnerSearch = 1,
OwnerRead = 2,
OwnerWrite = 4,
GroupSearch = 256,
GroupRead = 512,
GroupWrite = 1024,
EveryoneSearch = 65536,
EveryoneRead = 131072,
EveryoneWrite = 262144,
UserSearch = 1048576,
UserRead = 2097152,
UserWrite = 4194304,
BlankAccess = 268435456,
UserIsOwner = 2147483648
}
-- Each packet contains a sequential request id
-- this number is used within <code>create_fp_packet</code> and increased by one in each call
request_id = 1
--- Creates an AFP packet
--
-- @param command number should be one of the commands in the COMMAND table
-- @param data_offset number holding the offset to the data
-- @param data the actual data of the request
function create_fp_packet( command, data_offset, data )
local reserved = 0
local data = data or ""
local data_len = data:len()
local header = bin.pack("CC>SIII", FLAGS.Request, command, request_id, data_offset, data_len, reserved)
local packet = header .. data
request_id = request_id + 1
return packet
end
--- Parses the FP header (first 16-bytes of packet)
--
-- @param packet string containing the raw packet
-- @return table with header data containing <code>flags</code>, <code>command</code>,
-- <code>request_id</code>, <code>error_code</code>, <code>length</code> and <code>reserved</code> fields
function parse_fp_header( packet )
local header = {}
local pos
pos, header.flags, header.command, header.request_id = bin.unpack( "CC>S", packet )
pos, header.error_code, header.length, header.reserved = bin.unpack( "I>II", packet:sub(5) )
header.raw = packet:sub(1,16)
return header
end
--- Sends an OpenSession AFP request to the server and handles the response
--
-- @param socket already connected to the server
-- @return status (true or false)
-- @return nil (if status is true) or error string (if status is false)
function open_session( socket )
local data_offset = 0
local option = 0x01 -- Attention Quantum
local option_len = 4
local quantum = 1024
local data = bin.pack( "CCI", option, option_len, quantum )
local packet = create_fp_packet( REQUEST.OpenSession, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("OpenSession error: %d", packet.header.error_code)
end
return true, nil
end
--- Sends an FPGetUserInfo AFP request to the server and handles the response
--
-- @param socket already connected to the server
-- @return status (true or false)
-- @return table with user information containing <code>user_bitmap</code> and
-- <code>uid</code> fields (if status is true) or error string (if status is false)
function fp_get_user_info( socket )
local packet
local data_offset = 0
local flags = 1 -- Default User
local uid = 0
local bitmap = USER_BITMAP.UserId
local response = {}
local pos
local data = bin.pack( "CCI>S", COMMAND.FPGetUserInfo, flags, uid, bitmap )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("OpenSession error: %d", packet.header.error_code)
end
pos, response.user_bitmap, response.uid = bin.unpack(">S>I", packet.data)
return true, response
end
--- Sends an FPGetSrvrParms AFP request to the server and handles the response
--
-- @param socket already connected to the server
-- @return status (true or false)
-- @return table with server parameters containing <code>server_time</code>,
-- <code>vol_count</code>, <code>volumes</code> fields (if status is true) or error string (if status is false)
--
function fp_get_srvr_parms(socket)
local packet
local data_offset = 0
local response = {}
local pos = 0
local data = bin.pack("CC", COMMAND.FPGetSrvParms, 0)
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPGetSrvrParms error: %d", packet.header.error_code)
end
data = packet.data
pos, response.server_time, response.vol_count = bin.unpack("IC", data)
-- we should now be at the leading zero preceeding the first volume name
-- next is the length of the volume name, move pos there
pos = pos + 1
stdnse.print_debug("Volumes: %d", response.vol_count )
response.volumes = {}
for i=1, response.vol_count do
local _, vol_len = bin.unpack("C", data:sub(pos))
local volume_name = data:sub(pos + 1, pos + 1 + vol_len)
pos = pos + vol_len + 2
table.insert(response.volumes, string.format("%s", volume_name) )
stdnse.print_debug("Volume name: %s", volume_name)
end
return true, response
end
--- Sends an FPLogin request to the server and handles the response
--
-- This function currently only supports the 3.1 through 3.3 protocol versions
-- It does not support authentication so the uam parameter is currently ignored
--
-- @param socket already connected to the server--
-- @param afp_version string (AFP3.3|AFP3.2|AFP3.1)
-- @param uam string containing authentication information (currently ignored)
-- @return status (true or false)
-- @return nil (if status is true) or error string (if status is false)
function fp_login( socket, afp_version, uam )
local packet
local data_offset = 0
-- currently we only support AFP3.3
if afp_version == nil or ( afp_version ~= "AFP3.3" and afp_version ~= "AFP3.2" and afp_version ~= "AFP3.1" ) then
return
end
uam = "No User Authent"
local data = bin.pack( "CCACA", COMMAND.FPLogin, afp_version:len(), afp_version, uam:len(), uam )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPLogin error: %d", packet.header.error_code)
end
return true, nil
end
--- Reads a AFP packet of the socket
--
-- @param socket socket connected to the server
-- @return table containing <code>data</code> and <code>header</code> fields
function read_fp_packet( socket )
local packet = {}
local buf = ""
local catch = function()
socket:close()
end
local try = nmap.new_try(catch)
repeat
buf = buf .. try( socket:receive(16) )
until buf:len() >= 16 -- make sure we have got atleast the header
packet.header = parse_fp_header( buf )
-- if we didn't get the whole packet when reading the header, try to read the rest
while buf:len() < packet.header.length + packet.header.raw:len() do
buf = buf .. try( socket:receive(packet.header.length) )
end
packet.data = buf:len() > 16 and buf:sub( 17 ) or ""
return packet
end
--- Sends an FPOpenVol request to the server and handles the response
--
-- @param socket already connected to the server
-- @param bitmap number bitmask of volume information to request
-- @param volume_name string containing the volume name to query
-- @return status (true or false)
-- @return table containing <code>bitmap</code> and <code>volume_id</code> fields
-- (if status is true) or error string (if status is false)
function fp_open_vol( socket, bitmap, volume_name )
local packet
local data_offset = 0
local pad = 0
local response = {}
local pos
local data = bin.pack("CC>SCA", COMMAND.FPOpenVol, pad, bitmap, volume_name:len(), volume_name )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPOpenVol error: %d", packet.header.error_code )
end
pos, response.bitmap, response.volume_id = bin.unpack(">S>S", packet.data)
return true, response
end
--- Sends an FPGetFileDirParms request to the server and handles the response
--
-- Currently only handles a request for the Access rights (file_bitmap must be 0 and dir_bitmap must be 0x1000)
--
-- @param socket already connected to the server
-- @param volume_id number containing the id of the volume to query
-- @param did number containing the id of the directory to query
-- @param file_bitmap number bitmask of file information to query
-- @param dir_bitmap number bitmask of directory information to query
-- @param path string containing the name of the directory to query
-- @return status (true or false)
-- @return table containing <code>file_bitmap</code>, <code>dir_bitmap</code>,
-- <code>file_type</code> and <code>acls</code> fields
-- (if status is true) or error string (if status is false)
function fp_get_file_dir_parms( socket, volume_id, did, file_bitmap, dir_bitmap, path )
local packet
local data_offset = 0
local pad = 0
local response = {}
local pos
if file_bitmap ~= 0 or dir_bitmap ~= DIR_BITMAP.AccessRights then
return false, "Only AccessRights querys are supported (file_bitmap=0, dir_bitmap=DIR_BITMAP.AccessRights)"
end
local data = bin.pack("CC>S>I>S>SCCAC", COMMAND.FPGetFileDirParams, pad, volume_id, did, file_bitmap, dir_bitmap, path.type, path.len, path.name, 0)
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPGetFileDirParms error: %d", packet.header.error_code )
end
pos, response.file_bitmap, response.dir_bitmap, response.file_type, pad, response.acls = bin.unpack( ">S>SCC>I", packet.data )
return true, response
end
--- Sends an FPEnumerateExt2 request to the server and handles the response
--
-- @param socket already connected to the server
-- @param volume_id number containing the id of the volume to query
-- @param did number containing the id of the directory to query
-- @param file_bitmap number bitmask of file information to query
-- @param dir_bitmap number bitmask of directory information to query
-- @param req_count number
-- @param start_index number
-- @param reply_size number
-- @param path string containing the name of the directory to query
-- @return status (true or false)
-- @return table containing <code>file_bitmap</code>, <code>dir_bitmap</code>,
-- <code>req_count</code> fields
-- (if status is true) or error string (if status is false)
function fp_enumerate_ext2( socket, volume_id, did, file_bitmap, dir_bitmap, req_count, start_index, reply_size, path )
local packet
local data_offset = 0
local pad = 0
local response = {}
local data = bin.pack( "CC>S>I>S>S", COMMAND.FPEnumerateExt2, pad, volume_id, did, file_bitmap, dir_bitmap )
data = data .. bin.pack( ">S>I>IC>SA", req_count, start_index, reply_size, path.type, path.len, path.name )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPEnumerateExt2 error: %d", packet.header.error_code )
end
_, response.file_bitmap, response.dir_bitmap, response.req_count = bin.unpack(">S>S>S", packet.data)
return true, response
end
--- Sends an FPOpenFork request to the server and handles the response
--
-- @param socket already connected to the server
-- @param fork number
-- @param volume_id number containing the id of the volume to query
-- @param did number containing the id of the directory to query
-- @param file_bitmap number bitmask of file information to query
-- @param access_mode number containing bitmask of options from <code>ACCESS_MODE</code>
-- @param path string containing the name of the directory to query
-- @return status (true or false)
-- @return table containing <code>file_bitmap</code> and <code>fork</code> fields (if status is true) or
-- error string (if status is false)
function fp_open_fork( socket, fork, volume_id, did, file_bitmap, access_mode, path )
local packet
local data_offset = 0
local pad = 0
local response = {}
local data = bin.pack( "CC>S>I>S>S", COMMAND.FPOpenFork, fork, volume_id, did, file_bitmap, access_mode )
if path.type == PATH_TYPE.LongNames then
data = data .. bin.pack( "C>SA", path.type, path.len, path.name )
end
if path.type == PATH_TYPE.UnicodeNames then
local unicode_hint = 0x08000103
data = data .. bin.pack( "C>I>SA", path.type, unicode_hint, path.len, path.name )
end
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPOpenFork error: %d", packet.header.error_code )
end
_, response.file_bitmap, response.fork = bin.unpack(">S>S", packet.data)
return true, response
end
--- Sends an FPCloseVol request to the server and handles the response
--
-- @param socket already connected to the server
-- @param volume_id number containing the id of the volume to close
-- @return status (true or false)
-- @return nil (if status is true) or error string (if status is false)
function fp_close_vol( socket, volume_id )
local packet
local data_offset = 0
local pad = 0
local response = {}
local data = bin.pack( "CC>S>", COMMAND.FPCloseVol, pad, volume_id )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
send_fp_packet( socket, packet )
packet = read_fp_packet( socket )
if packet.header.error_code ~= 0 then
return false, string.format("FPCloseVol error: %d", packet.header.error_code )
end
return true, nil
end
--- Sends the raw packet over the socket
--
-- @param socket already connected to the server
-- @param packet containing the raw data
function send_fp_packet( socket, packet )
local catch = function()
socket:close()
end
local try = nmap.new_try(catch)
try( socket:send(packet) )
end
function fp_read_ext( fork, offset, count )
local packet
local data_offset = 0
local pad = 0
local data = bin.pack( "CC>S>L>L", COMMAND.FPReadExt, pad, fork, offset, count )
packet = create_fp_packet( REQUEST.Command, data_offset, data )
return packet
end

194
scripts/afp-showmount.nse Normal file
View File

@@ -0,0 +1,194 @@
description = [[ Shows AFP shares and ACLs ]]
---
--@output
-- PORT STATE SERVICE
-- 548/tcp open afp
-- | afp-showmount:
-- | Yoda's Public Folder
-- | Owner: Search,Read,Write
-- | Group: Search,Read
-- | Everyone: Search,Read
-- | User: Search,Read
-- | Vader's Public Folder
-- | Owner: Search,Read,Write
-- | Group: Search,Read
-- | Everyone: Search,Read
-- | User: Search,Read
-- |_ Options: IsOwner
-- Version 0.1
-- Created 01/03/2010 - v0.1 - created by Patrik Karlsson
-- Revised 01/13/2010 - v0.2 - Fixed a bug where a single share wouldn't show due to formatting issues
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
require 'shortport'
require 'stdnse'
require 'afp'
portrule = shortport.portnumber(548, "tcp")
--- Converts a group bitmask of Search, Read and Write to string
-- eg. WRS, -RS, W-S, etc ..
--
-- @param acls number containing bitmasked acls
-- @return string of ACLS
function acl_group_to_string( acls )
local acl_string = ""
if bit.band( acls, afp.ACLS.OwnerSearch ) == afp.ACLS.OwnerSearch then
acl_string = "S"
else
acl_string = "-"
end
if bit.band( acls, afp.ACLS.OwnerRead ) == afp.ACLS.OwnerRead then
acl_string = "R" .. acl_string
else
acl_string = "-" .. acl_string
end
if bit.band( acls, afp.ACLS.OwnerWrite ) == afp.ACLS.OwnerWrite then
acl_string = "W" .. acl_string
else
acl_string = "-" .. acl_string
end
return acl_string
end
--- Converts a group bitmask of Search, Read and Write to table
--
-- @param acls number containing bitmasked acls
-- @return table of ACLs
function acl_group_to_long_string(acls)
local acl_table = {}
if bit.band( acls, afp.ACLS.OwnerSearch ) == afp.ACLS.OwnerSearch then
table.insert( acl_table, "Search")
end
if bit.band( acls, afp.ACLS.OwnerRead ) == afp.ACLS.OwnerRead then
table.insert( acl_table, "Read")
end
if bit.band( acls, afp.ACLS.OwnerWrite ) == afp.ACLS.OwnerWrite then
table.insert( acl_table, "Write")
end
return acl_table
end
--- Converts a numeric acl to string
--
-- @param acls number containig acls as recieved from <code>fp_get_file_dir_parms</code>
-- @return string of ACLs
function acls_to_string( acls )
local owner = acl_group_to_string( bit.band( acls, 255 ) )
local group = acl_group_to_string( bit.band( bit.rshift(acls, 8), 255 ) )
local everyone = acl_group_to_string( bit.band( bit.rshift(acls, 16), 255 ) )
local user = acl_group_to_string( bit.band( bit.rshift(acls, 24), 255 ) )
local blank = bit.band( acls, afp.ACLS.BlankAccess ) == afp.ACLS.BlankAccess and "B" or "-"
local isowner = bit.band( acls, afp.ACLS.UserIsOwner ) == afp.ACLS.UserIsOwner and "O" or "-"
return string.format("Owner: %s; Group: %s; Everyone: %s; User: %s; Options: %s%s", owner, group, everyone, user, blank, isowner )
end
--- Converts a numeric acl to string
--
-- @param acls number containig acls as recieved from <code>fp_get_file_dir_parms</code>
-- @return table of long ACLs
function acls_to_long_string( acls )
local owner = acl_group_to_long_string( bit.band( acls, 255 ) )
local group = acl_group_to_long_string( bit.band( bit.rshift(acls, 8), 255 ) )
local everyone = acl_group_to_long_string( bit.band( bit.rshift(acls, 16), 255 ) )
local user = acl_group_to_long_string( bit.band( bit.rshift(acls, 24), 255 ) )
local blank = bit.band( acls, afp.ACLS.BlankAccess ) == afp.ACLS.BlankAccess and "Blank" or nil
local isowner = bit.band( acls, afp.ACLS.UserIsOwner ) == afp.ACLS.UserIsOwner and "IsOwner" or nil
local options = {}
if blank then
table.insert(options, "Blank")
end
if isowner then
table.insert(options, "IsOwner")
end
local acls_tbl = {}
table.insert( acls_tbl, string.format( "Owner: %s", stdnse.strjoin(",", owner) ) )
table.insert( acls_tbl, string.format( "Group: %s", stdnse.strjoin(",", group) ) )
table.insert( acls_tbl, string.format( "Everyone: %s", stdnse.strjoin(",", everyone) ) )
table.insert( acls_tbl, string.format( "User: %s", stdnse.strjoin(",", user) ) )
if #options > 0 then
table.insert( acls_tbl, string.format( "Options: %s", stdnse.strjoin(",", options ) ) )
end
return acls_tbl
end
action = function(host, port)
local socket = nmap.new_socket()
local status
local result = {}
-- set a reasonable timeout value
socket:set_timeout(5000)
-- do some exception handling / cleanup
local catch = function()
socket:close()
end
local try = nmap.new_try(catch)
try( socket:connect(host.ip, port.number, "tcp") )
response = try( afp.open_session(socket) )
response = try( afp.fp_login( socket, "AFP3.1", "No User Authent") )
response = try( afp.fp_get_user_info( socket ) )
response = try( afp.fp_get_srvr_parms( socket ) )
volumes = response.volumes
for _, vol in pairs(volumes) do
table.insert( result, vol )
status, response = afp.fp_open_vol( socket, afp.VOL_BITMAP.ID, vol )
if status then
local vol_id = response.volume_id
stdnse.print_debug(string.format("Vol_id: %d", vol_id))
local path = {}
path.type = afp.PATH_TYPE.LongNames
path.name = ""
path.len = path.name:len()
response = try( afp.fp_get_file_dir_parms( socket, vol_id, 2, 0, afp.DIR_BITMAP.AccessRights, path ) )
local acls = acls_to_long_string(response.acls)
acls.name = nil
try( afp.fp_close_vol( socket, vol_id ) )
table.insert( result, acls )
end
end
return stdnse.format_output(true, result)
end