pwn 0.5.199 → 0.5.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG_BETWEEN_TAGS.txt +190 -176
- data/Gemfile +4 -4
- data/README.md +3 -3
- data/bin/pwn_sast +2 -0
- data/etc/pwn.yaml.EXAMPLE +3 -0
- data/lib/pwn/plugins/hunter.rb +160 -0
- data/lib/pwn/plugins/repl.rb +3 -0
- data/lib/pwn/plugins/transparent_browser.rb +138 -20
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/sast/local_storage.rb +145 -0
- data/lib/pwn/sast/post_message.rb +144 -0
- data/lib/pwn/sast.rb +2 -0
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn.rb +3 -0
- data/spec/lib/pwn/plugins/hunter_spec.rb +15 -0
- data/spec/lib/pwn/sast/local_storage_spec.rb +25 -0
- data/spec/lib/pwn/sast/post_message_spec.rb +25 -0
- metadata +16 -10
| @@ -0,0 +1,160 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'base64'
         | 
| 4 | 
            +
            require 'json'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module PWN
         | 
| 7 | 
            +
              module Plugins
         | 
| 8 | 
            +
                # This plugin is used for interacting w/ Hunter's REST API using
         | 
| 9 | 
            +
                # the 'rest' browser type of PWN::Plugins::TransparentBrowser.
         | 
| 10 | 
            +
                #  This is based on the following Hunter API Specification:
         | 
| 11 | 
            +
                # https://hunter.how/search-api
         | 
| 12 | 
            +
                module Hunter
         | 
| 13 | 
            +
                  @@logger = PWN::Plugins::PWNLogger.create
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # Supported Method Parameters::
         | 
| 16 | 
            +
                  # hunter_rest_call(
         | 
| 17 | 
            +
                  #   http_method: 'optional HTTP method (defaults to GET)
         | 
| 18 | 
            +
                  #   rest_call: 'required rest call to make per the schema',
         | 
| 19 | 
            +
                  #   params: 'optional params passed in the URI or HTTP Headers',
         | 
| 20 | 
            +
                  #   http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST'
         | 
| 21 | 
            +
                  # )
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  private_class_method def self.hunter_rest_call(opts = {})
         | 
| 24 | 
            +
                    hunter_obj = opts[:hunter_obj]
         | 
| 25 | 
            +
                    http_method = if opts[:http_method].nil?
         | 
| 26 | 
            +
                                    :get
         | 
| 27 | 
            +
                                  else
         | 
| 28 | 
            +
                                    opts[:http_method].to_s.scrub.to_sym
         | 
| 29 | 
            +
                                  end
         | 
| 30 | 
            +
                    rest_call = opts[:rest_call].to_s.scrub
         | 
| 31 | 
            +
                    params = opts[:params]
         | 
| 32 | 
            +
                    http_body = opts[:http_body].to_s.scrub
         | 
| 33 | 
            +
                    base_hunter_api_uri = 'https://api.hunter.how'
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    browser_obj = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
         | 
| 36 | 
            +
                    rest_client = browser_obj[:browser]::Request
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    case http_method
         | 
| 39 | 
            +
                    when :get
         | 
| 40 | 
            +
                      response = rest_client.execute(
         | 
| 41 | 
            +
                        method: :get,
         | 
| 42 | 
            +
                        url: "#{base_hunter_api_uri}/#{rest_call}",
         | 
| 43 | 
            +
                        headers: {
         | 
| 44 | 
            +
                          content_type: 'application/json; charset=UTF-8',
         | 
| 45 | 
            +
                          params: params
         | 
| 46 | 
            +
                        },
         | 
| 47 | 
            +
                        verify_ssl: false
         | 
| 48 | 
            +
                      )
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    when :post
         | 
| 51 | 
            +
                      response = rest_client.execute(
         | 
| 52 | 
            +
                        method: :post,
         | 
| 53 | 
            +
                        url: "#{base_hunter_api_uri}/#{rest_call}",
         | 
| 54 | 
            +
                        headers: {
         | 
| 55 | 
            +
                          content_type: 'application/json; charset=UTF-8',
         | 
| 56 | 
            +
                          params: params
         | 
| 57 | 
            +
                        },
         | 
| 58 | 
            +
                        payload: http_body,
         | 
| 59 | 
            +
                        verify_ssl: false
         | 
| 60 | 
            +
                      )
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    else
         | 
| 63 | 
            +
                      raise @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                    JSON.parse(response.scrub, symbolize_names: true)
         | 
| 66 | 
            +
                  rescue JSON::ParserError => e
         | 
