Fixes API retry not properly working (cache issue), Fixes #1579, Updates remaining of WpVulnDB

This commit is contained in:
erwanlr
2020-12-16 12:12:45 +01:00
parent 3638241513
commit e42ce414de
12 changed files with 76 additions and 47 deletions

View File

@@ -8,7 +8,10 @@ module WPScan
def cli_options 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 end
@@ -19,7 +22,7 @@ module WPScan
api_status = DB::VulnApi.status 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 Error::ApiLimitReached if api_status['requests_remaining'] == 0
raise api_status['http_error'] if api_status['http_error'] raise api_status['http_error'] if api_status['http_error']
end end

View File

@@ -1,13 +1,13 @@
<% unless @status.empty? -%> <% unless @status.empty? -%>
<% if @status['http_error'] -%> <% if @status['http_error'] -%>
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %> <%= critical_icon %> WPScan DB API, <%= @status['http_error'].to_s %>
<% else -%> <% else -%>
<%= info_icon %> WPVulnDB API OK <%= info_icon %> WPScan DB API OK
| Plan: <%= @status['plan'] %> | Plan: <%= @status['plan'] %>
| Requests Done (during the scan): <%= @api_requests %> | Requests Done (during the scan): <%= @api_requests %>
| Requests Remaining: <%= @status['requests_remaining'] %> | Requests Remaining: <%= @status['requests_remaining'] %>
<% end -%> <% end -%>
<% else -%> <% 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 <%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpscan.com/register
<% end -%> <% end -%>

View File

@@ -8,6 +8,6 @@
"requests_remaining": <%= @status['requests_remaining'].to_json %> "requests_remaining": <%= @status['requests_remaining'].to_json %>
<% end -%> <% end -%>
<% else -%> <% 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 -%> <% end -%>
}, },

View File

