From 24e69d2f53b7b97a9743051840708bbe0528b504 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 29 Jun 2026 14:15:37 -0400 Subject: [PATCH 1/3] Also catch ECONNRESET Windows raises ECONNRESET not ECONNREFUSED as Linux does --- lib/msf/core/auxiliary/udp_scanner.rb | 8 +-- .../msf/core/auxiliary/udp_scanner_spec.rb | 52 ++++++++++++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/msf/core/auxiliary/udp_scanner.rb b/lib/msf/core/auxiliary/udp_scanner.rb index d09e679a0a0dd..c7366c6bf0041 100644 --- a/lib/msf/core/auxiliary/udp_scanner.rb +++ b/lib/msf/core/auxiliary/udp_scanner.rb @@ -148,7 +148,7 @@ def scanner_send(data, ip, port) retry - rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED + rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET # This fires for host unreachable, net unreachable, and broadcast sends # We can safely ignore all of these for UDP sends end @@ -175,9 +175,9 @@ def scanner_recv(timeout = 0.1) for sock in readable begin res = sock.recvfrom(65535) - rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED - # A connected UDP socket surfaces ICMP port-unreachable as ECONNREFUSED - # on recvfrom; skip this socket and keep processing the others. + rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET + # A connected UDP socket surfaces ICMP port-unreachable differently + # by platform; skip this socket and keep processing the others. next end diff --git a/spec/lib/msf/core/auxiliary/udp_scanner_spec.rb b/spec/lib/msf/core/auxiliary/udp_scanner_spec.rb index 2d6415969e7c0..4acf877936a3e 100644 --- a/spec/lib/msf/core/auxiliary/udp_scanner_spec.rb +++ b/spec/lib/msf/core/auxiliary/udp_scanner_spec.rb @@ -8,18 +8,26 @@ mod.extend described_class mod.instance_variable_set(:@udp_sockets, {}) mod.instance_variable_set(:@udp_sockets_mutex, Mutex.new) + mod.instance_variable_set(:@udp_send_count, 0) + mod.instance_variable_set(:@interval_mutex, Mutex.new) mod.instance_variable_set(:@results, {}) - mod.define_singleton_method(:datastore) { { 'ScannerRecvQueueLimit' => 100 } } + mod.define_singleton_method(:datastore) do + { + 'ScannerMaxResends' => 10, + 'ScannerRecvInterval' => 30, + 'ScannerRecvQueueLimit' => 100 + } + end mod.define_singleton_method(:inside_workspace_boundary?) { |_host| true } allow(mod).to receive(:cleanup_udp_sockets) mod end - # A socket whose recvfrom raises, mimicking a connected UDP socket that received an - # ICMP port-unreachable for a closed port. - let(:refused_socket) do + # A socket whose recvfrom raises, mimicking a connected UDP socket that received + # an ICMP port-unreachable for a closed port. + def error_socket(error_class) sock = double('refused_socket') - allow(sock).to receive(:recvfrom).and_raise(::Errno::ECONNREFUSED) + allow(sock).to receive(:recvfrom).and_raise(error_class) sock end @@ -31,20 +39,36 @@ sock end + describe '#scanner_send' do + [::Errno::ECONNREFUSED, ::Errno::ECONNRESET].each do |error_class| + it "swallows #{error_class} raised while sending UDP probes" do + sock = double('send_socket') + allow(sock).to receive(:send).and_raise(error_class) + allow(subject).to receive(:udp_socket).and_return(sock) + + expect { subject.scanner_send('probe', '192.0.2.5', 161) }.not_to raise_error + end + end + end + describe '#scanner_recv' do - it 'swallows ECONNREFUSED raised by recvfrom instead of propagating it' do - allow(::IO).to receive(:select).and_return([[refused_socket], [], []], nil) + [::Errno::ECONNREFUSED, ::Errno::ECONNRESET].each do |error_class| + it "swallows #{error_class} raised by recvfrom instead of propagating it" do + allow(::IO).to receive(:select).and_return([[error_socket(error_class)], [], []], nil) - expect { subject.scanner_recv(0.1) }.not_to raise_error - expect(subject.scanner_recv(0.1)).to eq(0) - expect(subject.results).to be_empty + expect { subject.scanner_recv(0.1) }.not_to raise_error + expect(subject.scanner_recv(0.1)).to eq(0) + expect(subject.results).to be_empty + end end - it 'continues processing other readable sockets after one raises ECONNREFUSED' do - allow(::IO).to receive(:select).and_return([[refused_socket, responding_socket], [], []], nil) + [::Errno::ECONNREFUSED, ::Errno::ECONNRESET].each do |error_class| + it "continues processing other readable sockets after one raises #{error_class}" do + allow(::IO).to receive(:select).and_return([[error_socket(error_class), responding_socket], [], []], nil) - expect(subject.scanner_recv(0.1)).to eq(1) - expect(subject.results['192.0.2.5']).to eq(['response-data']) + expect(subject.scanner_recv(0.1)).to eq(1) + expect(subject.results['192.0.2.5']).to eq(['response-data']) + end end end end From dfec2fc61d98cf6f319d0b8667fd814dab20e234 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 30 Jun 2026 14:15:16 -0400 Subject: [PATCH 2/3] Also catch ECONNRESET where ECONNREFUSED is caught --- lib/msf/core/handler/bind_udp.rb | 2 +- modules/auxiliary/admin/netbios/netbios_spoof.rb | 2 +- modules/auxiliary/admin/networking/cisco_sdwan_auth_bypass.rb | 2 +- .../auxiliary/admin/networking/cisco_sdwan_vhub_auth_bypass.rb | 2 +- modules/auxiliary/dos/rpc/rpcbomb.rb | 2 +- modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb | 2 +- modules/auxiliary/server/netbios_spoof_nat.rb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/handler/bind_udp.rb b/lib/msf/core/handler/bind_udp.rb index 48a8ada8740ea..4ecf45774c6ca 100644 --- a/lib/msf/core/handler/bind_udp.rb +++ b/lib/msf/core/handler/bind_udp.rb @@ -151,7 +151,7 @@ def start_handler client.close() client = nil end - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED, Errno::ECONNRESET client.close() client = nil wlog("Connection failed in udp bind handler continuing attempts: #{$!.class} #{$!}") diff --git a/modules/auxiliary/admin/netbios/netbios_spoof.rb b/modules/auxiliary/admin/netbios/netbios_spoof.rb index ec8cef7f0687c..b65e7872e57c3 100644 --- a/modules/auxiliary/admin/netbios/netbios_spoof.rb +++ b/modules/auxiliary/admin/netbios/netbios_spoof.rb @@ -71,7 +71,7 @@ def netbios_spam if pps > @targ_rate sleep(0.01) end - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED, Errno::ECONNRESET print_error('Error: Target sent us an ICMP port unreachable, port is likely closed') live = false break diff --git a/modules/auxiliary/admin/networking/cisco_sdwan_auth_bypass.rb b/modules/auxiliary/admin/networking/cisco_sdwan_auth_bypass.rb index 4cc553de8c2f5..df191ff0bda0b 100644 --- a/modules/auxiliary/admin/networking/cisco_sdwan_auth_bypass.rb +++ b/modules/auxiliary/admin/networking/cisco_sdwan_auth_bypass.rb @@ -167,7 +167,7 @@ def phase1_dtls_handshake(silent: false) print_status('DTLS handshake succeeded (self-signed cert accepted)') unless silent [ssl, ctx, udp_sock, rbio, wbio] - rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED => e + rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET => e print_error("Connection failed: #{e.message}") unless silent cleanup_dtls(ssl, ctx, udp_sock) [nil, nil, nil, nil, nil] diff --git a/modules/auxiliary/admin/networking/cisco_sdwan_vhub_auth_bypass.rb b/modules/auxiliary/admin/networking/cisco_sdwan_vhub_auth_bypass.rb index 264821d59f8c8..5159dde2a006a 100644 --- a/modules/auxiliary/admin/networking/cisco_sdwan_vhub_auth_bypass.rb +++ b/modules/auxiliary/admin/networking/cisco_sdwan_vhub_auth_bypass.rb @@ -184,7 +184,7 @@ def phase1_dtls_handshake(silent: false) print_status('DTLS handshake succeeded (self-signed cert accepted)') unless silent [ssl, ctx, udp_sock, rbio, wbio] - rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED => e + rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET => e print_error("Connection failed: #{e.message}") unless silent cleanup_dtls(ssl, ctx, udp_sock) [nil, nil, nil, nil, nil] diff --git a/modules/auxiliary/dos/rpc/rpcbomb.rb b/modules/auxiliary/dos/rpc/rpcbomb.rb index 9005aeb12a81d..596c62a449182 100644 --- a/modules/auxiliary/dos/rpc/rpcbomb.rb +++ b/modules/auxiliary/dos/rpc/rpcbomb.rb @@ -69,7 +69,7 @@ def scan_host(ip) while count < datastore['COUNT'] begin s.send(pkt, 0) - rescue ::Errno::ENOBUFS, ::Rex::ConnectionError, ::Errno::ECONNREFUSED + rescue ::Errno::ENOBUFS, ::Rex::ConnectionError, ::Errno::ECONNREFUSED, ::Errno::ECONNRESET vprint_error("Host #{ip} unreachable") break end diff --git a/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb b/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb index be2e9d64ebc50..1bae846e2e8d4 100644 --- a/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb +++ b/modules/auxiliary/scanner/snmp/snmp_enum_hp_laserjet.rb @@ -139,7 +139,7 @@ def run_host(ip) disconnect_snmp rescue SNMP::RequestTimeout print_error("#{ip}, SNMP request timeout.") - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED, Errno::ECONNRESET print_error("#{ip}, Connection refused.") rescue SNMP::InvalidIpAddress print_error("#{ip}, Invalid IP address. Check it with 'snmpwalk tool'.") diff --git a/modules/auxiliary/server/netbios_spoof_nat.rb b/modules/auxiliary/server/netbios_spoof_nat.rb index 585cc96181d6a..f0c67098619a7 100644 --- a/modules/auxiliary/server/netbios_spoof_nat.rb +++ b/modules/auxiliary/server/netbios_spoof_nat.rb @@ -137,7 +137,7 @@ def netbios_spam if pps > @targ_rate sleep(0.01) end - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED, Errno::ECONNRESET print_error("Error: Target sent us an ICMP port unreachable, port is likely closed") live = false break From 6ac855573d053eac6c90d275af911aa288848ed1 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 30 Jun 2026 15:20:24 -0400 Subject: [PATCH 3/3] Bump rex-socket to 0.1.70 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index f6fb2fed33bc3..5aa193e382c37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -551,7 +551,7 @@ GEM metasm rex-core rex-text - rex-socket (0.1.69) + rex-socket (0.1.70) dnsruby rex-core rex-sslscan (0.1.13)