From d802799bd207883a37c862ad8ed90a20ca744c03 Mon Sep 17 00:00:00 2001 From: Erwan Date: Thu, 22 Nov 2012 15:23:59 +0100 Subject: [PATCH] Ref #69, #7 Proxy Auth Support Added --- conf/browser.conf.json | 3 +- lib/browser.rb | 28 ++++ lib/wpscan/wpscan_options.rb | 16 +- .../conf/browser/browser.conf_proxy_auth.json | 8 + spec/lib/browser_spec.rb | 144 ++++++++++++------ spec/lib/wpscan/wpscan_options_spec.rb | 54 ++++--- 6 files changed, 186 insertions(+), 67 deletions(-) create mode 100644 spec/fixtures/conf/browser/browser.conf_proxy_auth.json diff --git a/conf/browser.conf.json b/conf/browser.conf.json index 8f963617..a4567610 100644 --- a/conf/browser.conf.json +++ b/conf/browser.conf.json @@ -11,7 +11,8 @@ SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000" If you do not specify the protocol, http will be used */ - //"proxy": "127.0.0.1:3038", + //"proxy": "127.0.0.1:3128", + //"proxy_auth": "username:password" "cache_timeout": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled diff --git a/lib/browser.rb b/lib/browser.rb index 9daae9b7..da230e74 100644 --- a/lib/browser.rb +++ b/lib/browser.rb @@ -25,6 +25,7 @@ class Browser :user_agent_mode, :available_user_agents, :proxy, + :proxy_auth, :max_threads, :cache_timeout, :request_timeout @@ -98,6 +99,29 @@ class Browser @max_threads = max_threads end + def proxy_auth=(auth) + unless auth.nil? + if auth.is_a?(Hash) + if !auth.include?(:proxy_username) or !auth.include?(:proxy_password) + raise_invalid_proxy_format() + end + @proxy_auth = auth + elsif auth.is_a?(String) + if matches = %r{([^:]+):(.*)}.match(auth) + @proxy_auth = {:proxy_username => matches[1], :proxy_password => matches[2]} + else + raise_invalid_proxy_format() + end + else + raise_invalid_proxy_format() + end + end + end + + def raise_invalid_proxy_format + raise "Invalid proxy auth format, expected username:password or {:proxy_username => username, :proxy_password => password}" + end + # TODO reload hydra (if the .load_config is called on a browser object, hydra will not have the new @max_threads and @request_timeout) def load_config(config_file = nil) @config_file = config_file || @config_file @@ -149,6 +173,10 @@ class Browser def merge_request_params(params = {}) if @proxy params = params.merge(:proxy => @proxy) + + if @proxy_auth + params = params.merge(@proxy_auth) + end end unless params.has_key?(:disable_ssl_host_verification) diff --git a/lib/wpscan/wpscan_options.rb b/lib/wpscan/wpscan_options.rb index 4b0c9fba..d993b4e1 100644 --- a/lib/wpscan/wpscan_options.rb +++ b/lib/wpscan/wpscan_options.rb @@ -27,6 +27,7 @@ class WpscanOptions :enumerate_usernames, :enumerate_usernames_range, :proxy, + :proxy_auth, :threads, :url, :wordlist, @@ -76,6 +77,14 @@ class WpscanOptions end end + def proxy_auth=(auth) + if auth.index(':') == nil + raise "Invalid proxy auth format, username:password expected" + else + @proxy_auth = auth + end + end + def enumerate_plugins=(enumerate_plugins) if enumerate_plugins === true and @enumerate_only_vulnerable_plugins === true raise "You can't enumerate plugins and only vulnerable plugins at the same time, please choose only one" @@ -150,7 +159,7 @@ class WpscanOptions cli_value ) elsif cli_option === "--enumerate" # Special cases - # Default value if no argument is given + # Default value if no argument is given cli_value = "vt,tt,u,vp" if cli_value.length == 0 enumerate_options_from_string(cli_value) @@ -201,7 +210,8 @@ class WpscanOptions ["--force", "-f", GetoptLong::NO_ARGUMENT], ["--help", "-h", GetoptLong::NO_ARGUMENT], ["--verbose", "-v", GetoptLong::NO_ARGUMENT], - ["--proxy", GetoptLong::OPTIONAL_ARGUMENT], + ["--proxy", GetoptLong::REQUIRED_ARGUMENT], + ["--proxy-auth", GetoptLong::REQUIRED_ARGUMENT], ["--update", GetoptLong::NO_ARGUMENT], ["--follow-redirection", GetoptLong::NO_ARGUMENT], ["--wp-content-dir", GetoptLong::REQUIRED_ARGUMENT], @@ -226,7 +236,7 @@ class WpscanOptions def self.option_to_instance_variable_setter(option) cleaned_option = WpscanOptions.clean_option(option) - option_syms = ACCESSOR_OPTIONS.grep(%r{^#{cleaned_option}}) + option_syms = ACCESSOR_OPTIONS.grep(%r{^#{cleaned_option}$}) option_syms.length == 1 ? :"#{option_syms.at(0)}=" : nil end diff --git a/spec/fixtures/conf/browser/browser.conf_proxy_auth.json b/spec/fixtures/conf/browser/browser.conf_proxy_auth.json new file mode 100644 index 00000000..75e10fdc --- /dev/null +++ b/spec/fixtures/conf/browser/browser.conf_proxy_auth.json @@ -0,0 +1,8 @@ +{ + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0) Gecko/20100101 Firefox/11.0", + "user_agent_mode": "static", + "proxy": "127.0.0.1:3038", + "proxy_auth": "user:pass", + "cache_timeout": 300, + "request_timeout": 2000 +} diff --git a/spec/lib/browser_spec.rb b/spec/lib/browser_spec.rb index adc7724c..9c0acdd0 100644 --- a/spec/lib/browser_spec.rb +++ b/spec/lib/browser_spec.rb @@ -19,13 +19,14 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Browser do - CONFIG_FILE_WITHOUT_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json' - CONFIG_FILE_WITH_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf_proxy.json' - INSTANCE_VARS_TO_CHECK = ['user_agent', 'user_agent_mode', 'available_user_agents', 'proxy', 'max_threads', 'request_timeout', 'cache_timeout'] + CONFIG_FILE_WITHOUT_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json' + CONFIG_FILE_WITH_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf_proxy.json' + CONFIG_FILE_WITH_PROXY_AND_AUTH = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf_proxy_auth.json' + INSTANCE_VARS_TO_CHECK = ['user_agent', 'user_agent_mode', 'available_user_agents', 'proxy', 'max_threads', 'request_timeout', 'cache_timeout'] before :all do @json_config_without_proxy = JSON.parse(File.read(CONFIG_FILE_WITHOUT_PROXY)) - @json_config_with_proxy = JSON.parse(File.read(CONFIG_FILE_WITH_PROXY)) + @json_config_with_proxy = JSON.parse(File.read(CONFIG_FILE_WITH_PROXY)) end before :each do @@ -71,6 +72,42 @@ describe Browser do end end + describe "#proxy_auth=" do + after :each do + if @raise_error + expect { @browser.proxy_auth = @proxy_auth }.to raise_error + else + @browser.proxy_auth = @proxy_auth + @browser.proxy_auth.should === @expected + end + end + + it "should raise an error if the format is not correct" do + @proxy_auth = "invaludauthformat" + @raise_error = true + end + + it "should raise an error if the hash does not contain :proxy_username and :proxy_password" do + @proxy_auth = { :proxy_password => "hello" } + @raise_error = true + end + + it "should raise an error if the auth if not a string or a hash" do + @proxy_auth = 10 + @raise_error = true + end + + it "should set the correct credentials" do + @proxy_auth = {:proxy_username => "user", :proxy_password => "pass" } + @expected = @proxy_auth + end + + it "should set the correct credentials" do + @proxy_auth = "username:passwd" + @expected = {:proxy_username => "username", :proxy_password => "passwd" } + end + end + describe "#user_agent" do available_user_agents = %w{ ua-1 ua-2 ua-3 ua-4 ua-6 ua-7 ua-8 ua-9 ua-10 ua-11 ua-12 ua-13 ua-14 ua-15 ua-16 ua-17} @@ -115,8 +152,8 @@ describe Browser do it "will check the instance vars" do Browser.reset check_instance_variables( - Browser.instance(:config_file => CONFIG_FILE_WITHOUT_PROXY), - @json_config_without_proxy + Browser.instance(:config_file => CONFIG_FILE_WITHOUT_PROXY), + @json_config_without_proxy ) end end @@ -125,8 +162,8 @@ describe Browser do it "will check the instance vars" do Browser.reset check_instance_variables( - Browser.instance(:config_file => CONFIG_FILE_WITH_PROXY), - @json_config_with_proxy + Browser.instance(:config_file => CONFIG_FILE_WITH_PROXY), + @json_config_with_proxy ) end end @@ -136,26 +173,27 @@ describe Browser do it "will check the instance vars, with an overriden one" do Browser.reset check_instance_variables( - Browser.instance( - :config_file => CONFIG_FILE_WITHOUT_PROXY, - :user_agent => "fake IE" - ), - @json_config_without_proxy.merge("user_agent" => "fake IE") + Browser.instance( + :config_file => CONFIG_FILE_WITHOUT_PROXY, + :user_agent => "fake IE" + ), + @json_config_without_proxy.merge("user_agent" => "fake IE") ) end it "should not override the max_threads if max_threads = nil" do Browser.reset check_instance_variables( - Browser.instance( - :config_file => CONFIG_FILE_WITHOUT_PROXY, - :max_threads => nil - ), - @json_config_without_proxy + Browser.instance( + :config_file => CONFIG_FILE_WITHOUT_PROXY, + :max_threads => nil + ), + @json_config_without_proxy ) end end + # TODO describe "#load_config" do end @@ -163,10 +201,10 @@ describe Browser do describe "#merge_request_params without proxy" do it "should return the default params" do expected_params = { - :disable_ssl_host_verification => true, - :disable_ssl_peer_verification => true, - :headers => {'user-agent' => @browser.user_agent}, - :cache_timeout => @json_config_without_proxy['cache_timeout'] + :disable_ssl_host_verification => true, + :disable_ssl_peer_verification => true, + :headers => {'user-agent' => @browser.user_agent}, + :cache_timeout => @json_config_without_proxy['cache_timeout'] } @browser.merge_request_params().should == expected_params @@ -174,25 +212,25 @@ describe Browser do it "should return the default params with some values overriden" do expected_params = { - :disable_ssl_host_verification => false, - :disable_ssl_peer_verification => true, - :headers => {'user-agent' => 'Fake IE'}, - :cache_timeout => 0 + :disable_ssl_host_verification => false, + :disable_ssl_peer_verification => true, + :headers => {'user-agent' => 'Fake IE'}, + :cache_timeout => 0 } @browser.merge_request_params( - :disable_ssl_host_verification => false, - :headers => {'user-agent' => 'Fake IE'}, - :cache_timeout => 0 + :disable_ssl_host_verification => false, + :headers => {'user-agent' => 'Fake IE'}, + :cache_timeout => 0 ).should == expected_params end it "should return the defaul params with :headers:accept = 'text/html' (should not override :headers:user-agent)" do expected_params = { - :disable_ssl_host_verification => true, - :disable_ssl_peer_verification => true, - :headers => {'user-agent' => @browser.user_agent, 'accept' => 'text/html'}, - :cache_timeout => @json_config_without_proxy['cache_timeout'] + :disable_ssl_host_verification => true, + :disable_ssl_peer_verification => true, + :headers => {'user-agent' => @browser.user_agent, 'accept' => 'text/html'}, + :cache_timeout => @json_config_without_proxy['cache_timeout'] } @browser.merge_request_params(:headers => {'accept' => 'text/html'}).should == expected_params @@ -205,11 +243,28 @@ describe Browser do browser = Browser.instance(:config_file => CONFIG_FILE_WITH_PROXY) expected_params = { - :proxy => @json_config_with_proxy['proxy'], - :disable_ssl_host_verification => true, - :disable_ssl_peer_verification => true, - :headers => {'user-agent' => @json_config_with_proxy['user_agent']}, - :cache_timeout => @json_config_with_proxy['cache_timeout'] + :proxy => @json_config_with_proxy['proxy'], + :disable_ssl_host_verification => true, + :disable_ssl_peer_verification => true, + :headers => {'user-agent' => @json_config_with_proxy['user_agent']}, + :cache_timeout => @json_config_with_proxy['cache_timeout'] + } + + browser.merge_request_params().should == expected_params + end + + it "should return the default params (proxy_auth set)" do + Browser.reset + browser = Browser.instance(:config_file => CONFIG_FILE_WITH_PROXY_AND_AUTH) + + expected_params = { + :proxy => @json_config_with_proxy['proxy'], + :proxy_username => "user", + :proxy_password => "pass", + :disable_ssl_host_verification => true, + :disable_ssl_peer_verification => true, + :headers => {'user-agent' => @json_config_with_proxy['user_agent']}, + :cache_timeout => @json_config_with_proxy['cache_timeout'] } browser.merge_request_params().should == expected_params @@ -226,11 +281,12 @@ describe Browser do url = 'http://example.com/' stub_request(:post, url). - with(:body => "login=master&password=it's me !"). - to_return(:status => 200, :body => "Welcome Master") + with(:body => "login=master&password=it's me !"). + to_return(:status => 200, :body => "Welcome Master") - response = @browser.post(url, - :params => {:login => "master", :password => "it's me !"} + response = @browser.post( + url, + :params => {:login => "master", :password => "it's me !"} ) response.should be_a Typhoeus::Response @@ -243,7 +299,7 @@ describe Browser do url = 'http://example.com/' stub_request(:get, url). - to_return(:status => 200, :body => "Hello World !") + to_return(:status => 200, :body => "Hello World !") response = @browser.get(url) @@ -278,7 +334,7 @@ describe Browser do url = 'http://example.localhost' stub_request(:get, url). - to_return(:status => 200, :body => "Hello World !") + to_return(:status => 200, :body => "Hello World !") response1 = @browser.get(url) response2 = @browser.get(url) diff --git a/spec/lib/wpscan/wpscan_options_spec.rb b/spec/lib/wpscan/wpscan_options_spec.rb index ea896230..8c52a3c8 100644 --- a/spec/lib/wpscan/wpscan_options_spec.rb +++ b/spec/lib/wpscan/wpscan_options_spec.rb @@ -89,11 +89,24 @@ describe "WpscanOptions" do end end + describe "#proxy_auth=" do + it "should raise an error if the format is not correct" do + expect { @wpscan_options.proxy_auth = "invalidauth" }.to raise_error + end + + it "should not raise en error" do + proxy_auth = "user:pass" + @wpscan_options.proxy_auth = proxy_auth + @wpscan_options.proxy_auth.should === proxy_auth + end + end + describe "#enumerate_plugins=" do it "should raise an error" do @wpscan_options.enumerate_only_vulnerable_plugins = true - expect { @wpscan_options.enumerate_plugins = true }.to raise_error(RuntimeError, - "You can't enumerate plugins and only vulnerable plugins at the same time, please choose only one") + expect { @wpscan_options.enumerate_plugins = true }.to raise_error( + RuntimeError, "You can't enumerate plugins and only vulnerable plugins at the same time, please choose only one" + ) end it "should not raise an error" do @@ -107,8 +120,9 @@ describe "WpscanOptions" do describe "#enumerate_themes=" do it "should raise an error" do @wpscan_options.enumerate_only_vulnerable_themes = true - expect { @wpscan_options.enumerate_themes = true }.to raise_error(RuntimeError, - "You can't enumerate themes and only vulnerable themes at the same time, please choose only one") + expect { @wpscan_options.enumerate_themes = true }.to raise_error( + RuntimeError, "You can't enumerate themes and only vulnerable themes at the same time, please choose only one" + ) end it "should not raise an error" do @@ -122,8 +136,9 @@ describe "WpscanOptions" do describe "#enumerate_only_vulnerable_plugins=" do it "should raise an error" do @wpscan_options.enumerate_plugins = true - expect { @wpscan_options.enumerate_only_vulnerable_plugins = true }.to raise_error(RuntimeError, - "You can't enumerate plugins and only vulnerable plugins at the same time, please choose only one") + expect { @wpscan_options.enumerate_only_vulnerable_plugins = true }.to raise_error( + RuntimeError, "You can't enumerate plugins and only vulnerable plugins at the same time, please choose only one" + ) end it "should not raise an error" do @@ -137,8 +152,9 @@ describe "WpscanOptions" do describe "#enumerate_only_vulnerable_themes=" do it "should raise an error" do @wpscan_options.enumerate_themes = true - expect { @wpscan_options.enumerate_only_vulnerable_themes = true }.to raise_error(RuntimeError, - "You can't enumerate themes and only vulnerable themes at the same time, please choose only one") + expect { @wpscan_options.enumerate_only_vulnerable_themes = true }.to raise_error( + RuntimeError, "You can't enumerate themes and only vulnerable themes at the same time, please choose only one" + ) end it "should not raise an error" do @@ -200,19 +216,19 @@ describe "WpscanOptions" do WpscanOptions.option_to_instance_variable_setter(@argument).should === @expected end - it "should return @url" do + it "should return :url=" do @argument = "--url" @expected = :url= end - it "should return @verbose" do - @argument = "-v" + it "should return :verbose=" do + @argument = "--verbose" @expected = :verbose= end - it "should return nil for -U" do - @argument = "-U" - @expected = nil + it "should return :proxy= for --proxy" do + @argument = "--proxy" + @expected = :proxy= end it "should return nil for --enumerate" do @@ -220,9 +236,9 @@ describe "WpscanOptions" do @expected = nil end - it "should return nil for -e" do - @argument = "-e" - @expected = nil + it "should return :proxy_auth= for --proxy_auth" do + @argument = "--proxy_auth" + @expected = :proxy_auth= end end @@ -279,8 +295,8 @@ describe "WpscanOptions" do it "should set enumerate_timthumbs to true, enumerate_usernames to true, enumerate_usernames_range to (1..2)" do @argument = "u[1-2],tt" @expected_hash = { - :enumerate_usernames => true, :enumerate_usernames_range => (1..2), - :enumerate_timthumbs => true + :enumerate_usernames => true, :enumerate_usernames_range => (1..2), + :enumerate_timthumbs => true } end end