| 67 | 
            +
                    {
         | 
| 68 | 
            +
                      total: 0,
         | 
| 69 | 
            +
                      matches: [],
         | 
| 70 | 
            +
                      error: "JSON::ParserError #{e.message}",
         | 
| 71 | 
            +
                      rest_call: rest_call,
         | 
| 72 | 
            +
                      params: params
         | 
| 73 | 
            +
                    }
         | 
| 74 | 
            +
                  rescue RestClient::TooManyRequests
         | 
| 75 | 
            +
                    print 'Too many requests.  Sleeping 10s...'
         | 
| 76 | 
            +
                    sleep 10
         | 
| 77 | 
            +
                    retry
         | 
| 78 | 
            +
                  rescue StandardError => e
         | 
| 79 | 
            +
                    case e.message
         | 
| 80 | 
            +
                    when '400 Bad Request', '404 Resource Not Found'
         | 
| 81 | 
            +
                      "#{e.message}: #{e.response}"
         | 
| 82 | 
            +
                    else
         | 
| 83 | 
            +
                      raise e
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  # Supported Method Parameters::
         | 
| 88 | 
            +
                  # search_results = PWN::Plugins::Hunter.search(
         | 
| 89 | 
            +
                  #   api_key: 'required hunter api key',
         | 
| 90 | 
            +
                  #   query: 'required - hunter search query',
         | 
| 91 | 
            +
                  #   start_time: 'required - start date for the search (format is yyyy-mm-dd)',
         | 
| 92 | 
            +
                  #   end_time: 'required - end date for the search (format is yyyy-mm-dd)',
         | 
| 93 | 
            +
                  #   start_page: 'optional - starting page number for pagination (default is 1)',
         | 
| 94 | 
            +
                  #   page_size: 'optional - number of results per page (default is 10)',
         | 
| 95 | 
            +
                  #   fields: 'optional - comma-separated list of fields 'product,transport_protocol,protocol,banner,country,province,city,asn,org,web,updated_at' (default is nil)'
         | 
| 96 | 
            +
                  # )
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  public_class_method def self.search(opts = {})
         | 
| 99 | 
            +
                    api_key = opts[:api_key].to_s.scrub
         | 
| 100 | 
            +
                    raise "ERROR: #{self} requires a valid Hunter API Key" if api_key.empty?
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    query = opts[:query].to_s.scrub
         | 
| 103 | 
            +
                    raise "ERROR: #{self} requires a valid query" if query.empty?
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    start_time = opts[:start_time]
         | 
| 106 | 
            +
                    raise "ERROR: #{self} requires a valid start time" if start_time.nil?
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    end_time = opts[:end_time]
         | 
| 109 | 
            +
                    raise "ERROR: #{self} requires a valid end time" if end_time.nil?
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    start_page = opts[:start_page] ||= 1
         | 
| 112 | 
            +
                    page_size = opts[:page_size] ||= 10
         | 
| 113 | 
            +
                    fields = opts[:fields]
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    params = {}
         | 
| 116 | 
            +
                    params[:'api-key'] = api_key
         | 
| 117 | 
            +
                    base64_query = Base64.urlsafe_encode64(query)
         | 
| 118 | 
            +
                    params[:query] = base64_query
         | 
| 119 | 
            +
                    params[:page] = start_page
         | 
| 120 | 
            +
                    params[:page_size] = page_size
         | 
| 121 | 
            +
                    params[:start_time] = start_time
         | 
| 122 | 
            +
                    params[:end_time] = end_time
         | 
| 123 | 
            +
                    params[:fields] = fields
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    hunter_rest_call(
         | 
| 126 | 
            +
                      rest_call: 'search',
         | 
| 127 | 
            +
                      params: params
         | 
| 128 | 
            +
                    )
         | 
| 129 | 
            +
                  rescue StandardError => e
         | 
| 130 | 
            +
                    raise e
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  # Author(s):: 0day Inc. <support@0dayinc.com>
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  public_class_method def self.authors
         | 
| 136 | 
            +
                    "AUTHOR(S):
         | 
| 137 | 
            +
                      0day Inc. <support@0dayinc.com>
         | 
| 138 | 
            +
                    "
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  # Display Usage for this Module
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  public_class_method def self.help
         | 
| 144 | 
            +
                    puts "USAGE:
         | 
| 145 | 
            +
                      search_results = #{self}.query(
         | 
| 146 | 
            +
                        api_key: 'required hunter api key',
         | 
| 147 | 
            +
                        query: 'required - hunter search query',
         | 
| 148 | 
            +
                        start_time: 'required - start date for the search (format is yyyy-mm-dd)',
         | 
