urbanadventurer / WhatWeb

Next generation web scanner
https://www.morningstarsecurity.com/research/whatweb
GNU General Public License v2.0
5.2k stars 885 forks source link

Support for specifying a specific network interface to use #302

Closed mzpqnxow closed 1 year ago

mzpqnxow commented 4 years ago

Hello, I recently started experimenting with WhatWeb and noticed what I think to be a missing feature

The use-case: I have a system I use for evaluating security research tools. This system has multiple Internet interfaces which can all route to the public Internet. It would be nice if I could explicitly tell WhatWeb which interface to bind to via the device name or via the device IP address rather than leaving it up to the OS routing table to choose

I realize there are ways to work-around this, but the cleanest (and correct) way to do it in my opinion is in the application. Making system-wide routing table changes or doing anything else that might require superuser privileges would be an undesirable solution for several reason.

In my opinion this is not only a valid use-case but possibly a common one, for researchers using specific IP addresses/interfaces to evaluate specific tools from a single machine

So, the questions I have boil down to:

  1. Is this supported already?
  2. If not, will it be implemented by the developers?
  3. If we can agree that it's a desirable feature, I can try my hand at sending a PR, though I admit I am not very fluent in Ruby.

At the end of the day I can't imagine it's more than a bind() call on the underlying socket used to make the connection, though I assume there's some higher-level abstraction to the socket creation itself

Thanks, I appreciate the work on this tool, it's really impressive!

EDIT: See the comments below, it's not currently supported, but not very difficult to add for any fluent Ruby speaker

mzpqnxow commented 4 years ago

I took a few moments to track down what would need to be changed:

in lib/extend-http.rb:

class ExtendedHTTP < Net::HTTP #:nodoc:
  include Net
...

The current code:

  def initialize(address, port = nil)
    @address = address
    @port    = (port || HTTP.default_port)
    @local_host = nil
    @local_port = nil

This would need to change to:

  def initialize(address, port = nil, local_host = nil, local_port = nil)
    @address = address
    @port    = (port || HTTP.default_port)
    @local_host = local_host
    @local_port = local_port

I tested it by just hardcoding an IP address for local_host and it worked as desired. There would just need to be some logic added to have the local_host argument propagate from the Getopt logic in /whatweb:

  ...
  ['-b', '--bind', GetoptLong::REQUIRED_ARGUMENT],
  ...
  opts.each do |opt, arg|
    case opt
    when '-b', '--bind'
      bind = arg
  ...

As I said, I'm really not a Ruby dev and the syntax confuses the hell out of me. But maybe it would be a quick job for you (or someone else) to add the logic so that a the bind_ip argument makes it to the ExtendedHTTP initialization?

I have no preference as to whether a local_port should be supported, but it might as well be supported by tokenizing the --bind argument, splitting ip:port into the two values if the : is present in the argument

For context- the local_host and local_port would then be passed to the session establishment in ExtendedHTTP:connect, further down in that class:

  def connect
    @raw = []
    if proxy?
      conn_address = proxy_address
      conn_port    = proxy_port
    else
      conn_address = address
      conn_port    = port
    end

    D "opening connection to #{conn_address}:#{conn_port}..."
    s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do
      TCPSocket.open(conn_address, conn_port, @local_host, @local_port)
    end

I'm not sure if the TCPSocket.open() call handles the DNS or not, but ideally the DNS query would also go out the specified interface. Maybe that TCPSocket class supports a FQDN as an argument? You would know better than I...

@urbanadventurer I'd appreciate your help/feedback on this- apologies I'm not able to give a full diff/PR for this at the moment. I think even if I did you would need to fix it up because my lack of knowledge of basic Ruby idioms would make the code clumsy :)

urbanadventurer commented 3 years ago

I made some notes about this on pull request #303

urbanadventurer commented 3 years ago

After doing some experiments with this feature on https://github.com/urbanadventurer/WhatWeb/tree/bind-interface, I have found that this feature cannot be implemented using the TCPSocket.open(conn_address, conn_port, @local_host, @local_port) function.

I implemented:

The results were not good. This only works under Linux and even then it does not work well. Under macOS it ignores the specified interface and port and uses the appropriate interface based on the routing table.

With Linux, it will not auto discover a local port when one is not specified. If a local port is specified, it runs into port reuse problems after a single scan. Managing the local ports used by WhatWeb for scanning is beyond what I'm interested to implement for this feature.

As this stage, I won't implement this feature.

I'm open to any suggestions for giving advice to users on how to specify an interface through other means, for example with a web proxy.