From 2a46dc3f405c207533490b322d6cb26ed9217ba3 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 19 Sep 2012 21:47:34 +0200 Subject: [PATCH] -) more rspec tests -) Bugfixing --- lib/wpscan/modules/brute_force.rb | 8 +- lib/wpscan/modules/web_site.rb | 6 +- lib/wpscan/modules/wp_plugins.rb | 10 +-- lib/wpscan/modules/wp_themes.rb | 10 +-- lib/wpscan/modules/wp_usernames.rb | 4 +- lib/wpscan/wp_enumerator.rb | 20 ++--- lib/wpscan/wp_item.rb | 16 +++- lib/wpscan/wp_options.rb | 16 ---- lib/wpscan/wp_plugin.rb | 7 +- lib/wpscan/wp_target.rb | 9 ++ lib/wpscan/wp_theme.rb | 4 +- lib/wpscan/wp_version.rb | 4 +- spec/lib/wpscan/modules/wp_plugins_spec.rb | 86 +++++++++++--------- spec/lib/wpscan/modules/wp_timthumbs_spec.rb | 24 +++--- spec/lib/wpscan/modules/wp_usernames_spec.rb | 7 +- spec/lib/wpscan/wp_options_spec.rb | 18 +--- wpscan.rb | 8 +- 17 files changed, 125 insertions(+), 132 deletions(-) diff --git a/lib/wpscan/modules/brute_force.rb b/lib/wpscan/modules/brute_force.rb index bebb9464..e3c93357 100644 --- a/lib/wpscan/modules/brute_force.rb +++ b/lib/wpscan/modules/brute_force.rb @@ -45,9 +45,11 @@ module BruteForce # the request object request = Browser.instance.forge_request(login_url, - :method => :post, - :params => {:log => username, :pwd => password}, - :cache_timeout => 0 + { + :method => :post, + :params => {:log => username, :pwd => password}, + :cache_timeout => 0 + } ) # tell hydra what to do when the request completes diff --git a/lib/wpscan/modules/web_site.rb b/lib/wpscan/modules/web_site.rb index 27d67e9c..2b5a3628 100644 --- a/lib/wpscan/modules/web_site.rb +++ b/lib/wpscan/modules/web_site.rb @@ -24,16 +24,14 @@ module WebSite wordpress = false response = Browser.instance.get(login_url(), - :follow_location => true, - :max_redirects => 2 + { :follow_location => true, :max_redirects => 2 } ) if response.body =~ %r{WordPress}i wordpress = true else response = Browser.instance.get(xmlrpc_url(), - :follow_location => true, - :max_redirects => 2 + { :follow_location => true, :max_redirects => 2 } ) if response.body =~ %r{XML-RPC server accepts POST requests only}i diff --git a/lib/wpscan/modules/wp_plugins.rb b/lib/wpscan/modules/wp_plugins.rb index 15cda935..78229e80 100644 --- a/lib/wpscan/modules/wp_plugins.rb +++ b/lib/wpscan/modules/wp_plugins.rb @@ -22,8 +22,8 @@ module WpPlugins # # return array of WpPlugin def plugins_from_aggressive_detection(options) - options[:file] = "#{DATA_DIR}/plugins.txt" - options[:vulns_file] = "#{DATA_DIR}/plugin_vulns.xml" + options[:file] = options[:file] || "#{DATA_DIR}/plugins.txt" + options[:vulns_file] = options[:vulns_file] || "#{DATA_DIR}/plugin_vulns.xml" options[:vulns_xpath] = "//plugin[@name='#{@name}']/vulnerability" options[:vulns_xpath_2] = "//plugin" options[:type] = "plugins" @@ -46,16 +46,16 @@ module WpPlugins # # ... # return array of WpPlugin - def plugins_from_passive_detection(wp_content_dir) + def plugins_from_passive_detection(options) plugins = [] - temp = WpDetector.passive_detection(url(), "plugins", wp_content_dir) + temp = WpDetector.passive_detection(options[:url], "plugins", options[:wp_content_dir]) temp.each do |item| plugins << WpPlugin.new( :url => item[:url], :name => item[:name], :path => item[:path], - :wp_content_dir => wp_content_dir + :wp_content_dir => options[:wp_content_dir] ) end plugins.sort_by { |p| p.name } diff --git a/lib/wpscan/modules/wp_themes.rb b/lib/wpscan/modules/wp_themes.rb index 0c4e61cf..bd3ae913 100644 --- a/lib/wpscan/modules/wp_themes.rb +++ b/lib/wpscan/modules/wp_themes.rb @@ -19,8 +19,8 @@ module WpThemes def themes_from_aggressive_detection(options) - options[:file] = "#{DATA_DIR}/themes.txt" - options[:vulns_file] = "#{DATA_DIR}/wp_theme_vulns.xml" + options[:file] = options[:file] || "#{DATA_DIR}/themes.txt" + options[:vulns_file] = options[:vulns_file] || "#{DATA_DIR}/wp_theme_vulns.xml" options[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability" options[:vulns_xpath_2] = "//theme" options[:type] = "themes" @@ -37,16 +37,16 @@ module WpThemes themes.sort_by { |t| t.name } end - def themes_from_passive_detection(wp_content_dir) + def themes_from_passive_detection(options) themes = [] - temp = WpDetector.passive_detection(url(), "themes", wp_content_dir) + temp = WpDetector.passive_detection(options[:url], "themes", options[:wp_content_dir]) temp.each do |item| themes << WpTheme.new( :url => item[:url], :name => item[:name], :path => item[:path], - :wp_content_dir => wp_content_dir + :wp_content_dir => options[:wp_content_dir] ) end themes.sort_by { |t| t.name } diff --git a/lib/wpscan/modules/wp_usernames.rb b/lib/wpscan/modules/wp_usernames.rb index d5e56872..821eebc0 100644 --- a/lib/wpscan/modules/wp_usernames.rb +++ b/lib/wpscan/modules/wp_usernames.rb @@ -39,7 +39,7 @@ module WpUsernames if response.code == 301 # username in location? username = response.headers_hash['location'][%r{/author/([^/]+)/}i, 1] # Get the real name from the redirect site - real_name = get_real_name_from_url(response.headers_hash['location']) + real_name = get_real_name_from_url(url) elsif response.code == 200 # username in body? username = response.body[%r{posts by (.*) feed}i, 1] real_name = get_real_name_from_response(response) @@ -62,7 +62,7 @@ module WpUsernames end def get_real_name_from_url(url) - resp = Browser.instance.get(url, :follow_location => true, :max_redirects => 2) + resp = Browser.instance.get(url, { :follow_location => true, :max_redirects => 2 }) real_name = nil if resp.code == 200 real_name = extract_real_name_from_body(resp.body) diff --git a/lib/wpscan/wp_enumerator.rb b/lib/wpscan/wp_enumerator.rb index 0d6bfc3f..ac2919a7 100644 --- a/lib/wpscan/wp_enumerator.rb +++ b/lib/wpscan/wp_enumerator.rb @@ -56,7 +56,7 @@ class WpEnumerator end url = "#{target[:url]}#{target[:wp_content_dir]}/#{target[:path]}" - request = enum_browser.forge_request(url, :cache_timeout => 0, :follow_location => true) + request = enum_browser.forge_request(url, { :cache_timeout => 0, :follow_location => true }) request_count += 1 request.on_complete do |response| @@ -116,17 +116,14 @@ class WpEnumerator # We check if the plugin name from the plugin_vulns_file is already in targets, otherwise we add it xml.xpath(options[:vulns_xpath_2]).each do |node| - item_name = node.attribute('name').text - - if targets_url.grep(%r{/#{item_name}/}).empty? - targets_url << { - :url => url, - :path => item_name, - :wp_content_dir => wp_content_dir, - :name => item_name - } + name = node.attribute("name").text + targets_url << { + :url => url, + :path => name, + :wp_content_dir => wp_content_dir, + :name => name + } end - end end targets_url.flatten! @@ -134,5 +131,4 @@ class WpEnumerator # randomize the plugins array to *maybe* help in some crappy IDS/IPS/WAF detection targets_url.sort_by! { rand } end - end diff --git a/lib/wpscan/wp_item.rb b/lib/wpscan/wp_item.rb index 4bff995a..a685e8e8 100644 --- a/lib/wpscan/wp_item.rb +++ b/lib/wpscan/wp_item.rb @@ -83,9 +83,19 @@ class WpItem < Vulnerable "#@name#{' v' + item_version.strip if item_version}" end - # Object comparer - def ==(item) - item.name == @name + # Compare + def ==(other) + other.name == self.name + end + + # Compare + def ===(other) + other.name == self.name + end + + # Compare + def <=>(other) + other.name <=> self.name end # Url for readme.txt diff --git a/lib/wpscan/wp_options.rb b/lib/wpscan/wp_options.rb index edd62591..e48c4196 100644 --- a/lib/wpscan/wp_options.rb +++ b/lib/wpscan/wp_options.rb @@ -31,22 +31,6 @@ # * +error_404_hash+ - MD5 hash of a 404 page # * +type+ - Type: plugins, themes class WpOptions - def self.get_empty_options - options = { - :url => "", - :only_vulnerable_ones => false, - :file => "", - :vulns_file => "", - :vulns_xpath => "", - :vulns_xpath_2 => "", - :wp_content_dir => "", - :show_progress_bar => true, - :error_404_hash => "", - :type => "" - } - options - end - def self.check_options(options) raise("url must be set") unless options[:url] != nil and options[:url].to_s.length > 0 raise("only_vulnerable_ones must be set") unless options[:only_vulnerable_ones] != nil diff --git a/lib/wpscan/wp_plugin.rb b/lib/wpscan/wp_plugin.rb index 40d6856c..3475f731 100644 --- a/lib/wpscan/wp_plugin.rb +++ b/lib/wpscan/wp_plugin.rb @@ -18,8 +18,10 @@ class WpPlugin < WpItem def initialize(options = {}) - options[:vulns_xml] = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml' - options[:vulns_xpath] = "//plugin[@name='#@name']/vulnerability" + options[:vulns_xml] = options[:vulns_xml] || DATA_DIR + '/plugin_vulns.xml' + options[:vulns_xpath] = "//plugin[@name='#@name']/vulnerability" + options[:vulns_xpath_2] = "//plugin" + options[:type] = "plugins" super(options) end @@ -36,5 +38,4 @@ class WpPlugin < WpItem def error_log_url get_url.merge("error_log").to_s end - end diff --git a/lib/wpscan/wp_target.rb b/lib/wpscan/wp_target.rb index 2adafb70..e13cbd83 100644 --- a/lib/wpscan/wp_target.rb +++ b/lib/wpscan/wp_target.rb @@ -117,4 +117,13 @@ class WpTarget @uri.merge("#{wp_content_dir()}/debug.log").to_s end + # Should check wp-login.php if registration is enabled or not + def registration_enabled? + # TODO + end + + def registration_url + # TODO + end + end diff --git a/lib/wpscan/wp_theme.rb b/lib/wpscan/wp_theme.rb index c4057437..a5c65079 100644 --- a/lib/wpscan/wp_theme.rb +++ b/lib/wpscan/wp_theme.rb @@ -24,7 +24,7 @@ class WpTheme < WpItem def initialize(options = {}) options[:vulns_xml] = options[:vulns_xml] || DATA_DIR + '/wp_theme_vulns.xml' - options[:vulns_xpath] = "//theme[@name='#{@name}']/vulnerability" + options[:vulns_xpath] = "//theme[@name='#@name']/vulnerability" @version = options[:version] @style_url = options[:style_url] super(options) @@ -56,7 +56,7 @@ class WpTheme < WpItem # Discover the wordpress theme name by parsing the css link rel def self.find_from_css_link(target_uri) - response = Browser.instance.get(target_uri.to_s, :follow_location => true, :max_redirects => 2) + response = Browser.instance.get(target_uri.to_s, { :follow_location => true, :max_redirects => 2 }) if matches = %r{https?://[^"']+/themes/([^"']+)/style.css}i.match(response.body) style_url = matches[0] diff --git a/lib/wpscan/wp_version.rb b/lib/wpscan/wp_version.rb index 2cc468ff..3441708d 100644 --- a/lib/wpscan/wp_version.rb +++ b/lib/wpscan/wp_version.rb @@ -60,14 +60,14 @@ class WpVersion < Vulnerable # that it is reinstated on upgrade. def self.find_from_meta_generator(options) target_uri = options[:url] - response = Browser.instance.get(target_uri.to_s, :follow_location => true, :max_redirects => 2) + response = Browser.instance.get(target_uri.to_s, { :follow_location => true, :max_redirects => 2 }) response.body[%r{name="generator" content="wordpress ([^"]+)"}i, 1] end def self.find_from_rss_generator(options) target_uri = options[:url] - response = Browser.instance.get(target_uri.merge("feed/").to_s, :follow_location => true, :max_redirects => 2) + response = Browser.instance.get(target_uri.merge("feed/").to_s, { :follow_location => true, :max_redirects => 2 }) response.body[%r{http://wordpress.org/\?v=([^<]+)}i, 1] end diff --git a/spec/lib/wpscan/modules/wp_plugins_spec.rb b/spec/lib/wpscan/modules/wp_plugins_spec.rb index 8d17efc2..ddf9f478 100644 --- a/spec/lib/wpscan/modules/wp_plugins_spec.rb +++ b/spec/lib/wpscan/modules/wp_plugins_spec.rb @@ -22,19 +22,38 @@ shared_examples_for "WpPlugins" do @fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + '/wp_plugins' @plugins_file = @fixtures_dir + "/plugins.txt" @plugin_vulns_file = @fixtures_dir + "/plugin_vulns.xml" + + @wp_url = "http://example.localhost/" end before :each do - @wp_url = "http://example.localhost" @module = WpScanModuleSpec.new(@wp_url) @module.error_404_hash = Digest::MD5.hexdigest("Error 404!") @module.extend(WpPlugins) @options = { :url => @wp_url, - :only_vulnerable_ones => true, + :only_vulnerable_ones => false, :show_progress_bar => false, - :error_404_hash => @module.error_404_hash + :error_404_hash => Digest::MD5.hexdigest("Error 404!"), + :vulns_file => @plugin_vulns_file, + :file => @plugins_file, + :type => "plugins", + :wp_content_dir => "wp-content", + :vulns_xpath_2 => "//plugin" } + File.exist?(@plugin_vulns_file).should == true + File.exist?(@plugins_file).should == true + target_hashes = WpEnumerator.generate_items(@options) + target_hashes.length.should > 0 + @targets = [] + target_hashes.each do |t| + @targets << WpPlugin.new( + :url => t[:url], + :path => "/plugins/#{t[:path]}", + :wp_content_dir => t[:wp_content_dir], + :name => t[:name]) + end + @targets.length.should > 0 end describe "#plugins_from_passive_detection" do @@ -42,8 +61,7 @@ shared_examples_for "WpPlugins" do it "should return an empty array" do stub_request_to_fixture(:url => @module.url, :fixture => File.new(passive_detection_fixtures + '/no_plugins.htm')) - - plugins = @module.plugins_from_passive_detection(@options) + plugins = @module.plugins_from_passive_detection(:url => @module.url, :wp_content_dir => "wp-content") plugins.should be_empty end @@ -66,42 +84,31 @@ shared_examples_for "WpPlugins" do :name => plugin_name) end - plugins = @module.plugins_from_passive_detection(@options) + plugins = @module.plugins_from_passive_detection(:url => @module.url, :wp_content_dir => "wp-content") plugins.should_not be_empty - plugins.sort.should === expected_plugins.sort + plugins.length.should == expected_plugins.length + plugins.sort.should == expected_plugins.sort end end describe "#plugins_from_aggressive_detection" do before :each do - @wp_url = "http://example.localhost" - @module = WpScanModuleSpec.new(@wp_url) - @module.error_404_hash = Digest::MD5.hexdigest("Error 404!") - @module.extend(WpPlugins) - @options = { :url => @wp_url, - :only_vulnerable_ones => true, - :show_progress_bar => false, - :error_404_hash => @module.error_404_hash, - :vulns_file => @plugin_vulns_file, - :file => @plugins_file - } - @targets_url = WpEnumerator.generate_items(@options) + stub_request(:get, @module.uri.to_s).to_return(:status => 200) # Point all targets to a 404 - @targets_url.each do |target| - stub_request(:get, "#{target[:url]}#{target[:wp_content_dir]}/#{target[:path]}").to_return(:status => 404) + @targets.each do |target| + stub_request(:get, target.get_url.to_s).to_return(:status => 404) + # to_s calls readme_url + stub_request(:get, target.readme_url.to_s).to_return(:status => 404) end end after :each do @passive_detection_fixture = SPEC_FIXTURES_DIR + "/empty-file" unless @passive_detection_fixture - - stub_request_to_fixture(:url => @wp_url, :fixture => @passive_detection_fixture) - - @module.plugins_from_aggressive_detection( - :plugins_file => @plugins_file, - :plugin_vulns_file => @plugin_vulns_file - ).sort.should === @expected_plugins.sort + stub_request_to_fixture(:url => "#{@module.uri}/".sub(/\/\/$/, "/") + "wp-content/plugins/", :fixture => @passive_detection_fixture) + detected = @module.plugins_from_aggressive_detection(@options) + detected.length.should == @expected_plugins.length + detected.sort.should == @expected_plugins.sort end it "should return an empty array" do @@ -109,25 +116,24 @@ shared_examples_for "WpPlugins" do end it "should return an array with 3 WpPlugin (1 detected from passive method)" do - @expected_plugins = [] - - @targets_url.sample(2).each do |target_url| - @expected_plugins << WpPlugin.new(target_url) - stub_request(:get, target_url).to_return(:status => 200) - end - @passive_detection_fixture = @fixtures_dir + "/passive_detection/one_plugin.htm" - @expected_plugins << WpPlugin.new("http://example.localhost/wp-content/plugins/comment-info-tip/") + @expected_plugins = @targets.sample(2) + new_plugin = WpPlugin.new(:url => "http://example.localhost/", + :path => "/plugins/comment-info-tip/", + :name => "comment-info-tip") + stub_request(:get, new_plugin.readme_url.to_s).to_return(:status => 200) + @expected_plugins << new_plugin end # testing response codes WpTarget.valid_response_codes.each do |valid_response_code| it "should detect the plugin if the reponse.code is #{valid_response_code}" do @expected_plugins = [] - - plugin_url = @targets_url.sample - @expected_plugins << WpPlugin.new(plugin_url) - stub_request(:get, plugin_url).to_return(:status => valid_response_code) + plugin_url = [@targets.sample(1)[0]] + plugin_url.should_not be_nil + plugin_url.length.should == 1 + @expected_plugins = plugin_url + stub_request(:get, plugin_url[0].get_url.to_s).to_return(:status => valid_response_code) end end end diff --git a/spec/lib/wpscan/modules/wp_timthumbs_spec.rb b/spec/lib/wpscan/modules/wp_timthumbs_spec.rb index 8e1aa9b0..82abb7c3 100644 --- a/spec/lib/wpscan/modules/wp_timthumbs_spec.rb +++ b/spec/lib/wpscan/modules/wp_timthumbs_spec.rb @@ -19,17 +19,19 @@ shared_examples_for "WpTimthumbs" do before :each do - @options = WpOptions.get_empty_options - @url = "http://example.localhost/" - @theme_name = "bueno" - @options[:url] = @url - @options[:wp_content_dir] = "wp-content" - @options[:name] = @theme_name - @options[:error_404_hash] = "xx" - @module = WpScanModuleSpec.new(@url) - @fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + "/wp_timthumbs" - @timthumbs_file = @fixtures_dir + "/timthumbs.txt" - @targets_from_file = + @options = {} + @url = "http://example.localhost/" + @theme_name = "bueno" + @options[:url] = @url + @options[:wp_content_dir] = "wp-content" + @options[:name] = @theme_name + @options[:error_404_hash] = "xx" + @options[:show_progress_bar] = false + @options[:only_vulnerable_ones] = false + @module = WpScanModuleSpec.new(@url) + @fixtures_dir = SPEC_FIXTURES_WPSCAN_MODULES_DIR + "/wp_timthumbs" + @timthumbs_file = @fixtures_dir + "/timthumbs.txt" + @targets_from_file = %w{ http://example.localhost/wp-content/plugins/fotoslide/timthumb.php http://example.localhost/wp-content/plugins/feature-slideshow/timthumb.php diff --git a/spec/lib/wpscan/modules/wp_usernames_spec.rb b/spec/lib/wpscan/modules/wp_usernames_spec.rb index e8022f23..89f4e586 100644 --- a/spec/lib/wpscan/modules/wp_usernames_spec.rb +++ b/spec/lib/wpscan/modules/wp_usernames_spec.rb @@ -28,7 +28,7 @@ shared_examples_for "WpUsernames" do describe "#author_url" do it "should return the auhor url according to his id" do - @module.author_url(1).should === "#{@target_url}?author=1" + @module.author_url(1).should === "#@target_url?author=1" end end @@ -49,7 +49,8 @@ shared_examples_for "WpUsernames" do usernames = @module.usernames usernames.should_not be_empty - usernames.should === ["Youhou"] + usernames.length.should == 1 + usernames[0].should == "id: 3, name: Youhou" end it "should return an array with 1 username (from in the body response)" do @@ -58,7 +59,7 @@ shared_examples_for "WpUsernames" do usernames = @module.usernames(:range => (1..2)) usernames.should_not be_empty - usernames.should === ["admin"] + usernames.should === ["id: 2, name: admin, real name: admin | Wordpress 3.3.2"] end it "should return an array with 1 username (testing duplicates)" do diff --git a/spec/lib/wpscan/wp_options_spec.rb b/spec/lib/wpscan/wp_options_spec.rb index 62dfdf35..cad297b2 100644 --- a/spec/lib/wpscan/wp_options_spec.rb +++ b/spec/lib/wpscan/wp_options_spec.rb @@ -19,25 +19,9 @@ require File.expand_path(File.dirname(__FILE__) + '/wpscan_helper') describe WpOptions do - describe "#get_empty_options" do - it "should initialize an empty options hash" do - options = WpOptions.get_empty_options - options[:url].should == "" - options[:only_vulnerable_ones].should == false - options[:file].should == "" - options[:vulns_file].should == "" - options[:vulns_xpath].should == "" - options[:vulns_xpath_2].should == "" - options[:wp_content_dir].should == "" - options[:show_progress_bar].should == true - options[:error_404_hash].should == "" - options[:type].should == "" - end - end - describe "#check_options" do before :each do - @options = WpOptions.get_empty_options + @options = {} @options[:url] = "url" @options[:only_vulnerable_ones] = false @options[:file] = "file" diff --git a/wpscan.rb b/wpscan.rb index 2a51208e..b864ab83 100755 --- a/wpscan.rb +++ b/wpscan.rb @@ -153,7 +153,7 @@ begin puts puts "[+] Enumerating plugins from passive detection ... " - plugins = wp_target.plugins_from_passive_detection(wp_target.wp_content_dir) + plugins = wp_target.plugins_from_passive_detection(:url => wp_target.uri, :wp_content_dir => wp_target.wp_content_dir) unless plugins.empty? puts "#{plugins.size} found :" @@ -179,7 +179,7 @@ begin puts "[+] Enumerating installed plugins #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_plugins} ..." puts - options = WpOptions.get_empty_options + options = {} options[:url] = wp_target.uri options[:only_vulnerable_ones] = wpscan_options.enumerate_only_vulnerable_plugins || false options[:show_progress_bar] = true @@ -233,7 +233,7 @@ begin puts "[+] Enumerating installed themes #{'(only vulnerable ones)' if wpscan_options.enumerate_only_vulnerable_themes} ..." puts - options = WpOptions.get_empty_options + options = {} options[:url] = wp_target.uri options[:only_vulnerable_ones] = wpscan_options.enumerate_only_vulnerable_themes || false options[:show_progress_bar] = true @@ -279,7 +279,7 @@ begin puts "[+] Enumerating timthumb files ..." puts - options = WpOptions.get_empty_options + options = {} options[:url] = wp_target.uri options[:show_progress_bar] = true options[:wp_content_dir] = wp_target.wp_content_dir