| 149 | 
            +
                        end_time: 'required - end date for the search (format is yyyy-mm-dd)',
         | 
| 150 | 
            +
                        start_page: 'optional - starting page number for pagination (default is 1)',
         | 
| 151 | 
            +
                        page_size: 'optional - number of results per page (default is 10)',
         | 
| 152 | 
            +
                        fields: 'optional - comma-separated list of fields 'product,transport_protocol,protocol,banner,country,province,city,asn,org,web,updated_at' (default is nil)'
         | 
| 153 | 
            +
                      )
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                      #{self}.authors
         | 
| 156 | 
            +
                    "
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
            end
         | 
    
        data/lib/pwn/plugins/repl.rb
    CHANGED
    
    | @@ -542,6 +542,9 @@ module PWN | |
| 542 542 | 
             
                        pi.config.pwn_irc = pi.config.p[:irc]
         | 
| 543 543 | 
             
                        Pry.config.pwn_irc = pi.config.pwn_irc
         | 
| 544 544 |  | 
| 545 | 
            +
                        pi.config.pwn_hunter = pi.config.p[:hunter][:api_key]
         | 
| 546 | 
            +
                        Pry.config.pwn_hunter = pi.config.pwn_hunter
         | 
| 547 | 
            +
             | 
| 545 548 | 
             
                        pi.config.pwn_shodan = pi.config.p[:shodan][:api_key]
         | 
| 546 549 | 
             
                        Pry.config.pwn_shodan = pi.config.pwn_shodan
         | 
| 547 550 |  | 
| @@ -110,7 +110,7 @@ module PWN | |
| 110 110 | 
             
                      # DevTools ToolBox Settings in Firefox about:config
         | 
| 111 111 | 
             
                      this_profile['devtools.f12.enabled'] = true
         | 
| 112 112 | 
             
                      this_profile['devtools.toolbox.host'] = 'right'
         | 
| 113 | 
            -
                      this_profile['devtools.toolbox.sidebar.width'] =  | 
| 113 | 
            +
                      this_profile['devtools.toolbox.sidebar.width'] = 1700
         | 
| 114 114 | 
             
                      this_profile['devtools.toolbox.splitconsoleHeight'] = 200
         | 
| 115 115 |  | 
| 116 116 | 
             
                      # DevTools Debugger Settings in Firefox about:config
         | 
| @@ -118,8 +118,7 @@ module PWN | |
| 118 118 | 
             
                      this_profile['devtools.debugger.start-panel-size'] = 200
         | 
| 119 119 | 
             
                      this_profile['devtools.debugger.end-panel-size'] = 200
         | 
| 120 120 | 
             
                      this_profile['devtools.debugger.auto-pretty-print'] = true
         | 
| 121 | 
            -
                       | 
| 122 | 
            -
                      # this_profile['devtools.debugger.ui.editor-wrapping'] = true
         | 
| 121 | 
            +
                      this_profile['devtools.debugger.ui.editor-wrapping'] = true
         | 
| 123 122 | 
             
                      this_profile['devtools.debugger.features.javascript-tracing'] = true
         | 
| 124 123 | 
             
                      this_profile['devtools.debugger.xhr-breakpoints-visible'] = true
         | 
| 125 124 | 
             
                      this_profile['devtools.debugger.expressions-visible'] = true
         | 
| @@ -333,6 +332,14 @@ module PWN | |
| 333 332 | 
             
                          browser_obj[:devtools].send_cmd('DOMSnapshot.enable')
         | 
| 334 333 | 
             
                        end
         | 
| 335 334 |  | 
| 335 | 
            +
                        firefox_browser_types = %i[firefox headless_firefox]
         | 
| 336 | 
            +
                        if firefox_browser_types.include?(browser_type)
         | 
| 337 | 
            +
                          # browser_obj[:devtools].send_cmd(
         | 
| 338 | 
            +
                          #   'EventBreakpoints.setInstrumentationBreakpoint',
         | 
| 339 | 
            +
                          #   eventName: 'script'
         | 
| 340 | 
            +
                          # )
         | 
| 341 | 
            +
                        end
         | 
| 342 | 
            +
             | 
| 336 343 | 
             
                        # Future BiDi API that's more universally supported across browsers
         | 
| 337 344 | 
             
                        # browser_obj[:bidi] = driver.bidi
         | 
| 338 345 |  | 
| @@ -450,16 +457,22 @@ module PWN | |
| 450 457 |  | 
| 451 458 | 
             
                    browser = browser_obj[:browser]
         | 
| 452 459 | 
             
                    case js
         | 
