# frozen_string_literal: true
require 'json'
require 'uri'
require 'yaml'
module PWN
module WWW
# This plugin supports hackerone.com actions.
module HackerOne
# Supported Method Parameters::
# browser_obj = PWN::WWW::HackerOne.open(
# browser_type: 'optional - :firefox|:chrome|:ie|:headless (Defaults to :firefox)',
# proxy: 'optional - scheme://proxy_host:port || tor'
# )
public_class_method def self.open(opts = {})
browser_obj = PWN::Plugins::TransparentBrowser.open(opts)
browser = browser_obj[:browser]
browser.goto('https://www.hackerone.com')
browser_obj
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# programs_arr = PWN::WWW::HackerOne.get_bounty_programs(
# browser_obj: 'required - browser_obj returned from #open method',
# proxy: 'optional - scheme://proxy_host:port || tor',
# min_payouts_enabled: 'optional - only display programs where payouts are > $0.00 (defaults to false)'
# )
public_class_method def self.get_bounty_programs(opts = {})
browser_obj = opts[:browser_obj]
browser = browser_obj[:browser]
min_payouts_enabled = true if opts[:min_payouts_enabled]
min_payouts_enabled ||= false
browser.goto('https://hackerone.com/bug-bounty-programs')
# Wait for JavaScript to load the DOM
programs_arr = []
browser.ul(class: 'program__meta-data').wait_until(&:present?)
browser.uls(class: 'program__meta-data').each do |ul|
min_payout = ul.text.split('$').last.split.first.to_f
next if min_payouts_enabled && min_payout.zero?
print '.'
link = "https://#{ul.first.text}"
min_payout_fmt = format('$%0.2f', min_payout)
scheme = URI.parse(link).scheme
host = URI.parse(link).host
path = URI.parse(link).path
burp_target_config = "#{scheme}://#{host}/teams#{path}/assets/download_burp_project_file.json"
bounty_program_hash = {
name: link.split('/').last,
min_payout: min_payout_fmt,
policy: "#{link}?view_policy=true",
burp_target_config: burp_target_config,
scope: "#{link}/policy_scopes",
hacktivity: "#{link}/hacktivity",
thanks: "#{link}/thanks",
updates: "#{link}/updates",
collaborators: "#{link}/collaborators"
}
programs_arr.push(bounty_program_hash)
end
programs_arr
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# scope_details = PWN::WWW::HackerOne.get_scope_details(
# program_name: 'required - program name from #get_bounty_programs method',
# proxy: 'optional - scheme://proxy_host:port || tor'
# )
public_class_method def self.get_scope_details(opts = {})
program_name = opts[:program_name]
proxy = opts[:proxy]
browser_obj = PWN::Plugins::TransparentBrowser.open(
browser_type: :rest,
proxy: proxy
)
rest_client = browser_obj[:browser]
rest_request = rest_client::Request
graphql_endpoint = 'https://hackerone.com/graphql'
headers = { content_type: 'application/json' }
# NOTE: If you copy this payload to the pwn REPL
# the triple dots ... attempt to execute commands
# Pry CE
payload = {
operationName: 'PolicySearchStructuredScopesQuery',
variables: {
handle: program_name,
searchString: '',
eligibleForSubmission: nil,
eligibleForBounty: nil,
asmTagIds: [],
from: 0,
size: 100,
sort: {
field: 'cvss_score',
direction: 'DESC'
},
product_area: 'h1_assets',
product_feature: 'policy_scopes'
},
query: 'query PolicySearchStructuredScopesQuery(
$handle: String!,
$searchString: String,
$eligibleForSubmission: Boolean,
$eligibleForBounty: Boolean,
$minSeverityScore: SeverityRatingEnum,
$asmTagIds: [Int],
$from: Int, $size: Int, $sort: SortInput) {
team(handle: $handle) {
id
structured_scopes_search(
search_string: $searchString
eligible_for_submission: $eligibleForSubmission
eligible_for_bounty: $eligibleForBounty
min_severity_score: $minSeverityScore
asm_tag_ids: $asmTagIds
from: $from
size: $size
sort: $sort
) {
nodes {
... on StructuredScopeDocument {
id
...PolicyScopeStructuredScopeDocument
__typename
}
__typename
}
pageInfo {
startCursor
hasPreviousPage
endCursor
hasNextPage
__typename
}
total_count
__typename
}
__typename
}
}
fragment PolicyScopeStructuredScopeDocument on StructuredScopeDocument {
id
identifier
display_name
instruction
cvss_score
eligible_for_bounty
eligible_for_submission
asm_system_tags
created_at
updated_at
attachments {
id
file_name
file_size
content_type
expiring_url
__typename
}
__typename
}
'
}
rest_response = rest_request.execute(
method: :post,
url: graphql_endpoint,
headers: headers,
payload: payload.to_json.delete("\n"),
verify_ssl: false
)
json_resp_hash = JSON.parse(rest_response.body, symbolize_names: true)
json_resp = {
name: program_name,
scope_details: json_resp_hash[:data][:team][:structured_scopes_search]
}
rescue RestClient::ExceptionWithResponse => e
if e.response
puts "HTTP RESPONSE CODE: #{e.response.code}"
puts "HTTP RESPONSE HEADERS:\n#{e.response.headers}"
puts "HTTP RESPONSE BODY:\n#{e.response.body}\n\n\n"
end
raise e
rescue StandardError => e
raise e
ensure
browser_obj = PWN::Plugins::TransparentBrowser.close(browser_obj: browser_obj) if browser_obj
rest_client = nil if rest_client
rest_request = nil if rest_request
end
# Supported Method Parameters::
# PWN::WWW::HackerOne.save_burp_target_config_file(
# programs_arr: 'required - array of hashes returned from #get_bounty_programs method',
# browser_opts: 'optional - opts supported by PWN::Plugins::TransparentBrowser.open method',
# name: 'optional - name of burp target config file (defaults to ALL)',
# root_dir: 'optional - directory to save burp target config files (defaults to "./"))'
# )
public_class_method def self.save_burp_target_config_file(opts = {})
programs_arr = opts[:programs_arr]
raise 'ERROR: programs_arr should be data returned from #get_bounty_programs' unless programs_arr.any?
browser_opts = opts[:browser_opts]
raise 'ERROR: browser_opts should be a hash' unless browser_opts.nil? ||
browser_opts.is_a?(Hash)
browser_opts ||= {}
browser_opts[:browser_type] = :rest
name = opts[:name]
root_dir = opts[:root_dir]
rest_obj = PWN::Plugins::TransparentBrowser.open(browser_opts)
rest_client = rest_obj[:browser]::Request
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
if name
path = "./burp_target_config_file-#{name}.json" if opts[:root_dir].nil?
path = "#{root_dir}/burp_target_config_file-#{name}.json" unless opts[:root_dir].nil?
burp_download_link = programs_arr.select do |program|
program[:name] == name
end.first[:burp_target_config]
resp = rest_client.execute(
method: :get,
headers: { user_agent: user_agent },
url: burp_download_link
)
json_resp = JSON.parse(resp.body)
puts "Saving to: #{path}"
File.write(path, JSON.pretty_generate(json_resp))
else
programs_arr.each do |program|
name = program[:name]
burp_download_link = program[:burp_target_config]
path = "./burp_target_config_file-#{name}.json" if opts[:root_dir].nil?
path = "#{root_dir}/burp_target_config_file-#{name}.json" unless opts[:root_dir].nil?
resp = rest_client.execute(
method: :get,
headers: { user_agent: user_agent },
url: burp_download_link
)
json_resp = JSON.parse(resp.body)
puts "Saving to: #{path}"
File.write(path, JSON.pretty_generate(json_resp))
rescue JSON::ParserError,
RestClient::NotFound
puts '-'
next
end
end
puts 'complete.'
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# browser_obj = PWN::WWW::HackerOne.login(
# browser_obj: 'required - browser_obj returned from #open method',
# username: 'required - username',
# password: 'optional - passwd (will prompt if blank)'
# )
public_class_method def self.login(opts = {})
browser_obj = opts[:browser_obj]
username = opts[:username].to_s.scrub.strip.chomp
password = opts[:password]
browser = browser_obj[:browser]
if password.nil?
password = PWN::Plugins::AuthenticationHelper.mask_password
else
password = opts[:password].to_s.scrub.strip.chomp
end
browser.goto('https://hackerone.com/users/sign_in')
browser.text_field(name: 'user[email]').wait_until(&:present?).set(username)
browser.text_field(name: 'user[password]').wait_until(&:present?).set(password)
browser.button(name: 'commit').click!
browser_obj
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# browser_obj = PWN::WWW::HackerOne.logout(
# browser_obj: 'required - browser_obj returned from #open method'
# )
public_class_method def self.logout(opts = {})
browser_obj = opts[:browser_obj]
browser = browser_obj[:browser]
browser.i(class: 'icon-arrow-closure').click!
browser.link(index: 16).click!
browser_obj
rescue StandardError => e
raise e
end
# Supported Method Parameters::
# browser_obj = PWN::WWW::HackerOne.close(
# browser_obj: 'required - browser_obj returned from #open method'
# )
public_class_method def self.close(opts = {})
browser_obj = opts[:browser_obj]
PWN::Plugins::TransparentBrowser.close(
browser_obj: browser_obj
)
rescue StandardError => e
raise e
end
# Author(s):: 0day Inc.
public_class_method def self.authors
"AUTHOR(S):
0day Inc.
"
end
# Display Usage for this Module
public_class_method def self.help
puts "USAGE:
browser_obj = #{self}.open(
browser_type: 'optional - :firefox|:chrome|:ie|:headless (Defaults to :firefox)',
proxy: 'optional - scheme://proxy_host:port || tor'
)
programs_arr = #{self}.get_bounty_programs(
browser_obj: 'required - browser_obj returned from #open method',
proxy: 'optional - scheme://proxy_host:port || tor',
min_payouts_enabled: 'optional - only display programs where payouts are > $0.00 (defaults to false)'
)
scope_details = PWN::WWW::HackerOne.get_scope_details(
program_name: 'required - program name from #get_bounty_programs method',
proxy: 'optional - scheme://proxy_host:port || tor'
)
#{self}.save_burp_target_config_file(
programs_arr: 'required - array of hashes returned from #get_bounty_programs method',
browser_opts: 'optional - opts supported by PWN::Plugins::TransparentBrowser.open method',
name: 'optional - name of burp target config file (defaults to ALL)',
root_dir: 'optional - directory to save burp target config files (defaults to \"./\"))'
)
browser_obj = #{self}.login(
browser_obj: 'required - browser_obj returned from #open method',
username: 'required - username',
password: 'optional - passwd (will prompt if blank),
)
browser_obj = #{self}.logout(
browser_obj: 'required - browser_obj returned from #open method'
)
#{self}.close(
browser_obj: 'required - browser_obj returned from #open method'
)
#{self}.authors
"
end
end
end
end