Skip to content

Commit

Permalink
Merge pull request #67 from zeroSteiner/fix/rex-getaddresses
Browse files Browse the repository at this point in the history
Fix .rex_getaddrinfo inconsistencies
  • Loading branch information
adfoster-r7 authored Nov 22, 2024
2 parents 73ffece + 8640709 commit 51a6f43
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 23 deletions.
51 changes: 28 additions & 23 deletions lib/rex/socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'thread'
require 'resolv'
require 'rex/exceptions'
require 'dnsruby'

module Rex

Expand Down Expand Up @@ -939,26 +940,30 @@ def self.rex_gethostbyname(name, resolver: @@resolver)
# @param resolver [Rex::Proto::DNS::CachedResolver] Resolver to query for the name
# @return [Array] Array mimicking the native getaddrinfo return type
def self.rex_getaddrinfo(name, resolver: @@resolver)
v4, v6 = self.rex_resolve_hostname(name, resolver: resolver)
# Build response array
getaddrinfo = []
v4.each do |a4|
getaddrinfo << Addrinfo.new(
self.to_sockaddr(a4.address.to_s, 0),
::Socket::AF_INET,
::Socket::SOCK_STREAM,
::Socket::IPPROTO_TCP,
) unless v4.empty?
v4_sockaddrs = []
v6_sockaddrs = []

if name =~ /\A\d+\Z/ && name.to_i.between?(0, 0xffffffff)
v4_sockaddrs << self.to_sockaddr(name.to_i, 0)
elsif name =~ /\A0x[0-9a-fA-F]+\Z/ && name.to_i(16).between?(0, 0xffffffff)
v4_sockaddrs << self.to_sockaddr(name.to_i(16), 0)
elsif self.is_ipv4?(name)
v4_sockaddrs << self.to_sockaddr(name, 0)
elsif self.is_ipv6?(name)
v6_sockaddrs << self.to_sockaddr(name, 0)
else
v4, v6 = self.rex_resolve_hostname(name, resolver: resolver)
v4.each do |a4|
v4_sockaddrs << self.to_sockaddr(a4.address.to_s, 0)
end
v6.each do |a6|
v6_sockaddrs << self.to_sockaddr(a6.address.to_s, 0)
end
end
v6.each do |a6|
getaddrinfo << Addrinfo.new(
self.to_sockaddr(a6.address.to_s, 0),
::Socket::AF_INET6,
::Socket::SOCK_STREAM,
::Socket::IPPROTO_TCP,
) unless v6.empty?

(v4_sockaddrs.map { |sa| [sa, ::Socket::AF_INET] } + v6_sockaddrs.map { |sa| [sa, ::Socket::AF_INET6] }).map do |sa, family|
Addrinfo.new(sa, family, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP)
end
return getaddrinfo
end


Expand All @@ -974,21 +979,21 @@ def self.rex_resolve_hostname(name, resolver: @@resolver)
) unless name.is_a?(String)
# Pull both record types
v4 = begin
resolver.send(name, ::Net::DNS::A).answer.select do |a|
resolver.send(name, ::Dnsruby::Types::A).answer.select do |a|
a.type == Dnsruby::Types::A
end.sort_by do |a|
self.addr_ntoi(a.address.address)
end
rescue
rescue StandardError
[]
end
v6 = begin
resolver.send(name, ::Net::DNS::AAAA).answer.select do |a|
resolver.send(name, Dnsruby::Types::AAAA).answer.select do |a|
a.type == Dnsruby::Types::AAAA
end.sort_by do |a|
self.addr_ntoi(a.address.address)
end
rescue
rescue StandardError
[]
end
# Emulate ::Socket's error if no responses found
Expand All @@ -1012,7 +1017,7 @@ def self.rex_getresources(name, typeclass, resolver: @@resolver)
if attribute.nil?
raise ArgumentError, "Invalid typeclass: #{typeclass}"
end
const = ::Net::DNS.const_get(typeclass)
const = Dnsruby::Types.const_get(typeclass)

resources = begin
resolver.send(name, const).answer.select do |a|
Expand Down
1 change: 1 addition & 0 deletions rex-socket.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "rspec"

spec.add_runtime_dependency "rex-core"
spec.add_runtime_dependency "dnsruby"
end
133 changes: 133 additions & 0 deletions spec/rex/socket_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@

it { expect { subject }.to raise_exception(::SocketError, 'getaddrinfo: nodename nor servname provided, or not known') }
end

