diff --git a/lib/wpscan/modules/web_site.rb b/lib/wpscan/web_site.rb similarity index 77% rename from lib/wpscan/modules/web_site.rb rename to lib/wpscan/web_site.rb index 83411c39..d7b8f2fa 100644 --- a/lib/wpscan/modules/web_site.rb +++ b/lib/wpscan/web_site.rb @@ -17,7 +17,21 @@ # along with this program. If not, see . #++ -module WebSite +class WebSite + + attr_reader :uri + + def initialize(site_url) + self.url = site_url + end + + def url=(url) + @uri = URI.parse(add_trailing_slash(add_http_protocol(url))) + end + + def url + @uri.to_s + end # Checks if the remote website is up. def online? @@ -28,40 +42,6 @@ module WebSite Browser.instance.get(@uri.to_s).code == 401 end - # check if the remote website is - # actually running wordpress. - def wordpress? - wordpress = false - - response = Browser.instance.get( - @uri.to_s, - { follow_location: true, max_redirects: 2 } - ) - if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i - wordpress = true - else - response = Browser.instance.get( - xml_rpc_url, - { follow_location: true, max_redirects: 2 } - ) - - if response.body =~ %r{XML-RPC server accepts POST requests only}i - wordpress = true - else - response = Browser.instance.get( - login_url(), - { follow_location: true, max_redirects: 2 } - ) - - if response.body =~ %r{WordPress}i - wordpress = true - end - end - end - - wordpress - end - def has_xml_rpc? !xml_rpc_url.nil? end diff --git a/lib/wpscan/wp_target.rb b/lib/wpscan/wp_target.rb index d37f226d..45bb5ab5 100644 --- a/lib/wpscan/wp_target.rb +++ b/lib/wpscan/wp_target.rb @@ -17,8 +17,7 @@ # along with this program. If not, see . #++ -class WpTarget - include WebSite +class WpTarget < WebSite include WpReadme include WpFullPathDisclosure include WpConfigBackup @@ -30,10 +29,11 @@ class WpTarget include WpThemes include BruteForce - attr_reader :uri, :verbose + attr_reader :verbose def initialize(target_url, options = {}) - @uri = URI.parse(add_trailing_slash(add_http_protocol(target_url))) + super(target_url) + @verbose = options[:verbose] @wp_content_dir = options[:wp_content_dir] @wp_plugins_dir = options[:wp_plugins_dir] @@ -42,9 +42,39 @@ class WpTarget Browser.instance(options.merge(:max_threads => options[:threads])) end - # Alias of @uri.to_s - def url - @uri.to_s + # check if the target website is + # actually running wordpress. + def wordpress? + wordpress = false + + response = Browser.instance.get( + @uri.to_s, + { follow_location: true, max_redirects: 2 } + ) + + if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i + wordpress = true + else + response = Browser.instance.get( + xml_rpc_url, + { follow_location: true, max_redirects: 2 } + ) + + if response.body =~ %r{XML-RPC server accepts POST requests only}i + wordpress = true + else + response = Browser.instance.get( + login_url, + { follow_location: true, max_redirects: 2 } + ) + + if response.body =~ %r{WordPress}i + wordpress = true + end + end + end + + wordpress end def login_url diff --git a/spec/lib/wpscan/modules/wp_readme_spec.rb b/spec/lib/wpscan/modules/wp_readme_spec.rb index 6a8ce7b9..0cec1fb1 100644 --- a/spec/lib/wpscan/modules/wp_readme_spec.rb +++ b/spec/lib/wpscan/modules/wp_readme_spec.rb @@ -28,7 +28,7 @@ shared_examples_for 'WpReadme' do describe '#readme_url' do it 'should return http://example.localhost/readme.html' do - @module.readme_url.should === "#{@module.uri}/readme.html" + @module.readme_url.should === "#{@module.uri}readme.html" end end diff --git a/spec/lib/wpscan/modules/web_site_spec.rb b/spec/lib/wpscan/web_site_spec.rb similarity index 64% rename from spec/lib/wpscan/modules/web_site_spec.rb rename to spec/lib/wpscan/web_site_spec.rb index 0171fd6c..67df2aca 100644 --- a/spec/lib/wpscan/modules/web_site_spec.rb +++ b/spec/lib/wpscan/web_site_spec.rb @@ -17,9 +17,34 @@ # along with this program. If not, see . #++ -shared_examples_for 'WebSite' do - let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/web_site' } - subject(:web_site) { WpScanModuleSpec.new('http://example.localhost/').extend(WebSite) } +describe 'WebSite' do + let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WEB_SITE_DIR } + subject(:web_site) { WebSite.new('http://example.localhost/') } + + describe "#new" do + its(:url) { should === 'http://example.localhost/' } + end + + describe '#url=' do + after :each do + web_site.url = @uri + web_site.url.should === @expected + end + + context 'when protocol or trailing slash is missing' do + it 'should add the them' do + @uri = 'example.localhost' + @expected = 'http://example.localhost/' + end + end + + context 'when there is a protocol or a trailing slash' do + it 'should not add them' do + @uri = 'http://example.localhost/' + @expected = @uri + end + end + end describe '#online?' do it 'should not be considered online if the status code is 0' do @@ -74,70 +99,6 @@ shared_examples_for 'WebSite' do end end - describe '#wordpress?' do - # each url (wp-login and xmlrpc) pointed to a 404 - before :each do - stub_request(:get, web_site.url). - to_return(status: 200, body: '', headers: { 'X-Pingback' => web_site.uri.merge('xmlrpc.php')}) - - [web_site.login_url, web_site.xml_rpc_url].each do |url| - stub_request(:get, url).to_return(status: 404, body: '') - end - end - - it 'should return false if both files are not found (404)' do - web_site.should_not be_wordpress - end - - it 'should return true if the wp-login is found and is a valid wordpress one' do - stub_request(:get, web_site.login_url). - to_return(status: 200, body: File.new(fixtures_dir + '/wp-login.php')) - - web_site.should be_wordpress - end - - it 'should return true if the xmlrpc is found' do - stub_request(:get, web_site.xml_rpc_url). - to_return(status: 200, body: File.new(fixtures_dir + '/xmlrpc.php')) - - web_site.should be_wordpress - end - end - - describe '#redirection' do - it 'should return nil if no redirection detected' do - stub_request(:get, web_site.url).to_return(status: 200, body: '') - - web_site.redirection.should be_nil - end - - [301, 302].each do |status_code| - it "should return http://new-location.com if the status code is #{status_code}" do - new_location = 'http://new-location.com' - - stub_request(:get, web_site.url). - to_return(status: status_code, headers: { location: new_location }) - - stub_request(:get, new_location).to_return(status: 200) - - web_site.redirection.should === 'http://new-location.com' - end - end - - context 'when multiple redirections' do - it 'should return the last redirection' do - first_redirection = 'www.redirection.com' - last_redirection = 'redirection.com' - - stub_request(:get, web_site.url).to_return(status: 301, headers: { location: first_redirection }) - stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection }) - stub_request(:get, last_redirection).to_return(status: 200) - - web_site.redirection.should === last_redirection - end - end - end - describe '#page_hash' do it 'should return the MD5 hash of the page' do url = 'http://e.localhost/somepage.php' @@ -196,4 +157,4 @@ shared_examples_for 'WebSite' do web_site.has_robots?.should be_false end end -end \ No newline at end of file +end diff --git a/spec/lib/wpscan/wp_target_spec.rb b/spec/lib/wpscan/wp_target_spec.rb index 5f69ac9d..b8ca609d 100644 --- a/spec/lib/wpscan/wp_target_spec.rb +++ b/spec/lib/wpscan/wp_target_spec.rb @@ -20,6 +20,7 @@ require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper') describe WpTarget do + let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR } before :each do Browser.reset @@ -33,7 +34,6 @@ describe WpTarget do @wp_target = WpTarget.new('http://example.localhost/', @options) end - it_should_behave_like 'WebSite' it_should_behave_like 'WpReadme' it_should_behave_like 'WpConfigBackup' it_should_behave_like 'WpFullPathDisclosure' @@ -50,20 +50,6 @@ describe WpTarget do expect { WpTarget.new(nil) }.to raise_error expect { Wptarget.new('') }.to raise_error end - - it 'should add the http protocol if missing' do - WpTarget.new('example.localhost/', @options).url.should === 'http://example.localhost/' - end - - it 'should add the trailing slash to the url if missing' do - WpTarget.new('lamp/wordpress', @options).url.should === 'http://lamp/wordpress/' - end - end - - describe '#url' do - it 'should return the url of the target' do - @wp_target.url.should === @wp_target.uri.to_s - end end describe '#login_url' do @@ -85,6 +71,73 @@ describe WpTarget do end end + describe '#wordpress?' do + # each url (wp-login and xmlrpc) pointed to a 404 + before :each do + stub_request(:get, @wp_target.url). + to_return(status: 200, body: '', headers: { 'X-Pingback' => @wp_target.uri.merge('xmlrpc.php')}) + + # Preventing redirection check from login_url() + @wp_target.stub(redirection: nil) + + [@wp_target.login_url, @wp_target.xml_rpc_url].each do |url| + stub_request(:get, url).to_return(status: 404, body: '') + end + end + + it 'should return false if both files are not found (404)' do + @wp_target.should_not be_wordpress + end + + it 'should return true if the wp-login is found and is a valid wordpress one' do + stub_request(:get, @wp_target.login_url). + to_return(status: 200, body: File.new(fixtures_dir + '/wp-login.php')) + + @wp_target.should be_wordpress + end + + it 'should return true if the xmlrpc is found' do + stub_request(:get, @wp_target.xml_rpc_url). + to_return(status: 200, body: File.new(fixtures_dir + '/xmlrpc.php')) + + @wp_target.should be_wordpress + end + end + + describe '#redirection' do + it 'should return nil if no redirection detected' do + stub_request(:get, @wp_target.url).to_return(status: 200, body: '') + + @wp_target.redirection.should be_nil + end + + [301, 302].each do |status_code| + it "should return http://new-location.com if the status code is #{status_code}" do + new_location = 'http://new-location.com' + + stub_request(:get, @wp_target.url). + to_return(status: status_code, headers: { location: new_location }) + + stub_request(:get, new_location).to_return(status: 200) + + @wp_target.redirection.should === 'http://new-location.com' + end + end + + context 'when multiple redirections' do + it 'should return the last redirection' do + first_redirection = 'www.redirection.com' + last_redirection = 'redirection.com' + + stub_request(:get, @wp_target.url).to_return(status: 301, headers: { location: first_redirection }) + stub_request(:get, first_redirection).to_return(status: 302, headers: { location: last_redirection }) + stub_request(:get, last_redirection).to_return(status: 200) + + @wp_target.redirection.should === last_redirection + end + end + end + describe '#wp_content_dir' do let(:fixtures_dir) { SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR + '/wp_content_dir' } diff --git a/spec/lib/wpscan/wpscan_helper.rb b/spec/lib/wpscan/wpscan_helper.rb index 21c526b2..c5ea8c63 100644 --- a/spec/lib/wpscan/wpscan_helper.rb +++ b/spec/lib/wpscan/wpscan_helper.rb @@ -23,18 +23,22 @@ require WPSCAN_LIB_DIR + '/wpscan_helper' SPEC_FIXTURES_WPSCAN_DIR = SPEC_FIXTURES_DIR + '/wpscan' SPEC_FIXTURES_WPSCAN_MODULES_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/modules' +SPEC_FIXTURES_WPSCAN_WEB_SITE_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/web_site' SPEC_FIXTURES_WPSCAN_WP_TARGET_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wp_target' SPEC_FIXTURES_WPSCAN_WPSCAN_OPTIONS_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wpscan_options' SPEC_FIXTURES_WPSCAN_WP_THEME_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wp_theme' SPEC_FIXTURES_WPSCAN_WP_PLUGIN_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wp_plugin' SPEC_FIXTURES_WPSCAN_WP_VERSION_DIR = SPEC_FIXTURES_WPSCAN_DIR + '/wp_version' +# This class is a HACK to simulate the WpTarget behavior in order +# to be able to test the modules independently class WpScanModuleSpec attr_reader :uri attr_accessor :error_404_hash, :homepage_hash, :wp_content_dir, :verbose def initialize(target_url) - @uri = URI.parse(add_http_protocol(target_url)) + @uri = URI.parse(add_trailing_slash(add_http_protocol(target_url))) + Browser.instance( config_file: SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json', cache_timeout: 0 diff --git a/spec/samples/wpscan/modules/web_site/rss_url/wordpress-3.5.htm b/spec/samples/wpscan/web_site/rss_url/wordpress-3.5.htm similarity index 100% rename from spec/samples/wpscan/modules/web_site/rss_url/wordpress-3.5.htm rename to spec/samples/wpscan/web_site/rss_url/wordpress-3.5.htm diff --git a/spec/samples/wpscan/modules/web_site/wp-login.php b/spec/samples/wpscan/wp_target/wp-login.php similarity index 100% rename from spec/samples/wpscan/modules/web_site/wp-login.php rename to spec/samples/wpscan/wp_target/wp-login.php diff --git a/spec/samples/wpscan/modules/web_site/xmlrpc.php b/spec/samples/wpscan/wp_target/xmlrpc.php similarity index 100% rename from spec/samples/wpscan/modules/web_site/xmlrpc.php rename to spec/samples/wpscan/wp_target/xmlrpc.php