@@ -93,6 +93,8 @@ def report_failure
9393 info [ :port ] = self . datastore [ 'RPORT' ]
9494 if self . class . ancestors . include? ( Msf ::Exploit ::Remote ::Tcp )
9595 info [ :proto ] = 'tcp'
96+ elsif self . class . ancestors . include? ( Msf ::Exploit ::Remote ::Udp )
97+ info [ :proto ] = 'udp'
9698 end
9799 end
98100
@@ -127,9 +129,9 @@ def report_failure
127129 # check_code is available, update the existing attempt so it carries the
128130 # check result details (the attempt created by report_vuln may not have
129131 # had the check_code yet because it runs before job_run_proc stores it).
130- if self . respond_to? ( :vuln_attempt_recorded ) && self . vuln_attempt_recorded
132+ if self . respond_to? ( :last_vuln_attempt ) && self . last_vuln_attempt
131133 if self . respond_to? ( :check_code ) && self . check_code . is_a? ( Msf ::Exploit ::CheckCode )
132- _enrich_existing_vuln_attempt ( info )
134+ _enrich_existing_vuln_attempt ( info , self . last_vuln_attempt )
133135 end
134136 info [ :skip_vuln_attempt ] = true
135137 end
@@ -139,28 +141,47 @@ def report_failure
139141
140142 private
141143
142- # Update the most recent VulnAttempt for this module/host with check code
143- # details that were not available when report_vuln originally created it.
144- def _enrich_existing_vuln_attempt ( info )
144+ # Update the VulnAttempt for this module/host with check code details that
145+ # were not available when report_vuln originally created it.
146+ #
147+ # @param info [Hash] enrichment data built by report_failure
148+ # @param recorded_attempt [Mdm::VulnAttempt, true] the attempt object stored
149+ # by report_vuln, or +true+ if only the flag was propagated (legacy/fallback).
150+ def _enrich_existing_vuln_attempt ( info , recorded_attempt = nil )
145151 return unless framework . db &.active
146152
147- host = info [ :host ]
148- return unless host
149-
150- host_obj = if host . is_a? ( ::Mdm ::Host )
151- host
152- else
153- wspace = info [ :workspace ] || framework . db . find_workspace ( workspace )
154- framework . db . get_host ( workspace : wspace , address : host . to_s )
155- end
156- return unless host_obj
157-
158- # Find the most recent vuln attempt for this module on this host
159- attempt = ::Mdm ::VulnAttempt
153+ # Use the stored attempt directly when available — avoids a racy
154+ # re-query that could match the wrong row under concurrency.
155+ attempt = recorded_attempt if recorded_attempt . is_a? ( ::Mdm ::VulnAttempt )
156+
157+ # Fallback: re-query if we only have the boolean flag (e.g. propagated
158+ # through a replicant that only forwarded +true+).
159+ if attempt . nil?
160+ host = info [ :host ]
161+ return unless host
162+
163+ host_obj = if host . is_a? ( ::Mdm ::Host )
164+ host
165+ else
166+ wspace = info [ :workspace ] || framework . db . find_workspace ( workspace )
167+ framework . db . get_host ( workspace : wspace , address : host . to_s )
168+ end
169+ return unless host_obj
170+
171+ scope = ::Mdm ::VulnAttempt
160172 . joins ( :vuln )
161173 . where ( module : fullname , vulns : { host_id : host_obj . id } )
162- . order ( attempted_at : :desc )
163- . first
174+
175+ # Narrow by port when available so we don't match an attempt against
176+ # a different service on the same host (e.g. port 80 vs 9200).
177+ if info [ :port ]
178+ scope = scope . joins ( vuln : :service )
179+ . where ( services : { port : info [ :port ] } )
180+ end
181+
182+ attempt = scope . order ( attempted_at : :desc ) . first
183+ end
184+
164185 return unless attempt
165186
166187 updates = { }
0 commit comments