| 453 | 
            -
                    when 'debugger', 'debugger;', 'debugger()', 'debugger();'
         | 
| 454 | 
            -
                      Timeout.timeout(1) { console_resp = browser.execute_script('debugger') }
         | 
| 455 460 | 
             
                    when 'clear', 'clear;', 'clear()', 'clear();'
         | 
| 456 | 
            -
                       | 
| 461 | 
            +
                      script = 'console.clear()'
         | 
| 457 462 | 
             
                    else
         | 
| 458 | 
            -
                       | 
| 463 | 
            +
                      script = "console.log(#{js})"
         | 
| 464 | 
            +
                    end
         | 
| 465 | 
            +
             | 
| 466 | 
            +
                    console_resp = nil
         | 
| 467 | 
            +
                    begin
         | 
| 468 | 
            +
                      Timeout.timeout(1) { console_resp = browser.execute_script(script) }
         | 
| 469 | 
            +
                    rescue Timeout::Error, Timeout::ExitException
         | 
| 470 | 
            +
                      console_resp
         | 
| 471 | 
            +
                    rescue Selenium::WebDriver::Error::JavascriptError
         | 
| 472 | 
            +
                      script = js
         | 
| 473 | 
            +
                      retry
         | 
| 459 474 | 
             
                    end
         | 
| 460 475 |  | 
| 461 | 
            -
                    console_resp
         | 
| 462 | 
            -
                  rescue Timeout::Error, Timeout::ExitException
         | 
| 463 476 | 
             
                    console_resp
         | 
| 464 477 | 
             
                  rescue StandardError => e
         | 
| 465 478 | 
             
                    raise e
         | 
| @@ -637,8 +650,10 @@ module PWN | |
| 637 650 |  | 
| 638 651 | 
             
                    case action.to_s.downcase.to_sym
         | 
| 639 652 | 
             
                    when :pause
         | 
| 640 | 
            -
                       | 
| 641 | 
            -
             | 
| 653 | 
            +
                      devtools.send_cmd(
         | 
| 654 | 
            +
                        'EventBreakpoints.setInstrumentationBreakpoint',
         | 
| 655 | 
            +
                        eventName: 'scriptFirstStatement'
         | 
| 656 | 
            +
                      )
         | 
| 642 657 | 
             
                      # devtools.send_cmd('Debugger.enable')
         | 
| 643 658 | 
             
                      # devtools.send_cmd(
         | 
| 644 659 | 
             
                      #   'Debugger.setInstrumentationBreakpoint',
         | 
| @@ -664,6 +679,10 @@ module PWN | |
| 664 679 | 
             
                        url
         | 
| 665 680 | 
             
                      end
         | 
| 666 681 | 
             
                    when :resume
         | 
| 682 | 
            +
                      devtools.send_cmd(
         | 
| 683 | 
            +
                        'EventBreakpoints.removeInstrumentationBreakpoint',
         | 
| 684 | 
            +
                        eventName: 'scriptFirstStatement'
         | 
| 685 | 
            +
                      )
         | 
| 667 686 | 
             
                      devtools.send_cmd('Debugger.resume')
         | 
| 668 687 | 
             
                    else
         | 
| 669 688 | 
             
                      raise 'ERROR: action parameter must be :pause or :resume'
         | 
| @@ -673,24 +692,69 @@ module PWN | |
| 673 692 | 
             
                  end
         | 
| 674 693 |  | 
| 675 694 | 
             
                  # Supported Method Parameters::
         | 
| 676 | 
            -
                  # PWN::Plugins::TransparentBrowser. | 
| 695 | 
            +
                  # current_dom = PWN::Plugins::TransparentBrowser.dom(
         | 
| 677 696 | 
             
                  #   browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 678 697 | 
             
                  # )
         | 
| 679 698 |  | 
| 699 | 
            +
                  public_class_method def self.dom(opts = {})
         | 
| 700 | 
            +
                    browser_obj = opts[:browser_obj]
         | 
| 701 | 
            +
                    supported = %i[chrome headless_chrome]
         | 
| 702 | 
            +
                    verify_devtools_browser(browser_obj: browser_obj, supported: supported)
         | 
| 703 | 
            +
             | 
| 704 | 
            +
                    devtools = browser_obj[:devtools]
         | 
| 705 | 
            +
                    computed_styles = %i[display color font-size font-family]
         | 
| 706 | 
            +
                    devtools.send_cmd(
         | 
| 707 | 
            +
                      'DOMSnapshot.captureSnapshot',
         | 
| 708 | 
            +
                      computedStyles: computed_styles
         | 
| 709 | 
            +
                    ).transform_keys(&:to_sym)
         | 
