diff --git a/Gemfile b/Gemfile index f1c885f..db1aaf7 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ source 'https://rubygems.org' gem 'graphql-client', '~> 0.18.0' gem 'kramdown', '~> 2.4.0' -gem 'nokogiri', '~> 1.16.2' +gem 'nokogiri', '~> 1.16.5' gem 'sentry-raven', '~> 3.1.2' gem 'ssrf_filter', '~> 1.0.8' gem 'twingly-url', '~> 6.0.4' diff --git a/Gemfile.lock b/Gemfile.lock index 650013d..cf02742 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,11 +41,11 @@ GEM rexml method_source (1.0.0) minitest (5.19.0) - nokogiri (1.16.2-aarch64-linux) + nokogiri (1.16.6-aarch64-linux) racc (~> 1.4) - nokogiri (1.16.2-arm64-darwin) + nokogiri (1.16.6-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.2-x86_64-linux) + nokogiri (1.16.6-x86_64-linux) racc (~> 1.4) parallel (1.23.0) parser (3.2.2.1) @@ -57,10 +57,11 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) public_suffix (4.0.7) - racc (1.7.3) + racc (1.8.0) rainbow (3.1.1) regexp_parser (2.8.0) - rexml (3.2.5) + rexml (3.3.1) + strscan rspec (3.11.0) rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) @@ -98,6 +99,7 @@ GEM simplecov-html (~> 0.10.0) simplecov-html (0.10.2) ssrf_filter (1.0.8) + strscan (3.1.0) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) @@ -125,7 +127,7 @@ DEPENDENCIES coveralls (~> 0.8.23) graphql-client (~> 0.18.0) kramdown (~> 2.4.0) - nokogiri (~> 1.16.2) + nokogiri (~> 1.16.5) pry-byebug (~> 3.10.1) rspec (~> 3.11.0) rubocop (~> 1.35.1) diff --git a/lib/bounty-targets/bugcrowd.rb b/lib/bounty-targets/bugcrowd.rb index b711d01..d8fd0a2 100644 --- a/lib/bounty-targets/bugcrowd.rb +++ b/lib/bounty-targets/bugcrowd.rb @@ -46,12 +46,12 @@ def directory_index page += 1 end - program_links.reject do |link| - link.start_with?('https://bugcrowd.com/engagements/') - end + program_links end def parse_program(program_link) + return parse_engagement(program_link) if program_link.start_with?('https://bugcrowd.com/engagements/') + uri = URI(program_link) response = ::SsrfFilter.get(uri).body document = ::Nokogiri::HTML(response) @@ -99,6 +99,38 @@ def parse_program(program_link) } end + def parse_engagement(program_link) + uri = URI(program_link) + response = ::SsrfFilter.get(uri).body + document = ::Nokogiri::HTML(response) + + brief_url = ::JSON.parse(document.css('div[data-react-class="ResearcherEngagementBrief"]') + .attr('data-api-endpoints').value)['engagementBriefApi']['getBriefVersionDocument'] + brief = ::JSON.parse(::SsrfFilter.get(URI("https://bugcrowd.com/#{brief_url}.json")).body) + data = brief['data']['brief'] + brief_scope = brief['data']['scope'] + { + name: data['name'], + url: program_link, + allows_disclosure: !brief['coordinatedDisclosure'], + managed_by_bugcrowd: true, # Bugcrowd seems to have removed the flag for this / all programs are managed + safe_harbor: data.dig('safeHarborStatus', 'status'), + max_payout: brief_scope.select do |scope| + scope['inScope'] == true + end.map do |scope| + scope.dig('rewardRangeData', '1', 'max') + end.max, + targets: { + in_scope: scopes_to_hashes_engagement(brief_scope.select do |scope| + scope['inScope'] == true + end.flatten), + out_of_scope: scopes_to_hashes_engagement(brief_scope.select do |scope| + scope['inScope'] == false + end.flatten) + } + } + end + def scopes_to_hashes(uri, groups) groups.flat_map do |group| targets_uri = uri.clone @@ -126,5 +158,18 @@ def scopes_to_hashes(uri, groups) scope[:target] end end + + def scopes_to_hashes_engagement(scopes) + scopes.flat_map do |targets| + targets['targets'].map do |scope| + { + type: scope['category'], + target: [scope['uri'], scope['name'], scope['ipAddress']].find do |target| + !target.nil? && !target.empty? + end + } + end + end + end end end