@@ -4,7 +4,7 @@ module WPScan
module DB module DB
# WPVulnDB API # WPVulnDB API
class VulnApi class VulnApi
NON_ERROR_CODES = [200, 401].freeze NON_ERROR_CODES = [200, 403].freeze
class << self class << self
attr_accessor :token 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 # 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)) 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) return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
raise Error::HTTP, res raise Error::HTTP, res
@@ -34,6 +34,8 @@ module WPScan
retries ||= 0 retries ||= 0
if (retries += 1) <= 3 if (retries += 1) <= 3
@default_request_params[:headers]['X-Retry'] = retries
sleep(1) sleep(1)
retry retry
end end
@@ -68,7 +70,7 @@ module WPScan
# @return [ Hash ] # @return [ Hash ]
# @note Those params can not be overriden by CLI options # @note Those params can not be overriden by CLI options
def self.default_request_params def self.default_request_params
Browser.instance.default_connect_request_params.merge( @default_request_params ||= Browser.instance.default_connect_request_params.merge(
headers: { headers: {
'User-Agent' => Browser.instance.default_user_agent, 'User-Agent' => Browser.instance.default_user_agent,
'Authorization' => "Token token=#{token}" 'Authorization' => "Token token=#{token}"

View File

@@ -7,6 +7,7 @@ describe WPScan::Controller::VulnApi do
before do before do
WPScan::ParsedCli.options = rspec_parsed_options(cli_args) WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
WPScan::DB::VulnApi.instance_variable_set(:'@default_request_params', nil)
end end
describe '#cli_options' do describe '#cli_options' do
@@ -27,7 +28,7 @@ describe WPScan::Controller::VulnApi do
let(:cli_args) { "#{super()} --api-token token" } let(:cli_args) { "#{super()} --api-token token" }
context 'when the token is invalid' do 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 it 'raise an InvalidApiToken error' do
expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken) expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken)

View File

@@ -10,6 +10,11 @@ describe WPScan::DB::VulnApi do
} }
end end
before do
# Reset the default_request_params
api.instance_variable_set(:'@default_request_params', nil)
end
describe '#uri' do describe '#uri' do
its(:uri) { should be_a Addressable::URI } its(:uri) { should be_a Addressable::URI }
end end
@@ -66,20 +71,16 @@ describe WPScan::DB::VulnApi do
let(:body) { { data: 'something' }.to_json } let(:body) { { data: 'something' }.to_json }
it 'returns the expected hash' do it 'returns the expected hash' do
result = api.get(path) expect(api.get(path)).to eql('data' => 'something')
expect(result).to eql('data' => 'something')
end end
end end
context 'when 401' do context 'when 403' do
let(:code) { 401 } let(:code) { 403 }
let(:body) { { error: 'HTTP Token: Access denied.' }.to_json } let(:body) { { status: 'forbidden' }.to_json }
it 'returns the expected hash' do it 'returns the expected hash' do
result = api.get(path) expect(api.get(path)).to eql('status' => 'forbidden')
expect(result).to eql('error' => 'HTTP Token: Access denied.')
end end
end end
@@ -88,20 +89,16 @@ describe WPScan::DB::VulnApi do
let(:body) { { error: 'Not found' }.to_json } let(:body) { { error: 'Not found' }.to_json }
it 'returns an empty hash' do it 'returns an empty hash' do
result = api.get(path) expect(api.get(path)).to eql({})
expect(result).to eql({})
end end
end
context 'when 404 with HTTML (API inconsistency due to dots in path)' do context 'when 429 (API Limit Reached)' do
let(:path) { 'path.b.c' } let(:code) { 429 }
let(:body) { '<!DOCTYPE html><html>Nop</html>' } let(:body) { { status: 'rate limit hit' }.to_json }
it 'returns an empty hash' do it 'returns an empty hash' do
result = api.get(path) expect(api.get(path)).to eql({})
expect(result).to eql({})
end
end end
end end
end end
@@ -120,6 +117,7 @@ describe WPScan::DB::VulnApi do
result = api.get('path') result = api.get('path')
expect(result['http_error']).to be_a WPScan::Error::HTTP expect(result['http_error']).to be_a WPScan::Error::HTTP
expect(api.default_request_params[:headers]['X-Retry']).to eql 3
end end
end end
@@ -134,9 +132,8 @@ describe WPScan::DB::VulnApi do
it 'tries 1 time and returns expected data' do it 'tries 1 time and returns expected data' do
expect(api).to receive(:sleep).with(1).exactly(1).times expect(api).to receive(:sleep).with(1).exactly(1).times
result = api.get('path') expect(api.get('path')).to eql('data' => 'test')
expect(api.default_request_params[:headers]['X-Retry']).to eql 1
expect(result).to eql('data' => 'test')
end end
end end
end end
@@ -173,6 +170,15 @@ describe WPScan::DB::VulnApi do
expect(api.plugin_data('slug-404')).to eql({}) expect(api.plugin_data('slug-404')).to eql({})
end end
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
end end
@@ -206,6 +212,15 @@ describe WPScan::DB::VulnApi do
expect(api.theme_data('slug-404')).to eql({}) expect(api.theme_data('slug-404')).to eql({})
end end
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
end end
@@ -239,6 +254,15 @@ describe WPScan::DB::VulnApi do
expect(api.wordpress_data('1.1')).to eql({}) expect(api.wordpress_data('1.1')).to eql({})
end end
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
end end
@@ -285,7 +309,7 @@ describe WPScan::DB::VulnApi do
WPScan::Browser.instance.headers = { 'CF-Connecting-IP' => '123.123.123.123' } WPScan::Browser.instance.headers = { 'CF-Connecting-IP' => '123.123.123.123' }
end 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 status = api.status
expect(status['success']).to be true expect(status['success']).to be true
@@ -295,21 +319,20 @@ describe WPScan::DB::VulnApi do
end end
end end
context 'when 401' do # When invalid/empty API token
let(:code) { 401 } context 'when 403' do
let(:return_body) { { error: 'HTTP Token: Access denied.' } } let(:code) { 403 }
let(:return_body) { { status: 'forbidden' } }
it 'returns the expected hash' do it 'returns the expected hash' do
status = api.status expect(api.status['status']).to eql 'forbidden'
expect(status['error']).to eql 'HTTP Token: Access denied.'
end end
end end
context 'otherwise' do context 'otherwise' do
let(:code) { 0 } 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 status = api.status
expect(status['http_error']).to be_a WPScan::Error::HTTP expect(status['http_error']).to be_a WPScan::Error::HTTP

View File

@@ -1,4 +1,4 @@
[+] WPVulnDB API OK [+] WPScan DB API OK
| Plan: paid | Plan: paid
| Requests Done (during the scan): 3 | Requests Done (during the scan): 3
| Requests Remaining: 120 | Requests Remaining: 120

View File

@@ -1 +1 @@
[!] WPVulnDB API, HTTP Error: url (Timeout was reached) [!] WPScan DB API, HTTP Error: url (Timeout was reached)

View File

@@ -1,4 +1,4 @@
[+] WPVulnDB API OK [+] WPScan DB API OK
| Plan: free | Plan: free
| Requests Done (during the scan): 3 | Requests Done (during the scan): 3
| Requests Remaining: 0 | Requests Remaining: 0

View File

@@ -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 [!] You can get a free API token with 50 daily requests by registering at https://wpscan.com/register

View File

@@ -1,5 +1,5 @@
{ {
"vuln_api": { "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"
} }
} }

View File

@@ -1,4 +1,4 @@
[+] WPVulnDB API OK [+] WPScan DB API OK
| Plan: enterprise | Plan: enterprise
| Requests Done (during the scan): 3 | Requests Done (during the scan): 3
| Requests Remaining: Unlimited | Requests Remaining: Unlimited