| 710 | 
            +
                  rescue StandardError => e
         | 
| 711 | 
            +
                    raise e
         | 
| 712 | 
            +
                  end
         | 
| 713 | 
            +
             | 
| 714 | 
            +
                  # Supported Method Parameters::
         | 
| 715 | 
            +
                  # PWN::Plugins::TransparentBrowser.step_into(
         | 
| 716 | 
            +
                  #   browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 717 | 
            +
                  #   steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 718 | 
            +
                  # )
         | 
| 719 | 
            +
             | 
| 680 720 | 
             
                  public_class_method def self.step_into(opts = {})
         | 
| 681 721 | 
             
                    browser_obj = opts[:browser_obj]
         | 
| 682 722 | 
             
                    supported = %i[chrome headless_chrome]
         | 
| 683 723 | 
             
                    verify_devtools_browser(browser_obj: browser_obj, supported: supported)
         | 
| 684 724 |  | 
| 725 | 
            +
                    steps = opts[:steps].to_i
         | 
| 726 | 
            +
                    steps = 1 if steps.zero? || steps.negative?
         | 
| 727 | 
            +
             | 
| 728 | 
            +
                    diff_arr = []
         | 
| 685 729 | 
             
                    devtools = browser_obj[:devtools]
         | 
| 686 | 
            -
                     | 
| 730 | 
            +
                    steps.times do |s|
         | 
| 731 | 
            +
                      diff_hash = {}
         | 
| 732 | 
            +
                      step = s + 1
         | 
| 733 | 
            +
                      diff_hash[:step] = step
         | 
| 734 | 
            +
             | 
| 735 | 
            +
                      dom_before = dom(browser_obj: browser_obj)
         | 
| 736 | 
            +
                      diff_hash[:dom_before_step] = dom_before
         | 
| 737 | 
            +
             | 
| 738 | 
            +
                      devtools.send_cmd('Debugger.stepInto')
         | 
| 739 | 
            +
             | 
| 740 | 
            +
                      dom_after = dom(browser_obj: browser_obj)
         | 
| 741 | 
            +
                      diff_hash[:dom_after_step] = dom_after
         | 
| 742 | 
            +
             | 
| 743 | 
            +
                      da = dom_before.to_a - dom_after.to_a
         | 
| 744 | 
            +
                      diff_hash[:diff_dom] = da.to_h.transform_keys(&:to_sym)
         | 
| 745 | 
            +
             | 
| 746 | 
            +
                      diff_arr.push(diff_hash)
         | 
| 747 | 
            +
                    end
         | 
| 748 | 
            +
             | 
| 749 | 
            +
                    diff_arr
         | 
| 687 750 | 
             
                  rescue StandardError => e
         | 
| 688 751 | 
             
                    raise e
         | 
| 689 752 | 
             
                  end
         | 
| 690 753 |  | 
| 691 754 | 
             
                  # Supported Method Parameters::
         | 
| 692 755 | 
             
                  # PWN::Plugins::TransparentBrowser.step_out(
         | 
| 693 | 
            -
                  #   browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 756 | 
            +
                  #   browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 757 | 
            +
                  #   steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 694 758 | 
             
                  # )
         | 
| 695 759 |  | 
| 696 760 | 
             
                  public_class_method def self.step_out(opts = {})
         | 
| @@ -698,15 +762,39 @@ module PWN | |
| 698 762 | 
             
                    supported = %i[chrome headless_chrome]
         | 
| 699 763 | 
             
                    verify_devtools_browser(browser_obj: browser_obj, supported: supported)
         | 
| 700 764 |  | 
| 765 | 
            +
                    steps = opts[:steps].to_i
         | 
| 766 | 
            +
                    steps = 1 if steps.zero? || steps.negative?
         | 
| 767 | 
            +
             | 
| 768 | 
            +
                    diff_arr = []
         | 
| 701 769 | 
             
                    devtools = browser_obj[:devtools]
         | 
| 702 | 
            -
                     | 
| 770 | 
            +
                    steps.times do |s|
         | 
| 771 | 
            +
                      diff_hash = {}
         | 
| 772 | 
            +
                      step = s + 1
         | 
| 773 | 
            +
                      diff_hash[:step] = step
         | 
| 774 | 
            +
             | 
| 775 | 
            +
                      dom_before = dom(browser_obj: browser_obj)
         | 
| 776 | 
            +
                      diff_hash[:pre_step] = dom_before
         | 
| 777 | 
            +
             | 
| 778 | 
            +
                      devtools.send_cmd('Debugger.stepOut')
         | 
