Skip to content

Commit

Permalink
Adds rhost url support behind a feature flag
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelch-r7 committed Aug 7, 2020
1 parent 0d1f4c1 commit e5841c9
Show file tree
Hide file tree
Showing 21 changed files with 281 additions and 80 deletions.
13 changes: 11 additions & 2 deletions lib/msf/core/data_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,23 @@ def []=(k, v)
end
end

super(k,v)
if v.is_a? Hash
v.each { |key, value| self[key] = value }
else
super(k,v)
end
end

#
# Case-insensitive wrapper around hash lookup
#
def [](k)
super(find_key_case(k))
k = find_key_case(k)
if options[k].respond_to? :calculate_value
options[k].calculate_value(self)
else
super(k)
end
end

#
Expand Down
4 changes: 4 additions & 0 deletions lib/msf/core/exploit/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def initialize(info = {})
], self.class
)

if framework.features.enabled?("RHOST_HTTP_URL")
register_options([Opt::RHOST_HTTP_URL])
end

register_advanced_options(
[
OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests',
Expand Down
5 changes: 5 additions & 0 deletions lib/msf/core/feature_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class FeatureManager
name: 'wrapped_tables',
description: 'When enabled Metasploit will wordwrap all tables to fit into the available terminal width',
default_value: false
}.freeze,
{
name: 'RHOST_HTTP_URL',
description: 'When enabled in supported modules you can specify a URL as a target',
default_value: false
}.freeze
].freeze

Expand Down
60 changes: 28 additions & 32 deletions lib/msf/core/opt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,88 +14,83 @@ module Msf
# register_advanced_options([Opt::Proxies])
#
module Opt

# @return [OptAddress]
def self.CHOST(default=nil, required=false, desc="The local client address")
def self.CHOST(default = nil, required = false, desc = 'The local client address')
Msf::OptAddress.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptPort]
def self.CPORT(default=nil, required=false, desc="The local client port")
def self.CPORT(default = nil, required = false, desc = 'The local client port')
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptAddressLocal]
def self.LHOST(default=nil, required=true, desc="The listen address (an interface may be specified)")
def self.LHOST(default = nil, required = true, desc = 'The listen address (an interface may be specified)')
Msf::OptAddressLocal.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptPort]
def self.LPORT(default=nil, required=true, desc="The listen port")
def self.LPORT(default = nil, required = true, desc = 'The listen port')
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptString]
def self.Proxies(default=nil, required=false, desc="A proxy chain of format type:host:port[,type:host:port][...]")
def self.Proxies(default = nil, required = false, desc = 'A proxy chain of format type:host:port[,type:host:port][...]')
Msf::OptString.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptAddressRange]
def self.RHOSTS(default=nil, required=true, desc="The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'")
def self.RHOSTS(default = nil, required = true, desc = "The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'")
Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ])
end

def self.RHOST(default=nil, required=true, desc="The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'")
def self.RHOST(default = nil, required = true, desc = "The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'")
Msf::OptAddressRange.new('RHOSTS', [ required, desc, default ], aliases: [ 'RHOST' ])
end

# @return [OptPort]
def self.RPORT(default=nil, required=true, desc="The target port")
def self.RPORT(default = nil, required = true, desc = 'The target port')
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptEnum]
def self.SSLVersion
Msf::OptEnum.new('SSLVersion',
'Specify the version of SSL/TLS to be used (Auto, TLS and SSL23 are auto-negotiate)',
enums: Rex::Socket::SslTcp.supported_ssl_methods
)
'Specify the version of SSL/TLS to be used (Auto, TLS and SSL23 are auto-negotiate)',
enums: Rex::Socket::SslTcp.supported_ssl_methods)
end

def self.RHOST_HTTP_URL(default = nil, required = false, desc = 'The target URL, only applicable if there is a single URL')
Msf::OptHTTPRhostURL.new(__method__.to_s, [required, desc, default ])
end

