Skip to content

Add find_writable_directories to Msf::Post::File#21232

Open
bcoles wants to merge 1 commit intorapid7:masterfrom
bcoles:file-find_writable_directories
Open

Add find_writable_directories to Msf::Post::File#21232
bcoles wants to merge 1 commit intorapid7:masterfrom
bcoles:file-find_writable_directories

Conversation

@bcoles
Copy link
Copy Markdown
Contributor

@bcoles bcoles commented Apr 4, 2026

Add a method to discover writable directories on Unix targets using the find command. This is useful in post-exploitation scenarios where a module needs to locate a writable staging path.

Parameters:

  • path: base directory to search (default: /)
  • max_depth: find -maxdepth limit (default: 2)
  • timeout: seconds before killing the remote process (default: 15)
    - user/group: filter by owner and/or group with -perm checks

The method uses a three-tier strategy to prevent a long-running find from tying up the session's shell channel:

1. GNU coreutils timeout - wraps the find command directly
2. perl alarm() - fallback for BSD, macOS, and Solaris targets
3. When neither is available, max_depth is capped at 1 and a warning is emitted to alert the operator

The remote timeout deadline is set 5 seconds shorter than the cmd_exec deadline so the server-side kill fires first and partial results are still collected.

Raises on Windows sessions. Returns an array of absolute paths, or nil on failure. Non-absolute lines (e.g. find error messages) are filtered from the output.

@bcoles
Copy link
Copy Markdown
Contributor Author

bcoles commented Apr 4, 2026

Broken tests are not my fault.

Comment thread lib/msf/core/post/file.rb Outdated
@bwatters-r7
Copy link
Copy Markdown
Contributor

Broken tests are not my fault.

Finished in 0.29876 seconds (files took 3.23 seconds to load)
26 examples, 15 failures

Failed examples:

rspec ./spec/lib/msf/core/post/file_spec.rb:113 # Msf::Post::File#find_writable_directories on Unix omits the remote timeout wrapper when timeout is 0
rspec ./spec/lib/msf/core/post/file_spec.rb:118 # Msf::Post::File#find_writable_directories on Unix passes user flag when specified
rspec ./spec/lib/msf/core/post/file_spec.rb:80 # Msf::Post::File#find_writable_directories on Unix passes a custom timeout to the remote command and cmd_exec
rspec ./spec/lib/msf/core/post/file_spec.rb:128 # Msf::Post::File#find_writable_directories on Unix passes both user and group flags when specified
rspec ./spec/lib/msf/core/post/file_spec.rb:123 # Msf::Post::File#find_writable_directories on Unix passes group flag when specified
rspec ./spec/lib/msf/core/post/file_spec.rb:85 # Msf::Post::File#find_writable_directories on Unix falls back to perl alarm when timeout is not available but perl is
rspec ./spec/lib/msf/core/post/file_spec.rb:133 # Msf::Post::File#find_writable_directories on Unix uses custom path and max_depth
rspec ./spec/lib/msf/core/post/file_spec.rb:138 # Msf::Post::File#find_writable_directories on Unix returns nil on failure
rspec ./spec/lib/msf/core/post/file_spec.rb:65 # Msf::Post::File#find_writable_directories on Unix filters out non-absolute paths and error lines
rspec ./spec/lib/msf/core/post/file_spec.rb:105 # Msf::Post::File#find_writable_directories on Unix warns without capping when max_depth is already safe and no timeout mechanism exists
rspec ./spec/lib/msf/core/post/file_spec.rb:70 # Msf::Post::File#find_writable_directories on Unix returns an empty array when no directories are found
rspec ./spec/lib/msf/core/post/file_spec.rb:97 # Msf::Post::File#find_writable_directories on Unix caps max_depth to 2 and warns when neither timeout nor perl is available
rspec ./spec/lib/msf/core/post/file_spec.rb:75 # Msf::Post::File#find_writable_directories on Unix wraps find with the remote timeout utility when available
rspec ./spec/lib/msf/core/post/file_spec.rb:60 # Msf::Post::File#find_writable_directories on Unix returns writable directories
rspec ./spec/lib/msf/core/post/file_spec.rb:48 # Msf::Post::File#find_writable_directories on Windows raises an error

But you wrote the tests that are failing? 😕

@bwatters-r7
Copy link
Copy Markdown
Contributor

I am not a spec expert, but it passes if I add some method stubs:

[ruby-3.3.8@metasploit-framework]((HEAD detached at upstream/pr/21232)) tmoose@ubuntu-dev2024:~/rapid7/metasploit-framework$ git diff
diff --git a/spec/lib/msf/core/post/file_spec.rb b/spec/lib/msf/core/post/file_spec.rb
index 786b1ba884f..5251c7ab49f 100644
--- a/spec/lib/msf/core/post/file_spec.rb
+++ b/spec/lib/msf/core/post/file_spec.rb
@@ -6,6 +6,13 @@ RSpec.describe Msf::Post::File do
     described_mixin = described_class
     klass = Class.new do
       include described_mixin
+
+      def session; end
+      def cmd_exec(*_args); end
+      def command_exists?(*_args); end
+      def print_warning(*_args); end
+      def print_error(*_args); end
+      def elog(*_args); end
     end
     klass.allocate
   end
[ruby-3.3.8@metasploit-framework]((HEAD detached at upstream/pr/21232)) tmoose@ubuntu-dev2024:~/rapid7/metasploit-framework$ msf_dockerdb_run_rspec spec/lib/msf/core/post/file_spec.rb
Overriding user environment variable 'OPENSSL_CONF' to enable legacy functions.
Run options:
  include {:focus=>true}
  exclude {:acceptance=>true}

All examples were filtered out; ignoring {:focus=>true}