| 779 | 
            +
             | 
| 780 | 
            +
                      dom_after = dom(browser_obj: browser_obj, step_sum: step_sum)
         | 
| 781 | 
            +
                      diff_hash[:post_step] = dom_after
         | 
| 782 | 
            +
             | 
| 783 | 
            +
                      da = dom_before.to_a - dom_after.to_a
         | 
| 784 | 
            +
                      diff_hash[:diff] = da.to_h.transform_keys(&:to_sym)
         | 
| 785 | 
            +
             | 
| 786 | 
            +
                      diff_arr.push(diff_hash)
         | 
| 787 | 
            +
                    end
         | 
| 788 | 
            +
             | 
| 789 | 
            +
                    diff_arr
         | 
| 703 790 | 
             
                  rescue StandardError => e
         | 
| 704 791 | 
             
                    raise e
         | 
| 705 792 | 
             
                  end
         | 
| 706 793 |  | 
| 707 794 | 
             
                  # Supported Method Parameters::
         | 
| 708 795 | 
             
                  # PWN::Plugins::TransparentBrowser.step_over(
         | 
| 709 | 
            -
                  #   browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 796 | 
            +
                  #   browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 797 | 
            +
                  #   steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 710 798 | 
             
                  # )
         | 
| 711 799 |  | 
| 712 800 | 
             
                  public_class_method def self.step_over(opts = {})
         | 
| @@ -714,8 +802,31 @@ module PWN | |
| 714 802 | 
             
                    supported = %i[chrome headless_chrome]
         | 
| 715 803 | 
             
                    verify_devtools_browser(browser_obj: browser_obj, supported: supported)
         | 
| 716 804 |  | 
| 805 | 
            +
                    steps = opts[:steps].to_i
         | 
| 806 | 
            +
                    steps = 1 if steps.zero? || steps.negative?
         | 
| 807 | 
            +
             | 
| 808 | 
            +
                    diff_arr = []
         | 
| 717 809 | 
             
                    devtools = browser_obj[:devtools]
         | 
| 718 | 
            -
                     | 
| 810 | 
            +
                    steps.times do |s|
         | 
| 811 | 
            +
                      diff_hash = {}
         | 
| 812 | 
            +
                      step = s + 1
         | 
| 813 | 
            +
                      diff_hash[:step] = step
         | 
| 814 | 
            +
             | 
| 815 | 
            +
                      dom_before = dom(browser_obj: browser_obj)
         | 
| 816 | 
            +
                      diff_hash[:dom_before_step] = dom_before
         | 
| 817 | 
            +
             | 
| 818 | 
            +
                      devtools.send_cmd('Debugger.stepOver')
         | 
| 819 | 
            +
             | 
| 820 | 
            +
                      dom_after = dom(browser_obj: browser_obj, step_sum: step_sum)
         | 
| 821 | 
            +
                      diff_hash[:dom_after_step] = dom_after
         | 
| 822 | 
            +
             | 
| 823 | 
            +
                      da = dom_before.to_a - dom_after.to_a
         | 
| 824 | 
            +
                      diff_hash[:diff_dom] = da.to_h.transform_keys(&:to_sym)
         | 
| 825 | 
            +
             | 
| 826 | 
            +
                      diff_arr.push(diff_hash)
         | 
| 827 | 
            +
                    end
         | 
| 828 | 
            +
             | 
| 829 | 
            +
                    diff_arr
         | 
| 719 830 | 
             
                  rescue StandardError => e
         | 
| 720 831 | 
             
                    raise e
         | 
| 721 832 | 
             
                  end
         | 
| @@ -986,16 +1097,23 @@ module PWN | |
| 986 1097 | 
             
                        url: 'optional - URL to navigate to after pausing debugger (Defaults to nil)'
         | 
| 987 1098 | 
             
                      )
         | 
| 988 1099 |  | 
| 989 | 
            -
                      #{self}. | 
| 1100 | 
            +
                      current_dom = #{self}.dom(
         | 
| 990 1101 | 
             
                        browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 991 1102 | 
             
                      )
         | 
| 992 1103 |  | 
| 1104 | 
            +
                      #{self}.step_into(
         | 
| 1105 | 
            +
                        browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 1106 | 
            +
                        steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 1107 | 
            +
                      )
         | 
| 1108 | 
            +
             | 
| 993 1109 | 
             
                      #{self}.step_out(
         | 
| 994 | 
            -
                        browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 1110 | 
            +
                        browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 1111 | 
            +
                        steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 995 1112 | 
             
                      )
         | 
