Reporting refactor - first iteration#21576
Open
cdelafuente-r7 wants to merge 3 commits into
Open
Conversation
fd99dec to
103a23a
Compare
- Add Errors, Reporter & Results - Add in_memory_backend, reporting test_helper & specs - Add db manager backend, connection pool & update reporter - Add Mdm::ModuleExecution lifecycle hooks - Add ModuleExecutionError capture - Update Gemfile to bring metasploit_data_models updates
103a23a to
d07f12b
Compare
d07f12b to
983bbdc
Compare
- add `check_code` & `check_message` fields on `ModuleExecution` - guarantees `mod.cleanup` runs at most once, inside `Msf::Reporting::CurrentExecution.with` - properly handle `fail_with` inside `#check` corner case - remove duplicate call to `cleanup` in `self.job_cleanup_proc` and rename it to `self.notify_job_complete` - always record `Failure::Unknown` for unhandled exceptions - add `Msf::Reporting::CurrentPhase` to make sure `fail_with` inside setup/cleanup now records phase='setup'
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 is the first iteration of the Metasploit reporting refactor. This project aims for a structured refactor of Metasploit reporting into a typed, backend-agnostic architecture that creates auditable execution records and consistent failure semantics across all reporting surfaces. The current system works in many success paths but has inconsistent behavior under failure and uneven attribution across module types and transports. This initiative addresses those reliability gaps while preserving delivery safety through additive, non-breaking iterations.
Current reporting behavior has three systemic issues:
Breaking Changes
The refactor is designed to be additive, so most changes are new surface area rather than removals. The items below are the places where an existing signature, symbol, behavior, or DB constraint actually changes in a way that could break a caller.
Msf::Simple::Auxiliary— public class-method changesjob_cleanup_procrenamed tonotify_job_complete.notify_job_completeno longer invokesmod.cleanup. Cleanup now runs insidejob_run_proc.derive_terminal_statusarity changed: the fourthunhandled_exceptionpositional parameter was removed.Msf::Simple::Exploit— internal helper arity changefinalize_exploit_executiondropped its thirdunhandled_exceptionpositional argument and reads the ivar instead.job_check_procnow unconditionally spawns/finalizes akind: 'check'Mdm::ModuleExecutionand hard-codesFailure::Unknownon the unhandled path.Msf::Exploit::Remote::AutoCheck— return-shape changerun_with_check_executionnow returns[check_code, check_execution], where it previously returned only thecheck_code.mod.cleanupruns earlier than before. It used to fire fromjob_cleanup_procafteron_module_complete; it now fires insidejob_run_proc(still exactly once, still on every rescue branch).fail_with— semantics widened across module typesfail_withoverride now records anMdm::ModuleExecutionErrorrow viarecord_failure!and then re-raises theMsf::Module::Failureexception with aRECORDED_EXCEPTION_IVARset on it, so the outerhandle_exception/ simple-wrapper rescues will short-circuit. Affected files:lib/msf/core/{exploit,auxiliary,post,evasion,module}.rb, scanner.rb.handle_exceptionin exploit.rbframework.events.on_module_error, the method now also callscapture_exception!(self, e, failure_reason: fail_reason)andmark_module_unhandled_exception(self)unless the exception is anMsf::Exploit::Failed.Msf::Reporting::CurrentExecutionandMsf::Reporting::CurrentPhaseinstall thread-local state aroundrun_simple/exploit_simple/check_simple/payload_generator.wrap_with_execution_lifecycle/with_phase_setup/with_phase_cleanup.schema.rbnow mirrors the new MDM tables (module_executions,module_execution_errors,module_execution_events) with thecheck_code/check_messagecolumns and twoCHECKconstraints.Reviewer Notes
IMPORTANT This needs this PR to be landed first.
This is the first iteration of this project and only a few features were implemented so far. ( e.g.
hosts,services,vulns,notes,loots,etc. data models are not linked to module executions yet).Verification Steps
Validate the new
Mdm::ModuleExecution/Mdm::ModuleExecutionErrorreportingby running two private test modules and inspecting the DB after each scenario.
Setup
Start the DB and run the migrations:
Install the test modules
Copy the two files from Test module source into:
Start
msfconsole, confirmdb_statusshows "Connected", thenreload_all.Inspecting the DB
Save this resource file:
Run it between test cases:
Test matrix
For every scenario:
#-commented error linescommented).
reload, run the command, thenresource ~/.msf4/reporting-check.rc.Every non-happy-path scenario produces exactly 1 new execution row + 1 new
error row unless stated otherwise. Fields not listed do not matter.
Legend:
status=terminal_statusreason=failure_reasonmsg=failure_message(on execution) ormessage(on error)phase=lifecycle_phaseclass=exception_classAuxiliary —
use auxiliary/test/reporting/reporting_auxrunkind=run,status=success,reason=NULL,msg=NULL,parent=NULL.checkon aux —checkkind=check,status=neutral,check_code=detected,check_message="reporting test: detected (default)".fail_withinrun— uncommentfail_within#run, thenrunstatus=expected_failure,reason=unexpected-reply,msg="boom in run (fail_with)".phase=run,class=NULL,reason=unexpected-reply,msg="boom in run (fail_with)".run— uncommentraisein#run, thenrunstatus=unhandled_exception,reason=unknown,msg="boom in run (unhandled)".phase=run,class=RuntimeError,reason=unknown,msg="boom in run (unhandled)", backtrace non-empty.fail_withinsetup— uncommentfail_within#setup, thenrun[run] runningmust not appear in console output.status=expected_failure,reason=bad-config,msg="boom in setup (fail_with)".phase=setup,class=NULL,reason=bad-config.setup— uncommentraisein#setup, thenrun[run] runningmust not appear.status=unhandled_exception,reason=unknown,msg="boom in setup (unhandled)".phase=setup,class=RuntimeError,reason=unknown, backtrace non-empty.fail_withincleanup— uncommentfail_within#cleanup, thenrun[run] successmust appear before the failure.status=expected_failure,reason=unknown,msg="boom in cleanup (fail_with)".phase=cleanup,class=NULL,reason=unknown.cleanup— uncommentraisein#cleanup, thenrunstatus=unhandled_exception,reason=unknown,msg="boom in cleanup (unhandled)".phase=cleanup,class=RuntimeError,reason=unknown, backtrace non-empty.Exploit —
use exploit/test/reporting/reporting_exploitEvery exploit scenario except 16. produces TWO execution rows:
kind=check,iface=autocheck,parent=<parent-id>)kind=run,iface=console,parent=NULL)runstatus=success,check_code=appears,check_message="reporting test: appears (default)".status=expected_failure,reason=payload-failed,msg="No session created",check_code=NULL.check—checkOne execution row:
kind=check,iface=console,parent=NULL,status=success,check_code=appears,check_message="reporting test: appears (default)". No error row.Alternate returns (uncomment the matching
return CheckCode::…line in#check, one at a time):statuscheck_codereturn CheckCode::Vulnerable('...')return CheckCode::Appears('...')(default)return CheckCode::Detected('...')return CheckCode::Safe('...')return CheckCode::Unknown('...')fail_withincheck— uncommentfail_within#check, thenrunstatus=expected_failure,reason=unexpected-reply,msg="boom in check (fail_with)",check_code=NULL.status=expected_failure,reason=unexpected-reply,msg="boom in check (fail_with)".phase=check,class=NULL,reason=unexpected-reply,msg="boom in check (fail_with)".Safe/Unsupported/Unknownreturn in#check, thenrunstatus=neutral,check_code=<safe|unsupported|unknown>.status=expected_failure,reason=as follows.phase=check,class=NULL,reason=as follows.reasonSafeUnsupportedUnknowncheck— uncommentraisein#check, thenrunstatus=unhandled_exception,reason=unknown,msg="boom in check (unhandled)".status=unhandled_exception,reason=unknown,msg="boom in check (unhandled)".phase=check,class=RuntimeError,reason=unknown, backtrace non-empty.fail_withinexploit— uncommentfail_within#exploit, thenrunstatus=success,check_code=appears.status=expected_failure,reason=payload-failed,msg="boom in exploit (fail_with)".phase=exploit,class=NULL,reason=payload-failed.exploit— uncommentraisein#exploit, thenrunstatus=success,check_code=appears.status=unhandled_exception,reason=unknown,msg="boom in exploit (unhandled)".phase=exploit,class=RuntimeError,reason=unknown, backtrace non-empty.setup— uncommentraisein#setup, thenrunOnly ONE execution row (the parent). No child row.
status=unhandled_exception,reason=unknown,msg="boom in setup (unhandled)",check_code=NULL.phase=setup,class=RuntimeError,reason=unknown, backtrace non-empty.cleanup— uncommentraisein#cleanup, thenrunstatus=success,check_code=appears.status=unhandled_exception,reason=unknown.phase=cleanup,class=RuntimeError,reason=unknown, backtrace non-empty.msfvenom— encoder + NOP wrapFrom a shell:
./msfvenom -p linux/x64/exec CMD=id -e x64/xor -i 3 -f raw > /dev/nullExpected: one or more new execution rows with
iface=console,kind=run,status=success,module_reference_namematching an encoder (x64/xor) and,if the payload required NOPs, a NOP (
x64/simple).Module runs and prints normally, no exception raised, and no new rows in
module_executions. Reconnect withdb_connect(or restartmsfconsole)before continuing.
Test module source
~/.msf4/modules/auxiliary/test/reporting/reporting_aux.rb~/.msf4/modules/exploits/test/reporting/reporting_exploit.rbAI Usage Disclosure
AI was used for this project (Copilot with Claude Opus 4.7 model) to help for the code analysis, the design and the implementation.
Pre-Submission Checklist
rubocopon new files with no new offenses (net new files only)msftidyon changed module files with no new offenses (modules only)msftidy_docson changed documentation files with no new offenses (documentation files only)documentation/modules(new modules only)lib/changes)