Randomized with seed 10582
Msf::Post::File ..........................

Top 10 slowest examples (0.02619 seconds, 10.1% of total time):
  Msf::Post::File#_can_echo? should return false for "%APPDATA%"
    0.00533 seconds ./spec/lib/msf/core/post/file_spec.rb:37
  Msf::Post::File#find_writable_directories on Windows raises an error
    0.00416 seconds ./spec/lib/msf/core/post/file_spec.rb:55
  Msf::Post::File#find_writable_directories on Unix warns without capping when max_depth is already safe and no timeout mechanism exists
    0.00274 seconds ./spec/lib/msf/core/post/file_spec.rb:112
  Msf::Post::File#find_writable_directories on Unix filters out non-absolute paths and error lines
    0.00235 seconds ./spec/lib/msf/core/post/file_spec.rb:72
  Msf::Post::File#find_writable_directories on Unix falls back to perl alarm when timeout is not available but perl is
    0.0023 seconds ./spec/lib/msf/core/post/file_spec.rb:92
  Msf::Post::File#find_writable_directories on Unix passes a custom timeout to the remote command and cmd_exec
    0.00196 seconds ./spec/lib/msf/core/post/file_spec.rb:87
  Msf::Post::File#find_writable_directories on Unix caps max_depth to 2 and warns when neither timeout nor perl is available
    0.0019 seconds ./spec/lib/msf/core/post/file_spec.rb:104
  Msf::Post::File#find_writable_directories on Unix returns nil on failure
    0.00182 seconds ./spec/lib/msf/core/post/file_spec.rb:145
  Msf::Post::File#find_writable_directories on Unix passes user flag when specified
    0.00182 seconds ./spec/lib/msf/core/post/file_spec.rb:125
  Msf::Post::File#find_writable_directories on Unix returns writable directories
    0.00181 seconds ./spec/lib/msf/core/post/file_spec.rb:67

Finished in 0.259 seconds (files took 2.78 seconds to load)
26 examples, 0 failures

Randomized with seed 10582
Coverage report generated for RSpec to /home/tmoose/rapid7/metasploit-framework/coverage.
Line Coverage: 17.78% (2357 / 13253)

@bcoles bcoles force-pushed the file-find_writable_directories branch 2 times, most recently from 8245708 to 4e964f7 Compare April 15, 2026 14:57
@bcoles
Copy link
Copy Markdown
Contributor Author

bcoles commented Apr 15, 2026

Broken tests are not my fault.

But you wrote the tests that are failing? 😕

Lulled into a false sense of security by always-broken tests.

I am not a spec expert, but it passes if I add some method stubs:

The tests passed locally without the database when run directly, although now break locally since the mkdir spec tests were added. Regardless, this should be fixed.

@bwatters-r7 bwatters-r7 self-assigned this Apr 15, 2026
@smcintyre-r7 smcintyre-r7 requested a review from Copilot April 16, 2026 13:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new helper on Msf::Post::File to enumerate writable directories on Unix targets (intended for selecting staging paths during post-exploitation), plus RSpec coverage for the new behavior.

Changes:

  • Add Msf::Post::File#find_writable_directories which runs find and parses absolute-path results.
  • Add specs covering Windows rejection, output filtering, timeout passthrough, max_depth warnings, and user/group options.
  • Update the spec file to load spec_helper.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
lib/msf/core/post/file.rb Introduces find_writable_directories and its command construction / parsing / error handling.
spec/lib/msf/core/post/file_spec.rb Adds unit tests validating the new helper’s behavior and command string expectations.

Comment thread lib/msf/core/post/file.rb Outdated
Comment thread lib/msf/core/post/file.rb
Comment thread lib/msf/core/post/file.rb Outdated
Comment thread lib/msf/core/post/file.rb Outdated
Comment thread spec/lib/msf/core/post/file_spec.rb
@bcoles bcoles force-pushed the file-find_writable_directories branch from 4e964f7 to a9f4f3c Compare April 16, 2026 13:56
@bcoles bcoles requested a review from Copilot April 16, 2026 13:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread lib/msf/core/post/file.rb Outdated
Comment thread lib/msf/core/post/file.rb Outdated
Comment thread spec/lib/msf/core/post/file_spec.rb
Comment thread lib/msf/core/post/file.rb Outdated
# Find writable directories under +path+ on a Unix system.
#
# When neither +user+ nor +group+ is specified, uses find's +-writable+ flag
# which checks effective access for the current user. When +user+ and/or
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

which checks effective access for the current user.

I think this is a bit misleading.

I noticed that this will result in false negatives if the user has permissions to a directory via their group membership. That is, if user metasploit is part of the wheel group and the wheel group has write permission on a folder, this won't catch it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All we want is a writable folder so we can stop hardcoding /tmp everywhere.

# grep -rn "/tmp" modules/exploits/**/local/ modules/post/ | wc -l
239

I've removed all the user and group arguments. This was only a nice-to-have feature - not something I care enough about to maintain and rarely likely to be useful.

Add a method to discover writable directories on Unix targets using the
`find` command. This is useful in post-exploitation scenarios where a
module needs to locate a writable staging path.

Parameters:
- path: base directory to search (default: /)
- max_depth: find -maxdepth limit (default: 2)
- timeout: maximum seconds for cmd_exec to wait (default: 15)

Raises on Windows sessions. Returns an array of absolute paths, or nil
on failure. Non-absolute lines (e.g. find error messages) are filtered
from the output.
@bcoles bcoles force-pushed the file-find_writable_directories branch from a9f4f3c to 6821066 Compare April 16, 2026 16:32
@bcoles bcoles requested a review from Copilot April 16, 2026 16:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

5 participants