| 996 1113 |  | 
| 997 1114 | 
             
                      #{self}.step_over(
         | 
| 998 | 
            -
                        browser_obj: 'required - browser_obj returned from #open method)'
         | 
| 1115 | 
            +
                        browser_obj: 'required - browser_obj returned from #open method)',
         | 
| 1116 | 
            +
                        steps: 'optional - number of steps taken (Defaults to 1)'
         | 
| 999 1117 | 
             
                      )
         | 
| 1000 1118 |  | 
| 1001 1119 | 
             
                      #{self}.toggle_devtools(
         | 
    
        data/lib/pwn/plugins.rb
    CHANGED
    
    | @@ -30,6 +30,7 @@ module PWN | |
| 30 30 | 
             
                autoload :Github, 'pwn/plugins/github'
         | 
| 31 31 | 
             
                autoload :GQRX, 'pwn/plugins/gqrx'
         | 
| 32 32 | 
             
                autoload :HackerOne, 'pwn/plugins/hacker_one'
         | 
| 33 | 
            +
                autoload :Hunter, 'pwn/plugins/hunter'
         | 
| 33 34 | 
             
                autoload :IPInfo, 'pwn/plugins/ip_info'
         | 
| 34 35 | 
             
                autoload :IRC, 'pwn/plugins/irc'
         | 
| 35 36 | 
             
                autoload :Jenkins, 'pwn/plugins/jenkins'
         | 
| @@ -0,0 +1,145 @@ | |
| 1 | 
            +
            # frozen_string_literal: false
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'socket'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module PWN
         | 
| 6 | 
            +
              module SAST
         | 
| 7 | 
            +
                # SAST Module used to identify any localStorage function/method
         | 
| 8 | 
            +
                # declarations within source code in an effort to
         | 
| 9 | 
            +
                # determine if XSS is possible
         | 
| 10 | 
            +
                module LocalStorage
         | 
| 11 | 
            +
                  @@logger = PWN::Plugins::PWNLogger.create
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # Supported Method Parameters::
         | 
| 14 | 
            +
                  # PWN::SAST::LocalStorage.scan(
         | 
| 15 | 
            +
                  #   dir_path: 'optional path to dir defaults to .'
         | 
| 16 | 
            +
                  #   git_repo_root_uri: 'optional http uri of git repo scanned'
         | 
| 17 | 
            +
                  # )
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  public_class_method def self.scan(opts = {})
         | 
| 20 | 
            +
                    dir_path = opts[:dir_path]
         | 
| 21 | 
            +
                    git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
         | 
| 22 | 
            +
                    result_arr = []
         | 
| 23 | 
            +
                    logger_results = ''
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    PWN::Plugins::FileFu.recurse_dir(dir_path: dir_path) do |entry|
         | 
| 26 | 
            +
                      if File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/ && entry !~ /test/i
         | 
| 27 | 
            +
                        line_no_and_contents_arr = []
         | 
| 28 | 
            +
                        entry_beautified = false
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
         | 
| 31 | 
            +
                          js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED`.to_s.scrub
         | 
| 32 | 
            +
                          entry = "#{entry}.JS-BEAUTIFIED"
         | 
| 33 | 
            +
                          entry_beautified = true
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        test_case_filter = "
         | 
| 37 | 
            +
                          grep -n \
         | 
| 38 | 
            +
                          -e 'localStorage.getItem(' \
         | 
| 39 | 
            +
                          -e 'localStorage.setItem(' #{entry}
         | 
| 40 | 
            +
                        "
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        str = `#{test_case_filter}`.to_s.scrub
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                        if str.to_s.empty?
         | 
| 45 | 
            +
                          # If str length is >= 64 KB do not include results. (Due to Mongo Document Size Restrictions)
         | 
| 46 | 
            +
                          logger_results = "#{logger_results}~" # Catching bugs is good :)
         | 
| 47 | 
            +
                        else
         | 
| 48 | 
            +
                          str = "1:Result larger than 64KB -> Size: #{str.to_s.length}.  Please click the \"Path\" link for more details." if str.to_s.length >= 64_000
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                          hash_line = {
         | 
| 51 | 
            +
                            timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s,
         | 
| 52 | 
            +
                            security_references: security_references,
         | 
| 53 | 
            +
                            filename: { git_repo_root_uri: git_repo_root_uri, entry: entry },
         | 
| 54 | 
            +
                            line_no_and_contents: '',
         | 
| 55 | 
            +
                            raw_content: str,
         | 
| 56 | 
            +
                            test_case_filter: test_case_filter
         | 
| 57 | 
            +
                          }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                          # COMMMENT: Must be a better way to implement this (regex is kinda funky)
         | 
