diff --git a/app/controllers/vuln_api.rb b/app/controllers/vuln_api.rb index 605f0ccc..8bd5ba1f 100644 --- a/app/controllers/vuln_api.rb +++ b/app/controllers/vuln_api.rb @@ -8,7 +8,10 @@ module WPScan def cli_options [ - OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data']) + OptString.new( + ['--api-token TOKEN', + 'The WPScan API Token to display vulnerability data, available at https://wpscan.com/profile'] + ) ] end @@ -19,7 +22,7 @@ module WPScan api_status = DB::VulnApi.status - raise Error::InvalidApiToken if api_status['error'] + raise Error::InvalidApiToken if api_status['status'] == 'forbidden' raise Error::ApiLimitReached if api_status['requests_remaining'] == 0 raise api_status['http_error'] if api_status['http_error'] end diff --git a/app/views/cli/vuln_api/status.erb b/app/views/cli/vuln_api/status.erb index 3edebef8..2da88516 100644 --- a/app/views/cli/vuln_api/status.erb +++ b/app/views/cli/vuln_api/status.erb @@ -1,13 +1,13 @@ <% unless @status.empty? -%> <% if @status['http_error'] -%> -<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %> +<%= critical_icon %> WPScan DB API, <%= @status['http_error'].to_s %> <% else -%> -<%= info_icon %> WPVulnDB API OK +<%= info_icon %> WPScan DB API OK | Plan: <%= @status['plan'] %> | Requests Done (during the scan): <%= @api_requests %> | Requests Remaining: <%= @status['requests_remaining'] %> <% end -%> <% else -%> -<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output. +<%= warning_icon %> No WPScan API Token given, as a result vulnerability data has not been output. <%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpscan.com/register <% end -%> diff --git a/app/views/json/vuln_api/status.erb b/app/views/json/vuln_api/status.erb index f24ad2c6..308508e7 100644 --- a/app/views/json/vuln_api/status.erb +++ b/app/views/json/vuln_api/status.erb @@ -8,6 +8,6 @@ "requests_remaining": <%= @status['requests_remaining'].to_json %> <% end -%> <% else -%> -"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpscan.com/register" +"error": "No WPScan API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpscan.com/register" <% end -%> }, \ No newline at end of file diff --git a/lib/wpscan/db/vuln_api.rb b/lib/wpscan/db/vuln_api.rb index 07822f26..d9015925 100644 --- a/lib/wpscan/db/vuln_api.rb +++ b/lib/wpscan/db/vuln_api.rb @@ -4,7 +4,7 @@ module WPScan module DB # WPVulnDB API class VulnApi - NON_ERROR_CODES = [200, 401].freeze + NON_ERROR_CODES = [200, 403].freeze class << self attr_accessor :token @@ -26,7 +26,7 @@ module WPScan # Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI res = Typhoeus.get(uri.join(path), default_request_params.merge(params)) - return {} if res.code == 404 # This is for API inconsistencies when dots in path + return {} if res.code == 404 || res.code == 429 return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code) raise Error::HTTP, res @@ -34,6 +34,8 @@ module WPScan retries ||= 0 if (retries += 1) <= 3 + @default_request_params[:headers]['X-Retry'] = retries + sleep(1) retry end @@ -68,7 +70,7 @@ module WPScan # @return [ Hash ] # @note Those params can not be overriden by CLI options def self.default_request_params - Browser.instance.default_connect_request_params.merge( + @default_request_params ||= Browser.instance.default_connect_request_params.merge( headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Authorization' => "Token token=#{token}" diff --git a/spec/app/controllers/vuln_api_spec.rb b/spec/app/controllers/vuln_api_spec.rb index dcae474b..08f609a3 100644 --- a/spec/app/controllers/vuln_api_spec.rb +++ b/spec/app/controllers/vuln_api_spec.rb @@ -7,6 +7,7 @@ describe WPScan::Controller::VulnApi do before do WPScan::ParsedCli.options = rspec_parsed_options(cli_args) + WPScan::DB::VulnApi.instance_variable_set(:'@default_request_params', nil) end describe '#cli_options' do @@ -27,7 +28,7 @@ describe WPScan::Controller::VulnApi do let(:cli_args) { "#{super()} --api-token token" } context 'when the token is invalid' do - before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('error' => 'HTTP Token: Access denied.') } + before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('status' => 'forbidden') } it 'raise an InvalidApiToken error' do expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken) diff --git a/spec/lib/db/vuln_api_spec.rb b/spec/lib/db/vuln_api_spec.rb index 79b6b1af..15c1be0e 100644 --- a/spec/lib/db/vuln_api_spec.rb +++ b/spec/lib/db/vuln_api_spec.rb @@ -10,6 +10,11 @@ describe WPScan::DB::VulnApi do } end + before do + # Reset the default_request_params + api.instance_variable_set(:'@default_request_params', nil) + end + describe '#uri' do its(:uri) { should be_a Addressable::URI } end @@ -66,20 +71,16 @@ describe WPScan::DB::VulnApi do let(:body) { { data: 'something' }.to_json } it 'returns the expected hash' do - result = api.get(path) - - expect(result).to eql('data' => 'something') + expect(api.get(path)).to eql('data' => 'something') end end - context 'when 401' do - let(:code) { 401 } - let(:body) { { error: 'HTTP Token: Access denied.' }.to_json } + context 'when 403' do + let(:code) { 403 } + let(:body) { { status: 'forbidden' }.to_json } it 'returns the expected hash' do - result = api.get(path) - - expect(result).to eql('error' => 'HTTP Token: Access denied.') + expect(api.get(path)).to eql('status' => 'forbidden') end end @@ -88,20 +89,16 @@ describe WPScan::DB::VulnApi do let(:body) { { error: 'Not found' }.to_json } it 'returns an empty hash' do - result = api.get(path) - - expect(result).to eql({}) + expect(api.get(path)).to eql({}) end + end - context 'when 404 with HTTML (API inconsistency due to dots in path)' do - let(:path) { 'path.b.c' } - let(:body) { 'Nop' } + context 'when 429 (API Limit Reached)' do + let(:code) { 429 } + let(:body) { { status: 'rate limit hit' }.to_json } - it 'returns an empty hash' do - result = api.get(path) - - expect(result).to eql({}) - end + it 'returns an empty hash' do + expect(api.get(path)).to eql({}) end end end @@ -120,6 +117,7 @@ describe WPScan::DB::VulnApi do result = api.get('path') expect(result['http_error']).to be_a WPScan::Error::HTTP + expect(api.default_request_params[:headers]['X-Retry']).to eql 3 end end @@ -134,9 +132,8 @@ describe WPScan::DB::VulnApi do it 'tries 1 time and returns expected data' do expect(api).to receive(:sleep).with(1).exactly(1).times - result = api.get('path') - - expect(result).to eql('data' => 'test') + expect(api.get('path')).to eql('data' => 'test') + expect(api.default_request_params[:headers]['X-Retry']).to eql 1 end end end @@ -173,6 +170,15 @@ describe WPScan::DB::VulnApi do expect(api.plugin_data('slug-404')).to eql({}) end end + + context 'when API limit reached' do + it 'returns an empty hash' do + stub_request(:get, api.uri.join('plugins/slug-429')) + .to_return(status: 429, body: { status: 'rate limit hit' }.to_json) + + expect(api.plugin_data('slug-429')).to eql({}) + end + end end end @@ -206,6 +212,15 @@ describe WPScan::DB::VulnApi do expect(api.theme_data('slug-404')).to eql({}) end end + + context 'when API limit reached' do + it 'returns an empty hash' do + stub_request(:get, api.uri.join('themes/slug-429')) + .to_return(status: 429, body: { status: 'rate limit hit' }.to_json) + + expect(api.theme_data('slug-429')).to eql({}) + end + end end end @@ -239,6 +254,15 @@ describe WPScan::DB::VulnApi do expect(api.wordpress_data('1.1')).to eql({}) end end + + context 'when API limit reached' do + it 'returns an empty hash' do + stub_request(:get, api.uri.join('wordpresses/429')) + .to_return(status: 429, body: { status: 'rate limit hit' }.to_json) + + expect(api.wordpress_data('4.2.9')).to eql({}) + end + end end end @@ -285,7 +309,7 @@ describe WPScan::DB::VulnApi do WPScan::Browser.instance.headers = { 'CF-Connecting-IP' => '123.123.123.123' } end - it 'removes the CF-Connecting-IP header from the request' do + it 'does not contain the CF-Connecting-IP header in the request' do status = api.status expect(status['success']).to be true @@ -295,21 +319,20 @@ describe WPScan::DB::VulnApi do end end - context 'when 401' do - let(:code) { 401 } - let(:return_body) { { error: 'HTTP Token: Access denied.' } } + # When invalid/empty API token + context 'when 403' do + let(:code) { 403 } + let(:return_body) { { status: 'forbidden' } } it 'returns the expected hash' do - status = api.status - - expect(status['error']).to eql 'HTTP Token: Access denied.' + expect(api.status['status']).to eql 'forbidden' end end context 'otherwise' do let(:code) { 0 } - it 'returns the expected hash with the response' do + it 'returns the expected hash with the response as an exception' do status = api.status expect(status['http_error']).to be_a WPScan::Error::HTTP diff --git a/spec/output/vuln_api/all_ok.cli_no_colour b/spec/output/vuln_api/all_ok.cli_no_colour index f95873d1..c02717b4 100644 --- a/spec/output/vuln_api/all_ok.cli_no_colour +++ b/spec/output/vuln_api/all_ok.cli_no_colour @@ -1,4 +1,4 @@ -[+] WPVulnDB API OK +[+] WPScan DB API OK | Plan: paid | Requests Done (during the scan): 3 | Requests Remaining: 120 diff --git a/spec/output/vuln_api/http_error.cli_no_colour b/spec/output/vuln_api/http_error.cli_no_colour index 5fd40fa3..b4ec9df7 100644 --- a/spec/output/vuln_api/http_error.cli_no_colour +++ b/spec/output/vuln_api/http_error.cli_no_colour @@ -1 +1 @@ -[!] WPVulnDB API, HTTP Error: url (Timeout was reached) +[!] WPScan DB API, HTTP Error: url (Timeout was reached) diff --git a/spec/output/vuln_api/no_more_requests.cli_no_colour b/spec/output/vuln_api/no_more_requests.cli_no_colour index a0cdc0b9..4ac4cedd 100644 --- a/spec/output/vuln_api/no_more_requests.cli_no_colour +++ b/spec/output/vuln_api/no_more_requests.cli_no_colour @@ -1,4 +1,4 @@ -[+] WPVulnDB API OK +[+] WPScan DB API OK | Plan: free | Requests Done (during the scan): 3 | Requests Remaining: 0 diff --git a/spec/output/vuln_api/no_token.cli_no_colour b/spec/output/vuln_api/no_token.cli_no_colour index db3c8d8f..b43e054a 100644 --- a/spec/output/vuln_api/no_token.cli_no_colour +++ b/spec/output/vuln_api/no_token.cli_no_colour @@ -1,2 +1,2 @@ -[!] No WPVulnDB API Token given, as a result vulnerability data has not been output. +[!] No WPScan API Token given, as a result vulnerability data has not been output. [!] You can get a free API token with 50 daily requests by registering at https://wpscan.com/register diff --git a/spec/output/vuln_api/no_token.json b/spec/output/vuln_api/no_token.json index 50893ddd..ae13fc2e 100644 --- a/spec/output/vuln_api/no_token.json +++ b/spec/output/vuln_api/no_token.json @@ -1,5 +1,5 @@ { "vuln_api": { - "error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpscan.com/register" + "error": "No WPScan API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpscan.com/register" } } \ No newline at end of file diff --git a/spec/output/vuln_api/unlimited_requests.cli_no_colour b/spec/output/vuln_api/unlimited_requests.cli_no_colour index adf71437..7fa8d516 100644 --- a/spec/output/vuln_api/unlimited_requests.cli_no_colour +++ b/spec/output/vuln_api/unlimited_requests.cli_no_colour @@ -1,4 +1,4 @@ -[+] WPVulnDB API OK +[+] WPScan DB API OK | Plan: enterprise | Requests Done (during the scan): 3 | Requests Remaining: Unlimited