Skip to content

Commit

Permalink
Update DNS resolution for socks proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
adfoster-r7 committed Dec 5, 2024
1 parent 22ade4f commit 0a41f51
Show file tree
Hide file tree
Showing 23 changed files with 197 additions and 46 deletions.
3 changes: 2 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,8 @@ GEM
metasm
rex-core
rex-text
rex-socket (0.1.57)
rex-socket (0.1.58)
dnsruby
rex-core
rex-sslscan (0.1.10)
rex-core
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/opt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ def self.LPORT(default=nil, required=true, desc="The listen port")
end

# @return [OptString]
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 ])
def self.Proxies(default=nil, required=false, desc="A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: #{Rex::Socket::Proxies.supported_types.join(', ')}")
Msf::OptProxies.new(__method__.to_s, [ required, desc, default ])
end

# @return [OptRhosts]
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def type
return 'address'
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return false unless value.kind_of?(String) or value.kind_of?(NilClass)

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_address_local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def normalize(value)
sorted_addrs.any? ? sorted_addrs.first : ''
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return false unless value.kind_of?(String) || value.kind_of?(NilClass)

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

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return false unless value.kind_of?(String) or value.kind_of?(NilClass)

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_address_routable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module Msf
###
class OptAddressRoutable < OptAddress

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if Rex::Socket.is_ip_addr?(value) && Rex::Socket.addr_atoi(value) == 0
super
end
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def validate_on_assignment?
#
# If it's required and the value is nil or empty, then it's not valid.
#
def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
if check_empty && required?
# required variable not set
return false if (value.nil? || value.to_s.empty?)
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_bool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def type
return 'bool'
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return true if value.nil? && !required?

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(in_name, attrs = [],
super
end

def valid?(value = self.value, check_empty: true)
def valid?(value = self.value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return true if value.nil? && !required?

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_float.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def normalize(value)
Float(value) if value.present? && valid?(value)
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
Float(value) rescue return false if value.present?
super
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_int.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def normalize(value)
end
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return false if value.present? && !value.to_s.match(/^0x[0-9a-fA-F]+$|^-?\d+$/)
super
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_meterpreter_debug_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def validate_on_assignment?
true
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if !super(value, check_empty: check_empty)

begin
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def validate_on_assignment?
end

# Generally, 'value' should be a file that exists.
def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
if value and !value.empty?
if value =~ /^memory:\s*([0-9]+)/i
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/opt_port.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def type
return 'port'
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
port = normalize(value).to_i
super && port <= 65535 && port >= 0
end
Expand Down
35 changes: 35 additions & 0 deletions lib/msf/core/opt_proxies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: binary -*-

module Msf

###
#
# Proxies option
#
###
class OptProxies < OptBase

def type
'proxies'
end

def validate_on_assignment?
true
end

def normalize(value)
value
end

def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)

parsed = Rex::Socket::Proxies.parse(value)
allowed_types = Rex::Socket::Proxies.supported_types
parsed.all? do |type, host, port|
allowed_types.include?(type) && host.present? && port.present?
end
end
end

end
2 changes: 1 addition & 1 deletion lib/msf/core/opt_regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def type
return 'regexp'
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
if check_empty && empty_required_value?(value)
return false
elsif value.nil?
Expand Down
7 changes: 4 additions & 3 deletions lib/msf/core/opt_rhosts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ def normalize(value)
value
end

def valid?(value, check_empty: true)
def valid?(value, check_empty: true, datastore: nil)
return false if check_empty && empty_required_value?(value)
return false unless value.is_a?(String) || value.is_a?(NilClass)

if !value.nil? && value.empty? == false
return Msf::RhostsWalker.new(value).valid?
if !value.nil? && !value.empty?
rhost_walker = datastore ? Msf::RhostsWalker.new(value, datastore) : Msf::RhostsWalker.new(value)
return rhost_walker.valid?
end

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

def valid?(value=self.value, check_empty: true)
def valid?(value=self.value, check_empty: true, datastore: nil)
value = normalize(value)
return false if check_empty && empty_required_value?(value)
return false if invalid_value_length?(value)
Expand Down
6 changes: 3 additions & 3 deletions lib/msf/core/option_container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def add_evasion_options(opts, owner = nil)
def validate(datastore)
# First mutate the datastore and normalize all valid values before validating permutations of RHOST/etc.
each_pair do |name, option|
if option.valid?(datastore[name]) && (val = option.normalize(datastore[name])) != nil
if option.valid?(datastore[name], datastore: datastore) && (val = option.normalize(datastore[name])) != nil
# This *will* result in a module that previously used the
# global datastore to have its local datastore set, which
# means that changing the global datastore and re-running
Expand Down Expand Up @@ -232,7 +232,7 @@ def validate(datastore)

rhosts_walker.each do |datastore|
each_pair do |name, option|
unless option.valid?(datastore[name])
unless option.valid?(datastore[name], datastore: datastore)
error_options << name
if rhosts_count > 1
error_reasons[name] << "for rhosts value #{datastore['UNPARSED_RHOSTS']}"
Expand All @@ -248,7 +248,7 @@ def validate(datastore)
else
error_options = []
each_pair do |name, option|
unless option.valid?(datastore[name])
unless option.valid?(datastore[name], datastore: datastore)
error_options << name
end
end
Expand Down
56 changes: 39 additions & 17 deletions lib/msf/core/rhosts_walker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class RhostResolveError < StandardError
MESSAGE = 'Host resolution failed'
end

# @param [String] value
# @param [Msf::DataStore] datastore
def initialize(value = '', datastore = Msf::ModuleDataStore.new(nil))
@value = value
@datastore = datastore
Expand Down Expand Up @@ -141,30 +143,42 @@ def parse(value, datastore)
schema = Regexp.last_match(:schema)
raise InvalidSchemaError unless SUPPORTED_SCHEMAS.include?(schema)

found = false
parse_method = "parse_#{schema}_uri"
parsed_options = send(parse_method, value, datastore)
Rex::Socket::RangeWalker.new(parsed_options['RHOSTS']).each_ip do |ip|
results << datastore.merge(
parsed_options.merge('RHOSTS' => ip, 'UNPARSED_RHOSTS' => value)
)
found = true
end
unless found
raise RhostResolveError.new(value)
if perform_dns_resolution?(datastore)
found = false
Rex::Socket::RangeWalker.new(parsed_options['RHOSTS']).each_ip do |ip|
results << datastore.merge(
parsed_options.merge('RHOSTS' => ip, 'UNPARSED_RHOSTS' => value)
)
found = true
end
unless found
raise RhostResolveError.new(value)
end
else
results << datastore.merge(parsed_options.merge('UNPARSED_RHOSTS' => value))
end
else
found = false
Rex::Socket::RangeWalker.new(value).each_host do |rhost|
if perform_dns_resolution?(datastore)
found = false
Rex::Socket::RangeWalker.new(value).each_host do |rhost|
overrides = {}
overrides['UNPARSED_RHOSTS'] = value
overrides['RHOSTS'] = rhost[:address]
set_hostname(datastore, overrides, rhost[:hostname])
results << datastore.merge(overrides)
found = true
end
unless found
raise RhostResolveError.new(value)
end
else
overrides = {}
overrides['UNPARSED_RHOSTS'] = value
overrides['RHOSTS'] = rhost[:address]
set_hostname(datastore, overrides, rhost[:hostname])
overrides['RHOSTS'] = value
set_hostname(datastore, overrides, value)
results << datastore.merge(overrides)
found = true
end
unless found
raise RhostResolveError.new(value)
end
end
rescue ::Interrupt
Expand Down Expand Up @@ -344,6 +358,14 @@ def parse_tcp_uri(value, datastore)

protected

# @param [Msf::DataStore] datastore
# @return [Boolean] True if DNS resolution should be performed the RHOST values, false otherwise
def perform_dns_resolution?(datastore)
# If a socks proxy has been configured, don't perform DNS resolution - so that it instead happens via the proxy
# rex-socket does not currently support socks4a. SAPNI may need to be added to this list.
!(datastore['PROXIES'].to_s.include?(Rex::Socket::Proxies::ProxyType::HTTP) || datastore['PROXIES'].to_s.include?(Rex::Socket::Proxies::ProxyType::SOCKS5))
end

def set_hostname(datastore, result, hostname)
hostname = Rex::Socket.is_ip_addr?(hostname) ? nil : hostname
result['RHOSTNAME'] = hostname if datastore['RHOSTNAME'].blank?
Expand Down
27 changes: 27 additions & 0 deletions spec/lib/msf/core/opt_proxies_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding:binary -*-

require 'spec_helper'

RSpec.describe Msf::OptProxies do

valid_values = [
nil,
'',
' ',
'socks5:198.51.100.1:1080',
'socks4:198.51.100.1:1080',
'http:198.51.100.1:8080,socks4:198.51.100.1:1080',
'http:198.51.100.1:8080, socks4:198.51.100.1:1080',
'sapni:198.51.100.1:8080, socks4:198.51.100.1:1080',
].map { |value| { value: value, normalized: value } }

invalid_values = [
{ :value => 123 },
{ :value => 'foo(' },
{ :value => 'foo:198.51.100.1:8080' },
{ :value => 'foo:198.51.100.18080' },
{ :value => 'foo::' },
]

it_behaves_like "an option", valid_values, invalid_values, 'proxies'
end
4 changes: 2 additions & 2 deletions spec/lib/msf/core/opt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

context 'Proxies' do
subject { described_class::Proxies }
it { is_expected.to be_a(Msf::OptString) }
it { is_expected.to be_a(Msf::OptProxies) }
end

context 'RHOST' do
Expand Down Expand Up @@ -89,7 +89,7 @@

context 'Proxies()' do
subject { described_class::Proxies(default) }
it { is_expected.to be_a(Msf::OptString) }
it { is_expected.to be_a(Msf::OptProxies) }
specify 'sets default' do
expect(subject.default).to eq(default)
end
Expand Down
Loading

0 comments on commit 0a41f51

Please sign in to comment.