def self.stager_retry_options
[
OptInt.new('StagerRetryCount',
'The number of times the stager should retry if the first connect fails',
default: 10,
aliases: ['ReverseConnectRetries']
),
'The number of times the stager should retry if the first connect fails',
default: 10,
aliases: ['ReverseConnectRetries']),
OptInt.new('StagerRetryWait',
'Number of seconds to wait for the stager between reconnect attempts',
default: 5
)
'Number of seconds to wait for the stager between reconnect attempts',
default: 5)
]
end

def self.http_proxy_options
[
OptString.new('HttpProxyHost', 'An optional proxy server IP address or hostname',
aliases: ['PayloadProxyHost']
),
aliases: ['PayloadProxyHost']),
OptPort.new('HttpProxyPort', 'An optional proxy server port',
aliases: ['PayloadProxyPort']
),
aliases: ['PayloadProxyPort']),
OptString.new('HttpProxyUser', 'An optional proxy server username',
aliases: ['PayloadProxyUser'],
max_length: Rex::Payloads::Meterpreter::Config::PROXY_USER_SIZE - 1
),
aliases: ['PayloadProxyUser'],
max_length: Rex::Payloads::Meterpreter::Config::PROXY_USER_SIZE - 1),
OptString.new('HttpProxyPass', 'An optional proxy server password',
aliases: ['PayloadProxyPass'],
max_length: Rex::Payloads::Meterpreter::Config::PROXY_PASS_SIZE - 1
),
aliases: ['PayloadProxyPass'],
max_length: Rex::Payloads::Meterpreter::Config::PROXY_PASS_SIZE - 1),
OptEnum.new('HttpProxyType', 'The type of HTTP proxy',
enums: ['HTTP', 'SOCKS'],
aliases: ['PayloadProxyType']
)
enums: ['HTTP', 'SOCKS'],
aliases: ['PayloadProxyType'])
]
end

Expand All @@ -114,6 +109,7 @@ def self.http_header_options
Proxies = Proxies()
RHOST = RHOST()
RHOSTS = RHOSTS()
RHOST_HTTP_URL = RHOST_HTTP_URL()
RPORT = RPORT()
SSLVersion = SSLVersion()
end
Expand Down
91 changes: 91 additions & 0 deletions lib/msf/core/opt_http_rhost_url.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- coding: binary -*-

module Msf
###
#
# RHOST URL option.
#
###
class OptHTTPRhostURL < OptBase
def type
'rhost http url'
end

def normalize(value)
return unless value

uri = get_uri(value)
return unless uri

option_hash = {}
# Blank this out since we don't know if this new value will have a `VHOST` to ensure we remove the old value
option_hash['VHOST'] = nil

option_hash['RHOSTS'] = uri.hostname
option_hash['RPORT'] = uri.port
option_hash['SSL'] = %w[ssl https].include?(uri.scheme)

# Both `TARGETURI` and `URI` are used as datastore options to denote the path on a uri
option_hash['TARGETURI'] = uri.path || '/'
option_hash['URI'] = uri.path || '/'

if uri.scheme && %(http https).include?(uri.scheme)
option_hash['VHOST'] = uri.hostname unless Rex::Socket.is_ip_addr?(uri.hostname)
option_hash['HttpUsername'] = uri.user.to_s
option_hash['HttpPassword'] = uri.password.to_s
end

option_hash
end

def valid?(value, check_empty: false)
return true unless value || required

uri = get_uri(value)
return false unless uri && !uri.host.nil? && !uri.port.nil?

super
end

def calculate_value(datastore)
return unless datastore['RHOSTS']
begin
uri_type = datastore['SSL'] ? URI::HTTPS : URI::HTTP
uri = uri_type.build(host: datastore['RHOSTS'])
uri.port = datastore['RPORT']
# The datastore uses both `TARGETURI` and `URI` to denote the path of a URL, we try both here and fall back to `/`
uri.path = (datastore['TARGETURI'] || datastore['URI'] || '/')
uri.user = datastore['HttpUsername']
uri.password = datastore['HttpPassword'] if uri.user
uri
rescue URI::InvalidComponentError
nil
end
end