| 60 | 
            +
                          line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1]
         | 
| 61 | 
            +
                          line_no_count = line_contents_split.length # This should always be an even number
         | 
| 62 | 
            +
                          current_count = 0
         | 
| 63 | 
            +
                          while line_no_count > current_count
         | 
| 64 | 
            +
                            line_no = line_contents_split[current_count]
         | 
| 65 | 
            +
                            contents = line_contents_split[current_count + 1]
         | 
| 66 | 
            +
                            if Dir.exist?("#{dir_path}/.git") ||
         | 
| 67 | 
            +
                               Dir.exist?('.git')
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                              repo_root = dir_path
         | 
| 70 | 
            +
                              repo_root = '.' if Dir.exist?('.git')
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                              author = PWN::Plugins::Git.get_author(
         | 
| 73 | 
            +
                                repo_root: repo_root,
         | 
| 74 | 
            +
                                from_line: line_no,
         | 
| 75 | 
            +
                                to_line: line_no,
         | 
| 76 | 
            +
                                target_file: entry,
         | 
| 77 | 
            +
                                entry_beautified: entry_beautified
         | 
| 78 | 
            +
                              )
         | 
| 79 | 
            +
                            else
         | 
| 80 | 
            +
                              author = 'N/A'
         | 
| 81 | 
            +
                            end
         | 
| 82 | 
            +
                            hash_line[:line_no_and_contents] = line_no_and_contents_arr.push(
         | 
| 83 | 
            +
                              line_no: line_no,
         | 
| 84 | 
            +
                              contents: contents,
         | 
| 85 | 
            +
                              author: author
         | 
| 86 | 
            +
                            )
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                            current_count += 2
         | 
| 89 | 
            +
                          end
         | 
| 90 | 
            +
                          result_arr.push(hash_line)
         | 
| 91 | 
            +
                          logger_results = "#{logger_results}x" # Seeing progress is good :)
         | 
| 92 | 
            +
                        end
         | 
| 93 | 
            +
                      end
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                    logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
         | 
| 96 | 
            +
                    if logger_results.empty?
         | 
| 97 | 
            +
                      @@logger.info("#{logger_banner}: No files applicable to this test case.\n")
         | 
| 98 | 
            +
                    else
         | 
| 99 | 
            +
                      @@logger.info("#{logger_banner} => #{logger_results}complete.\n")
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
                    result_arr
         | 
| 102 | 
            +
                  rescue StandardError => e
         | 
| 103 | 
            +
                    raise e
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  # Used primarily to map NIST 800-53 Revision 4 Security Controls
         | 
| 107 | 
            +
                  # https://web.nvd.nist.gov/view/800-53/Rev4/impact?impactName=HIGH
         | 
| 108 | 
            +
                  # to PWN Exploit & Static Code Anti-Pattern Matching Modules to
         | 
| 109 | 
            +
                  # Determine the level of Testing Coverage w/ PWN.
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  public_class_method def self.security_references
         | 
| 112 | 
            +
                    {
         | 
| 113 | 
            +
                      sast_module: self,
         | 
| 114 | 
            +
                      section: 'MALICIOUS CODE PROTECTION',
         | 
| 115 | 
            +
                      nist_800_53_uri: 'https://csrc.nist.gov/Projects/risk-management/sp800-53-controls/release-search#/control/?version=5.1&number=SI-3',
         | 
| 116 | 
            +
                      cwe_id: '79',
         | 
| 117 | 
            +
                      cwe_uri: 'https://cwe.mitre.org/data/definitions/79.html'
         | 
| 118 | 
            +
                    }
         | 
| 119 | 
            +
                  rescue StandardError => e
         | 
| 120 | 
            +
                    raise e
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  # Author(s):: 0day Inc. <support@0dayinc.com>
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  public_class_method def self.authors
         | 
| 126 | 
            +
                    "AUTHOR(S):
         | 
| 127 | 
            +
                      0day Inc. <support@0dayinc.com>
         | 
| 128 | 
            +
                    "
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  # Display Usage for this Module
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  public_class_method def self.help
         | 
| 134 | 
            +
                    puts "USAGE:
         | 
| 135 | 
            +
                      sast_arr = #{self}.scan(
         | 
| 136 | 
            +
                        dir_path: 'optional path to dir defaults to .',
         | 
| 137 | 
            +
                        git_repo_root_uri: 'optional http uri of git repo scanned'
         | 
| 138 | 
            +
                      )
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      #{self}.authors
         | 
| 141 | 
            +
                    "
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
            end
         |