Fix fail-safe fileless fetch review findings from PR #21548 - #5
Merged
msutovsky-r7 merged 1 commit intoAug 21, 2026
Conversation
- Restore `; exit 0` (not `; break`) in the shell-search match branch of
_generate_fileless_bash_search, so a hit terminates the whole script
instead of only the inner search loop. Matches the PR's own regression
spec and the original author's intent, reverted by a later "forgot to
break" commit.
- Wrap get_file_cmd in a subshell before appending the noise-suppressing
`>/dev/null` in both _generate_fileless_shell and
_generate_fileless_bash_search, so a redirect get_file_cmd already embeds
itself (e.g. the plain GET-based `...>$f`) keeps priority over the
outer redirect instead of being silently clobbered.
- Factor the duplicated hex byte-swap shell fragment (7 call sites across
_generate_jmp_instruction) into a single _hex_byte_swap_shell(width)
helper, and fix an odd-length hang: when $vdso_addr needs more digits
than the padded width, the old ${v%??} trim loop never terminated on
the final single leftover character.
- Have the TFTP fail-safe fallback (tftp_fetch_and_exec) honor
FETCH_DELETE like the generic _execute_nix path already does, with a
trailing `;` since it's concatenated in front of a closing ` fi`.
- Drop a redundant `if datastore['FETCH_DELETE']` modifier nested inside
its own enclosing `if datastore['FETCH_DELETE']`.
Verified via rspec (37 examples, 0 failures) and against real hardware
across all affected architectures (x64, x86, aarch64, armle, mipsle,
riscv64le), including forcing an actual shell-search match (not just the
disk-fallback path) and a real TFTP + FETCH_DELETE round trip.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzVszngwHjJmwTq5sviTHJ
msutovsky-r7
merged commit Aug 21, 2026
998d325
into
msutovsky-r7:feat/payloads/fetch_payload_failsafe
16 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restore
; exit 0(not; break) in the shell-search match branch of _generate_fileless_bash_search, so a hit terminates the whole script instead of only the inner search loop. Matches the PR's own regression spec and the original author's intent, reverted by a later "forgot to break" commit.Wrap get_file_cmd in a subshell before appending the noise-suppressing
>/dev/nullin both _generate_fileless_shell and _generate_fileless_bash_search, so a redirect get_file_cmd already embeds itself (e.g. the plain GET-based...>$f) keeps priority over the outer redirect instead of being silently clobbered.Factor the duplicated hex byte-swap shell fragment (7 call sites across _generate_jmp_instruction) into a single _hex_byte_swap_shell(width) helper, and fix an odd-length hang: when
$vdso_addrneeds more digits than the padded width, the old${v%??}trim loop never terminated on the final single leftover character.Have the TFTP fail-safe fallback (tftp_fetch_and_exec) honor FETCH_DELETE like the generic _execute_nix path already does, with a trailing
;since it's concatenated in front of a closingfi.Drop a redundant
if datastore['FETCH_DELETE']modifier nested inside its own enclosingif datastore['FETCH_DELETE'].Verified via rspec (37 examples, 0 failures) and against real hardware across all affected architectures (x64, x86, aarch64, armle, mipsle, riscv64le), including forcing an actual shell-search match (not just the disk-fallback path) and a real TFTP + FETCH_DELETE round trip.
AI Usage Disclosure
Claude is my Copilot
Test Evidence
PR rapid7#21548 — Fail-Safe Fileless Fetch Payloads: Review & Test Results
PR: rapid7#21548 — "Adds fail-safe for search fileless fetch payloads and updates the docs accordingly" (msutovsky-r7)
Base commit tested:
d7e9781b28c("Remove debug print")Fix branch:
collab/fetch_payload_failsafe(commit1942bacecbb)Summary
/code-review 21548surfaced 6 findings. All 6 were fixed, covered by new/updated rspec tests, and — beyond the usual rspec pass — verified against real hardware across every architecture and code path the fixes touch, including two scenarios (a genuine search-hit, and a full TFTP round trip) that required deliberately engineering the trigger condition since normal runs never hit them.Findings and fixes
_generate_fileless_bash_search's match branch used; break, which only exits the inner search loop — a regression from a "forgot to break" commit that undid an earlier; exit 0fileless.rb; exit 0if #{get_file_cmd} >/dev/null— forFETCH_COMMAND=GET,get_file_cmdalready ends in its own>$fredirect; the appended>/dev/nullsilently won, so the payload was never writtenfileless.rbif (#{get_file_cmd}) >/dev/null_generate_jmp_instruction(x64/x86/aarch64/armle/mipsle/riscv64le/riscv32le)fileless.rb_hex_byte_swap_shell(width)helpertftp_fetch_and_exec) never honoredFETCH_DELETE, unlike the generic pathfetch.rb;since the string is concatenated in front of a closingfiif datastore['FETCH_DELETE']nested inside its own identical enclosingiffetch.rbprintf %0Nxcan emit an odd count when the value exceeds the padded width, hanging the loop foreverfileless.rb${#v}is oddUnit tests
bundle exec rspec spec/lib/msf/core/payload/adapter/fetch/fileless_spec.rb spec/lib/msf/core/payload/adapter/fetch_spec.rb37 examples, 0 failures (up from 31 pre-fix; added regression tests for the TFTP cleanup/semicolon placement and the byte-swap helper, including a
timeout-guarded test proving the old code genuinely hangs on the odd-length case:timeout 2 sh -c '...'→ exit 124).Hardware verification — all 6 affected architectures
10.5.135.119(Azure x86_64 VM)msfuser@ubuntu-vm10.5.134.161(real i686 kernel, Ubuntu 16.04)Architecture: i68610.5.132.214(Kali Raspberry Pi)10.5.132.212(Kali Raspberry Pi)armv7l10.5.132.221(Ubiquiti EdgeRouter)GETbinary on this box)10.5.132.225(Banana Pi F3)uname -a→riscv64 riscv64 riscv64Deeper verification of specific fixes
Fix #1 (
break→exit 0): all 6 runs took the fallback branch, never the fix's own line. Verified instead via a local, deterministic simulation with fakeps/find/GETforcing a real hit across 3 scenarios (single candidate, two PIDs, one PID with two simultaneous candidates).breakandexit 0produced identical behavior in all three — the code already double-guards against double-execution via$FOUNDat both the per-PID and fallback level. Fix kept for correctness/defense-in-depth; no live divergence exists to test further.Fix #2 (subshell redirect): planted a real
memfd_create()fd on the x64 box, matched the exactfind -perm u=rwxfilter, ran the generatedshell-searchcommand against it. Session opened by executing/proc/<pid>/fd/3directly, fallback file never created — provesGET's>$fredirect survived the wrapping>/dev/null.Fix #4 (TFTP
FETCH_DELETE): stood up a realtftpd-hpaserver +msfvenom-generated payload, ran the real generated command. Session opened, fetched file confirmed deleted afterward.Adjacent finding (not part of this PR)
FETCH_COMMAND=TFTP+ any non-noneFETCH_FILELESSis currently unlaunchable viaset/exploit -j/msfvenom—FETCH_WRITABLE_DIR/FETCH_FILENAME'sconditions:metadata is only honored by console UI code, never the launch-time validator, so required-blank and required-non-blank checks directly contradict. Confirmed onupstream/master, unrelated to rapid7#21548. Drafted as a GitHub issue (delivered separately, for you to file).Verification checklist
FETCH_DELETEround trip confirmed livebreakvsexitbehavior empirically characterizedcollab/fetch_payload_failsafe(commit1942bacecbb)