metasploit-framework icon indicating copy to clipboard operation
metasploit-framework copied to clipboard

Issue interpreting 'localhost' as '127.0.0.1'

Open jheysel-r7 opened this issue 2 years ago • 6 comments

Steps to reproduce

Run the wp_bookingpress_category_services_sqli module and set RHOSTS to localhost

msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > run
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unknown: Cannot reliably check exploitability. Unable to get wp-nonce as an unauthenticated user "set ForceExploit true" to override check result.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable.
[*] Extracting credential information
Wordpress User Credentials
==========================

 Username       Email                         Hash
 --------       -----                         ----
 admin          [email protected]               $P$BfxUckldN6AiHPD0BK6jg58se2b.aL.
 hackerman      [email protected]     $P$BESfz7bqSOY8VkUfuYXAZ/bT5E36ww/
 mr_metasploit  [email protected]  $P$BDb8pIfym5dS6WTnNU8vU5Uk6i89fk.
 msfuser        [email protected]            $P$BpITVDPiqOZ7fyQbI5g9rsgUvZQFBd1
 todd           [email protected]             $P$BnlpkVgxGFWnmvdDQ3JStgpIx8LMFj0

[*] Auxiliary module execution completed

Notice it says it's running the module against: 0.0.0.1 and then Auxiliary aborted due to failure: unknown: Cannot reliably check exploitability

Now this isn't a solution but is interesting behaviour, if you: include Msf::Auxiliary::Scanner in the module and reload it, the module has no issue interpreting localhost as 127.0.0.1

I had wrongfully included Msf::Auxiliary::Scanner when I first wrote the module, it was removed during code review. I noticed this when I went back to record a demo for this module, when all of a sudden setting RHOSTS to localhost started causing this problem.

jheysel-r7 avatar Jan 10 '23 18:01 jheysel-r7

Looks like this gets caused by the Rex socket RangeWalker implementation which the rhost walker calls under the hood:

Resolving 127.0.0.1:

>> Rex::Socket::RangeWalker.new('127.0.0.1').to_enum(:each_host).to_a
=> [{:address=>"127.0.0.1", :hostname=>nil}]

Resolving localhost:

>> Rex::Socket::RangeWalker.new('localhost').to_enum(:each_host).to_a
=> [{:address=>"0.0.0.1", :hostname=>"localhost"}, {:address=>"127.0.0.1", :hostname=>"localhost"}]

If we dig a bit deeper, under the covers the Rex::Socket.addreses function returns the ipv4 and ipv6 address for localhost which the range walker doesn't seem to handle correctly as part of the next_host logic:

=> [{:address=>"0.0.0.1", :hostname=>"localhost"}, {:address=>"127.0.0.1", :hostname=>"localhost"}]
>> Rex::Socket.getaddresses('127.0.0.1')
=> ["127.0.0.1"]
>> Rex::Socket.getaddresses('localhost')
=> ["::1", "127.0.0.1"]

A quick sanity check of Metasploit 5.0.101 shows this isn't a regression in the Rex::Socket resolving logic:

>> Rex::Socket::RangeWalker.new('127.0.0.1').to_enum(:each).to_a
=> ["127.0.0.1"]
>> Rex::Socket::RangeWalker.new('localhost').to_enum(:each).to_a
=> ["0.0.0.1", "127.0.0.1"]

adfoster-r7 avatar Jan 10 '23 20:01 adfoster-r7

This discussion might be relevant to rex-socket #43 and @gwillcox-r7 as the behavior you're currently observing and upon which this output relies ends up falling-through Rex Socket to the OS resolver running Ruby (and leaking DNS requests through the Framework host...). On order to achieve those localhost lookups using the Rex based Resolver used to plug the DNS leak and pivot our lookups, we need to do the following:

Rex::Socket.getaddresses('localhost')
SocketError: getaddrinfo: Name or service not known
resolver = Rex::Socket.class_variable_get(:@@resolver)
resolver.cache.add_static('localhost', '127.0.0.1')
=> 0
resolver.cache.add_static('localhost', '::1', 'AAAA')
=> 0
Rex::Socket.getaddresses('localhost')
=> ["127.0.0.1", "::1"]

alternatively, i can change how the CachedResolver reads-in the system's local hostsfile to handle localhost in 4 and 6 modalities (its explicitly excluded in the init method).

sempervictus avatar Jan 17 '23 03:01 sempervictus

I believe this bug fix is unrelated/separate to https://github.com/rapid7/rex-socket/pull/43

We're already resolving localhost to an ipv4 and ipv6 address ["::1", "127.0.0.1"] - it's just a bug in the Rex::Socket::RangeWalker implementation which causes the result to be two ipv4 addresses that are yielded ["0.0.0.1", "127.0.0.1"]

adfoster-r7 avatar Jan 17 '23 15:01 adfoster-r7

Ah, thanks for the correction. Interesting problem, reproducible from my version as well:

Rex::Socket::RangeWalker.new('localhost').to_enum(:each).to_a
=> ["127.0.0.1", "0.0.0.1"]

Probably a rex-socket issue in that case, which i'm happy to triage and try to fix in there since i dig around those pieces quite often.

sempervictus avatar Jan 17 '23 15:01 sempervictus

Got it:

> before.to_enum.to_a
=> ["127.0.0.1", "0.0.0.1"]
> after.to_enum.to_a
=> ["127.0.0.1", "::1"]

Rex::Socket::Host needed to "figure out" whether it's IPv6 or not during its init. PR incoming on that side.

sempervictus avatar Jan 18 '23 03:01 sempervictus

Worth mentioning that not all systems resolve dual-stack for localhost:

$ grep localhost /etc/hosts
127.0.0.1	localhost
::1     ip6-localhost ip6-loopback

and when using the native resolver:

> Rex::Socket.class_variable_get(:@@resolver)
=> nil
> Rex::Socket::RangeWalker.new('localhost').to_enum.to_a
=> ["127.0.0.1"]

sempervictus avatar Jan 18 '23 03:01 sempervictus