Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture plugin #16298

Merged
merged 27 commits into from
Mar 22, 2022
Merged

Capture plugin #16298

merged 27 commits into from
Mar 22, 2022

Conversation

smashery
Copy link
Contributor

@smashery smashery commented Mar 7, 2022

This introduces a new plugin, capture, which activates a range of spoof and capture modules, in an attempt to capture credentials from other systems on the network. It provides no new modules in and of itself, but rather a way to orchestrate them together in a more cohesive way.

This can be used to run modules on remote sessions, although currently UDP services (including all the active spoofing ones) are not supported remotely.

To use this feature, load the plugin with load capture and then run it with capture start, and the settings below:

Usage: capture start -i <ip> [options]

OPTIONS:

    --basic        Use Basic auth for HTTP listener (default is NTLM)
    --cert         Path to SSL cert for encrypted communication
    --configfile   Path to a config file
    --hashdir      Directory to store hash results
    --logfile      Path to store logs
    --regex        Regex to match for spoofing
    --session      Session to bind on
    --spoofip      IP to use for spoofing (poisoning); default is the bound IP address
    --stdout       Show results in stdout
    -h             Display this message
    -i             IP to bind to
    -v             Verbose output

To stop a capture, run capture stop

Usage: capture stop [options]

OPTIONS:

    --session   Session to stop (otherwise all capture jobs on all sessions will be stopped)
    -h          Display this message

Other settings are controlled by a config file; a sample one (the default one used) is found in the metasploit directory, in the data/capture_config.yaml file. This includes the ability to disable certain services, set a specific NTLM challenge and domain name.

@smashery
Copy link
Contributor Author

smashery commented Mar 8, 2022

Known issue: SMB and HTTP servers do not support different Comms channels (e.g. forwarding via Meterpreter or SSH), so these do not work; but this should be resolved in #16250

@smashery smashery marked this pull request as ready for review March 8, 2022 06:23
@smashery
Copy link
Contributor Author

smashery commented Mar 8, 2022

Open to the possibility that we set the URIPath for the HTTP(S) capture modules to be /.*, to just catch everything.

Copy link
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some comments with some suggestions based on my testing so far.

plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
smashery and others added 2 commits March 10, 2022 17:17
Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com>

mod = framework.modules.create(module_name)
# Bail if we couldn't
unless mod
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a pattern for extracting module load errors that we follow over here

unless mod
# Checks to see if we have any load_errors for the current module.
# and if so, returns them to the user.
load_error = framework.modules.load_error_by_name(mod_name)
if load_error
print_error("Failed to load module: #{load_error}")
return false
end
unless mod_resolved
elog("Module #{mod_name} not found, and no loading errors found. If you're using a custom module" \
' refer to our wiki: https://github.com/rapid7/metasploit-framework/wiki/Running-Private-Modules')
# Avoid trying to use the search result if it exactly matches
# the module we were trying to load. The module cannot be
# loaded and searching isn't going to change that.
mods_found = cmd_search('-I', '-u', *args)
end
unless mods_found
print_error("Failed to load module: #{mod_name}")
return false
end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I guess in that case, it's doing some searching to try to figure out what the user wants; in this case, we're explicitly specifying the exact module, and don't want it to go searching. So I'd imagine that most of that we don't want to use; but I can use the load_error_by_name method to give a better error?

@jmartin-tech jmartin-tech added the needs-linting The module needs additional work to pass our automated linting rules label Mar 10, 2022
@github-actions
Copy link

Thanks for your pull request! Before this pull request can be merged, it must pass the checks of our automated linting tools.

We use Rubocop and msftidy to ensure the quality of our code. This can be ran from the root directory of Metasploit:

rubocop <directory or file>
tools/dev/msftidy.rb <directory or file>

You can automate most of these changes with the -a flag:

rubocop -a <directory or file>

Please update your branch after these have been made, and reach out if you have any problems.

Copy link
Contributor

@jmartin-tech jmartin-tech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts from a group review.

data/capture_config.yaml Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
data/capture_config.yaml Outdated Show resolved Hide resolved
plugins/capture.rb Outdated Show resolved Hide resolved
@smcintyre-r7 smcintyre-r7 self-assigned this Mar 11, 2022
Comment on lines +2 to +5
ntlm_challenge: "1122334455667788"
ntlm_domain: anonymous
http_basic: no
ssl_cert: null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it makes sense to make these values belong to the HTTP or HTTPS services? Is there a reason they should be global per config file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If moved into per service consider all hardcoded defaults could move into config and the configure_* methods could be consolidated to iterate the proposed datastore map in a meta-programing format similar to:

def configure_module(datastore, config)
  config[:datastore].each do |option|
    datastore[option.to_s] = config[:datastore][option]
  end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no particular reason they should be global; just an artifact of the earlier request to have the command line options defaults be pulled from a file. That said, even if we did it per-service, I think having an overall default makes sense, to provide a default in its absence.

I can see how this suggested approach would be much more flexible; but I'm weighing up flexibility vs usability in my mind. Given a fixed NTLM challenge is used for rainbow tables, would anyone want to provide a different value for each different service? Would there be an opsec use case for different NTLM domains for each service (I'd think someone would probably want the same for all of them)? Is providing a different TLS cert for each service a valuable thing? (I'd think that 99% of the time, we just want them to be the same).

To me, this feature is most useful because it packages everything up neatly for the common use case, and it's easy for someone to understand how to use it. I should clarify: I'm happy to make that change if you think it's important; just presenting my view as an operator.

If we did want to go ahead, though, the question to answer is: how do these settings interact with the individual command line options? Would providing a command line option for SSLCert override all of the individual datastore settings in the config file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sound reasoning to me. I think having the a default in place is good, customizing the services down the line can be another iteration.

The ntlm_challenge value seems like a fingerprintable item that may be worth thinking more on in the future. As things iterate I could see http_basic for a configured service being something a user might want to very and fallback to the global default for some services.

Shifting hardcoded port values and such currently in the configure_* methods may help users understand what is running at a glance in the config instead of having to view the plugin source. That also can be deferred as an enhancement.

@smcintyre-r7 smcintyre-r7 removed the needs-linting The module needs additional work to pass our automated linting rules label Mar 21, 2022
Multiple modules provide a "Capture" action that would collide with this
name. Rename it to `captureg` for Capture-Global.
@smcintyre-r7 smcintyre-r7 merged commit da16aad into rapid7:master Mar 22, 2022
@smcintyre-r7
Copy link
Contributor

Release Notes

This adds the new "capture" plugin which can be used to easily start and stop credential-capturing services.

@smcintyre-r7 smcintyre-r7 added the rn-enhancement release notes enhancement label Mar 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants