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.

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.

if timeout > 0
if command_exists?('timeout')
# GNU coreutils timeout - common on Linux
cmd = "timeout #{timeout} #{find_cmd}"
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.

Busybox' timeout is using timeout [-t SECS] [-s SIG] PROG ARGS :/

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.

Rather than over-complicate it by trying to figure out which version of timeout is available, it may simply be better to strip out all the timeout/perl handling, pass the timeout argument to cmd_exec and print a warning if the depth is > 2.

@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)

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.

4 participants