lib/pwn/www/hacker_one.rb



# 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::
      # 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. <request.pentest@0dayinc.com>

      public_class_method def self.authors
        "AUTHOR(S):
          0day Inc. <request.pentest@0dayinc.com>
        "
      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'
          )
          browser = browser_obj[:browser]
          puts browser.public_methods

          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)'
          )

          #{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