protected

def get_uri(value)
return unless value
return if check_for_range(value)

value = 'http://' + value unless value.start_with?(%r{https?://})
URI(value)
rescue URI::InvalidURIError
nil
end

def check_for_range(value)
return false if value =~ /[^-0-9,.*\/]/
walker = Rex::Socket::RangeWalker.new(value)
if walker&.valid?
# if there is only a single ip then it's not a range
return walker.length != 1
end
rescue ::Exception
# couldn't create a range therefore it isn't one
return false
end

end
end
2 changes: 1 addition & 1 deletion lib/msf/core/opt_raw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def normalize(value)
value
end

def valid?(value=self.value)
def valid?(value=self.value, check_empty: true)
value = normalize(value)
return false if empty_required_value?(value)
return super
Expand Down
1 change: 1 addition & 0 deletions lib/msf/core/option_container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Msf
autoload :OptRaw, 'msf/core/opt_raw'
autoload :OptRegexp, 'msf/core/opt_regexp'
autoload :OptString, 'msf/core/opt_string'
autoload :OptHTTPRhostURL, 'msf/core/opt_http_rhost_url'

#
# The options purpose in life is to associate named options with arbitrary
Expand Down
7 changes: 5 additions & 2 deletions spec/lib/metasploit/framework/aws/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
RSpec.describe Metasploit::Framework::Aws::Client do

subject do
s = Class.new(Msf::Auxiliary) do
mod_klass = Class.new(Msf::Auxiliary) do
include Metasploit::Framework::Aws::Client
end.new
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
s = mod_klass.new
s.datastore['Region'] = 'us-east-1'
s.datastore['RHOST'] = '127.0.0.1'
s
Expand Down
10 changes: 6 additions & 4 deletions spec/lib/msf/core/exploit/http/jboss/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::Base do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::Exploit::Remote::HTTP::JBoss
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::JBoss
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

describe "#deploy" do
Expand Down
10 changes: 6 additions & 4 deletions spec/lib/msf/core/exploit/http/jboss/bean_shell_scripts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::BeanShellScripts do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::Exploit::Remote::HTTP::JBoss
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::JBoss
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

describe "#generate_bsh" do
Expand Down
10 changes: 6 additions & 4 deletions spec/lib/msf/core/exploit/http/jboss/bean_shell_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::BeanShell do

subject do
mod = ::Msf::Exploit.new
mod.extend Msf::Exploit::Remote::HTTP::JBoss
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::JBoss
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

before :example do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::DeploymentFileRepositoryScripts do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::Exploit::Remote::HTTP::JBoss
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::JBoss
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

describe "#stager_jsp_with_payload" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

RSpec.describe Msf::Exploit::Remote::HTTP::JBoss::DeploymentFileRepository do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::Exploit::Remote::HTTP::JBoss
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include Msf::Exploit::Remote::HTTP::JBoss
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

let (:base_name) do
Expand Down
10 changes: 6 additions & 4 deletions spec/lib/msf/core/exploit/http/joomla/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
RSpec.describe Msf::Exploit::Remote::HTTP::Joomla::Base do

subject do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Exploit::Remote::HTTP::Joomla
mod.send(:initialize)
mod
mod_klass = Class.new(::Msf::Exploit) do
include ::Msf::Exploit::Remote::HTTP::Joomla
end
features = instance_double(Msf::FeatureManager, enabled?: false)
mod_klass.framework = instance_double(Msf::Framework, features: features, datastore: {})
mod_klass.new
end

let(:joomla_body) do
Expand Down
Loading

0 comments on commit e5841c9

Please sign in to comment.