RPC: Add job tracking for all module types#21653
Open
cdelafuente-r7 wants to merge 1 commit into
Open
Conversation
Adds a job_listener: kwarg to run_simple / check_simple for exploit, auxiliary, post and evasion modules, exposes Msf::Module#run_uuid, and threads the resulting UUID through Msf::ExploitDriver and Msf::EvasionDriver so RunAsJob paths return [uuid, job_id] uniformly. Fix RPC module.check NotImplementedError handling and JSON-RPC regression Fix RPC session.interactive_read/write for non-meterpreter sessions
This was referenced Jul 7, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends Metasploit’s RPC execution/check pathways to uniformly track module runs as “jobs” across exploit/aux/post/evasion by introducing a job_listener: kwarg through the Simple wrappers and drivers, exposing Msf::Module#run_uuid, and ensuring RPC returns the run UUID consistently while also improving RPC session interactivity for shell-like sessions.
Changes:
- Add
job_listener:plumbing throughrun_simple/check_simpleand throughMsf::ExploitDriver/Msf::EvasionDriver, generating and propagating a 24-charrun_uuid. - Update RPC
module.execute/module.checkbehavior to validate options structurally and to returnuuid == run_uuid, plus adjustmodule.resultsmissing-uuid status code. - Fix RPC
session.interactive_read/writeto support non-meterpreter interactive shell/powershell sessions and add extensive RSpec coverage for the new behaviors.
Impact Analysis:
- Blast radius: high — affects RPC consumers (JSON-RPC/Msgpack RPC clients), module execution orchestration, and job/result polling; also touches console and session scripting entrypoints that call Simple wrappers.
- Data and contract effects: breaking RPC contract changes (
uuidsemantics now map torun_uuid; options validation may reject previously accepted shapes;module.resultsmissing UUID status code changes); introduces new persisted-in-memory job result serialization behavior (JSON sanitization). - Rollback and test focus: rollback is straightforward (code-only) but verify RPC compatibility carefully; focus testing on
module.execute/module.checkreturn payloads,module.resultspolling across module types, and interactive read/write for shell + meterpreter sessions.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/lib/msf/exploit/local_spec.rb | Adds coverage for Exploit::Local#run_simple accepting/forwarding job_listener:. |
| spec/lib/msf/core/rpc/v10/rpc_session_spec.rb | Expands specs to validate interactive read/write for shell sessions. |
| spec/lib/msf/core/rpc/v10/rpc_module_execute_spec.rb | New specs ensuring module.execute returns run_uuid + job id across module types and validates opts. |
| spec/lib/msf/core/rpc/v10/rpc_module_check_spec.rb | New specs for module.check NotImplementedError behavior + listener wiring. |
| spec/lib/msf/core/rpc/v10/rpc_job_status_tracker_spec.rb | Adds specs for JSON-safe result storage with framework objects and primitives. |
| spec/lib/msf/base/simple/post_job_tracking_spec.rb | Adds job listener lifecycle tests for post modules + run_uuid assignment. |
| spec/lib/msf/base/simple/exploit_job_tracking_spec.rb | Adds exploit driver job listener lifecycle tests + signature/forwarding checks. |
| spec/lib/msf/base/simple/evasion_job_tracking_spec.rb | Adds evasion driver job listener lifecycle tests + signature checks. |
| spec/lib/msf/base/simple/auxiliary_job_tracking_spec.rb | Adds auxiliary job listener lifecycle tests + signature/forwarding checks. |
| lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb | Updates run_simple call sites to pass an explicit options Hash (keyword-arg safety). |
| lib/rex/post/hwbridge/ui/console/command_dispatcher/core.rb | Same: wraps run_simple args in {} to avoid keyword ambiguity. |
| lib/msf/ui/console/module_command_dispatcher.rb | Same: wraps check_simple args in {} for keyword-arg safety. |
| lib/msf/ui/console/command_dispatcher/post.rb | Same: wraps post run_simple options in {}. |
| lib/msf/ui/console/command_dispatcher/core.rb | Same: wraps post module run_simple options in {}. |
| lib/msf/ui/console/command_dispatcher/auxiliary.rb | Same: wraps auxiliary run_simple options in {} for both single and multi-target paths. |
| lib/msf/core/rpc/v10/service.rb | Broadens top-level RPC exception handling to include ScriptError. |
| lib/msf/core/rpc/v10/rpc_session.rb | Adds interactive shell/powershell read/write support and compatible module listing updates. |
| lib/msf/core/rpc/v10/rpc_module.rb | Adds options validation, threads job listener through RunAsJob paths, returns run_uuid, adjusts results missing-uuid status. |
| lib/msf/core/rpc/v10/rpc_job_status_tracker.rb | Sanitizes stored job results to be JSON-safe (handles cyclic framework objects). |
| lib/msf/core/post/session_upgrade.rb | Wraps exploit_simple args in {} to avoid keyword ambiguity. |
| lib/msf/core/module.rb | Introduces attr_accessor :run_uuid on modules. |
| lib/msf/core/exploit/remote/check_module.rb | Wraps run_simple args in {} to avoid keyword ambiguity. |
| lib/msf/core/exploit/remote/browser_autopwn2.rb | Wraps exploit_simple args in {} to avoid keyword ambiguity. |
| lib/msf/core/exploit/local.rb | Adds job_listener: kwarg to local exploit run_simple and forwards it. |
| lib/msf/core/exploit_driver.rb | Adds job listener + run_uuid generation and reports lifecycle events. |
| lib/msf/core/evasion_driver.rb | Adds job listener + run_uuid generation and reports lifecycle events. |
| lib/msf/base/simple/post.rb | Adds job_listener: to post simple runner and emits lifecycle events + run_uuid propagation. |
| lib/msf/base/simple/exploit.rb | Adds job_listener: to exploit simple wrappers and propagates run_uuid. |
| lib/msf/base/simple/evasion.rb | Adds job_listener: to evasion simple runner and propagates run_uuid. |
| lib/msf/base/simple/auxiliary.rb | Adds job_listener: to auxiliary simple wrappers and propagates run_uuid. |
| lib/msf/base/sessions/scriptable.rb | Wraps run_simple / exploit_simple args in {} to avoid keyword ambiguity. |
| end | ||
|
|
||
| iout = StringIO.new | ||
| rex_out = Rex::Ui::Text::Output::Stdio.new(io: iout) |
Comment on lines
+332
to
+340
| if SHELL_SESSION_TYPES.include?(session.type) | ||
| begin | ||
| payload = data.end_with?("\n") ? data : "#{data}\n" | ||
| session.shell_write(payload) | ||
| return { 'result' => 'success' } | ||
| rescue ::Exception => e | ||
| error(500, "Session Disconnected: #{e.class} #{e}") | ||
| end | ||
| end |
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.
Description
This PR adds support to job tracking for all module types when run through the RPC endpoints. This is required for the new MCP tools that were added and submitted in a separate PR. Particularly:
job_listener:kwarg torun_simple/check_simplefor exploit, auxiliary, post and evasion modulesMsf::Module#run_uuidMsf::ExploitDriverandMsf::EvasionDriverso RunAsJob paths return[uuid, job_id]uniformly.This also fixes two issues:
module.checkNotImplementedErrorhandling and JSON-RPC regressionsession.interactive_read/writefor non-meterpreter sessionsRelated Issue:
Orignial PR: #21632
Breaking Changes
module.execute/module.checkRPC endpoints:uuidfield now returnsrun_uuid, notmod.uuidmodule.execute/module.checkRPC endpoints: new input validation on optsmodule.results: status code for unknown UUID changed from 404 to 500module.resultsresult payload is now JSON-sanitisedservice.rb:ScriptError(incl.NotImplementedError) is now caught at the RPC entry pointReviewer Notes
This comes from this PR. I have extracted the RPC/Framework changes here, as requested. The MCP-related code is in a separate PR.
Verification Steps
Setup a simple target with docker:
docker compose up./msfrpcd -f -U msfuser -P 123456 -p 55553 -a 127.0.0.1./msfrpc -a 127.0.0.1 -U msfuser -P 123456rpc.call('module.execute', 'exploit', 'unix/webapp/thinkphp_rce', {'LHOST' => '192.168.4.4', 'RPORT'=>8080, 'RHOSTS'=>'127.0.0.1', 'SRVPORT'=>8888})module.running_stats:rpc.call('module.running_stats')waiting,runningorresultsarraysrpc.call('module.results', <UUID>)rpc.call('session.list')rpc.call('session.interactive_write', <sessionID>, "sysinfo")rpc.call('session.interactive_read', 1)rpc.call('module.check', 'exploit', 'unix/webapp/thinkphp_rce', {'LHOST' => '192.168.4.4', 'RPORT'=>8080, 'RHOSTS'=>'127.0.0.1', 'SRVPORT'=>8888})Test Evidence
Environment
AI Usage Disclosure
Copilot has been used to assist.
Pre-Submission Checklist
documentation/modules(new modules only)lib/changes)Hardware and Complex Software Module Guidance
If your module targets specialized hardware (routers, IoT, PLCs, etc.) or complex software (licensed, multi-service, or multi-version), provide a pcap, screen recording, or video showing successful execution.
Email sanitized pcaps/recordings to msfdev@metasploit.com — remove real IPs, credentials, and hostnames before sending. If hardware/software is unavailable, explain in the PR description.
Responsiveness and PR Takeover Policy
We want every contribution to make it into the project. If approximately 2 weeks pass after a review request without a comment or code update from you, the team may take over the PR and complete the work on your behalf.
If this happens, you will remain credited as a co-author on the final commit — your contribution is always recognized.
This policy exists to keep the project moving forward. It is not a reflection on the quality of your work or your involvement. Life happens, and we would rather finish the work together than let a good contribution go stale.