context 'when passed in a numeric hostname' do
let(:mock_resolver) { double('Resolver', send: nil) }

before(:each) do
described_class._install_global_resolver(mock_resolver)
expect(mock_resolver).not_to receive(:send)
end

after(:each) do
described_class._install_global_resolver(nil)
end

context 'when passed in a decimal hostname' do
let(:hostname) { '0' }
let(:response_addresses) { ['0.0.0.0'] }

it { is_expected.to be_an(Array) }
it { expect(subject.size).to eq(1) }
it "should return the ASCII addresses" do
expect(subject).to include("0.0.0.0")
end
end

context 'when passed in a decimal hostname' do
let(:hostname) { '0x0' }
let(:response_addresses) { ['0.0.0.0'] }

it { is_expected.to be_an(Array) }
it { expect(subject.size).to eq(1) }
it "should return the ASCII addresses" do
expect(subject).to include("0.0.0.0")
end
end
end
end

describe '.portspec_to_portlist' do
Expand Down Expand Up @@ -389,4 +424,102 @@
end
end
end

describe '.rex_getaddrinfo' do
subject(:addrinfos) do
described_class.rex_getaddrinfo(hostname)
end

context 'with a hostname' do
let(:hostname) { 'localhost' }
it 'should call .rex_resolve_hostname' do
expect(described_class).to receive(:rex_resolve_hostname).with(hostname, {resolver: nil}).and_return([ [], [] ])
subject
end

it 'should return IPv4 and IPv6 addresses' do
expect(described_class).to receive(:rex_resolve_hostname).and_return([
[Dnsruby::RR::IN::A.new(address: '127.0.0.1')],
[Dnsruby::RR::IN::AAAA.new(address: '::1')]
])

expect(subject).to match_array([
have_attributes(ip_address: '127.0.0.1', afamily: ::Socket::AF_INET, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP),
have_attributes(ip_address: '::1', afamily: ::Socket::AF_INET6, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP)
])
end
end

context 'with an IPv4 name' do
let(:hostname) { '127.0.0.1' }
it 'should not call .rex_resolve_hostname' do
expect(described_class).to_not receive(:rex_resolve_hostname)
subject
end

it 'should return one IPv4 address' do
expect(subject).to match_array([
have_attributes(ip_address: '127.0.0.1', afamily: ::Socket::AF_INET, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP),
])
end
end

context 'with an IPv6 name' do
let(:hostname) { '::1' }
it 'should not call .rex_resolve_hostname' do
expect(described_class).to_not receive(:rex_resolve_hostname)
subject
end

it 'should return one IPv6 address' do
expect(subject).to match_array([
have_attributes(ip_address: '::1', afamily: ::Socket::AF_INET6, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP),
])
end
end

context 'with a decimal name' do
let(:hostname) { '255' }
it 'should not call .rex_resolve_hostname' do
expect(described_class).to_not receive(:rex_resolve_hostname)
subject
end

it 'should return one IPv4 address' do
expect(subject).to match_array([
have_attributes(ip_address: '0.0.0.255', afamily: ::Socket::AF_INET, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP),
])
end
end

context 'with an invalid decimal name' do
let(:hostname) { '4294967296' }
it 'should call .rex_resolve_hostname' do
expect(described_class).to receive(:rex_resolve_hostname).with(hostname, {resolver: nil}).and_raise(::SocketError.new('getaddrinfo: Name or service not known'))
expect { subject }.to raise_error(::SocketError)
end
end

context 'with a hexadecimal name' do
let(:hostname) { '0xff' }
it 'should not call .rex_resolve_hostname' do
expect(described_class).to_not receive(:rex_resolve_hostname)
subject
end

it 'should return one IPv4 address' do
expect(subject).to match_array([
have_attributes(ip_address: '0.0.0.255', afamily: ::Socket::AF_INET, socktype: ::Socket::SOCK_STREAM, protocol: ::Socket::IPPROTO_TCP),
])
end
end

context 'with an invalid hexadecimal name' do
let(:hostname) { '0x100000000' }
it 'should call .rex_resolve_hostname' do
expect(described_class).to receive(:rex_resolve_hostname).with(hostname, {resolver: nil}).and_raise(::SocketError.new('getaddrinfo: Name or service not known'))
expect { subject }.to raise_error(::SocketError)
end
end
end
end

0 comments on commit 51a6f43

Please sign in to comment.