Skip to content

TFTP Fetch Fileless issues #21801

Description

@bwatters-r7

Summary

FETCH_COMMAND=TFTP cannot currently be combined with any FETCH_FILELESS mode other than none — the two required options FETCH_WRITABLE_DIR and FETCH_FILENAME are impossible to satisfy at the same time when launching via exploit -j/msfvenom (i.e. any non-interactive path).

Root cause

FETCH_WRITABLE_DIR and FETCH_FILENAME are registered as required: true, with a conditions: entry meant to relax that requirement once fileless mode is active:

https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/adapter/fetch/linux_options.rb#L9-L11

Msf::OptString.new('FETCH_FILENAME', [ false, '...', Rex::Text.rand_text_alpha(rand(8..12))], regex: %r{^[^\s/\\]*$}, conditions: ['FETCH_FILELESS', '==', 'none']),
...
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, '...', './'], regex: /^\S*$/, conditions: ['FETCH_FILELESS', '==', 'none'])

But Msf::OptCondition.show_option — the thing that actually reads conditions: — is only ever called from console-layer helpers (show options, tab-completion, set validation):

https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/ui/console/module_option_validation.rb#L33,L43,L70

It is never consulted by the core datastore/module launch-time validator (Msf::OptBase#valid?, used by exploit -j / msfvenom / RPC). That validator only looks at the option's static required flag:

https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/opt_base.rb#L119-L123

def valid?(value, check_empty: true, datastore: nil)
  if check_empty && required?
    return false if (value.nil? || value.to_s.empty?)
  end
  ...

So at launch time FETCH_WRITABLE_DIR is always required to be non-empty, regardless of FETCH_FILELESS.

Meanwhile, the TFTP-specific fetch-command generator explicitly requires the opposite whenever it runs:

https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/payload/adapter/fetch.rb#L281-L287

def _check_tftp_file
  # Older Linux tftp clients do not support saving the file under a different name
  unless datastore['FETCH_WRITABLE_DIR'].blank? && datastore['FETCH_FILENAME'].blank?
    print_error('The Linux TFTP client does not support saving a file under a different name than the URI.')
    fail_with(Msf::Module::Failure::BadConfig, 'FETCH_WRITABLE_DIR and FETCH_FILENAME must be blank when using the tftp client')
  end
end

The two checks contradict each other for FETCH_COMMAND=TFTP combined with FETCH_FILELESS != none: the launch validator rejects a blank FETCH_WRITABLE_DIR, and _check_tftp_file rejects a non-blank one. There is no value that satisfies both.

Reproduction

msf6 > use exploit/multi/handler
msf6 exploit(multi/handler) > set PAYLOAD cmd/linux/tftp/x64/meterpreter_reverse_tcp
msf6 exploit(multi/handler) > set LHOST <attacker>
msf6 exploit(multi/handler) > set FETCH_SRVHOST <attacker>
msf6 exploit(multi/handler) > set FETCH_SRVPORT 69
msf6 exploit(multi/handler) > set FETCH_COMMAND TFTP
msf6 exploit(multi/handler) > set FETCH_FILELESS shell-search
msf6 exploit(multi/handler) > exploit -j -z
[-] Msf::OptionValidateError One or more options failed to validate: FETCH_WRITABLE_DIR.

Manually blanking the option (unset --clear FETCH_WRITABLE_DIR FETCH_FILENAME) gets past that error, but then hits _check_tftp_file's own rejection instead:

msf6 exploit(multi/handler) > set --clear FETCH_WRITABLE_DIR
msf6 exploit(multi/handler) > set --clear FETCH_FILENAME
msf6 exploit(multi/handler) > exploit -j -z
[-] Exploit failed: bad-config: FETCH_WRITABLE_DIR and FETCH_FILENAME must be blank when using the tftp client

i.e. FETCH_COMMAND=TFTP + fileless mode is unreachable through the normal set/exploit -j/msfvenom flow at all. (It's only reachable by poking datastore[...] directly from irb, bypassing option validation entirely — which is how I worked around it to confirm the underlying fetch/cleanup logic in #21548 was otherwise correct.)

Suggested fix

Msf::OptBase#valid? (or its caller in the module/datastore validation path) should consult conditions: before enforcing required?, the same way the console layer already does via Msf::OptCondition.show_option. Alternatively, as a narrower fix scoped to this module family, FETCH_WRITABLE_DIR/FETCH_FILENAME could register as required: false and have _check_tftp_file (and the equivalent non-TFTP paths) do their own presence/blankness enforcement based on FETCH_FILELESS, rather than relying on the base option's required: flag at all.

Environment

Found while testing #21548 (fail-safe fileless fetch payloads) against real hardware. Not a bug introduced by that PR — the conditions: metadata and this validation gap both predate it.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions