arachni 1.0.2 → 1.0.3
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.md +57 -0
- data/README.md +1 -9
- data/bin/arachni_script +1 -1
- data/components/checks/active/xss_dom_script_context.rb +1 -1
- data/components/checks/active/xss_event.rb +1 -1
- data/components/checks/active/xss_script_context.rb +1 -1
- data/components/plugins/autologin.rb +2 -2
- data/components/plugins/content_types.rb +4 -5
- data/components/plugins/cookie_collector.rb +6 -3
- data/components/plugins/uncommon_headers.rb +6 -2
- data/lib/arachni/browser.rb +26 -2
- data/lib/arachni/browser/element_locator.rb +9 -2
- data/lib/arachni/browser/javascript.rb +6 -0
- data/lib/arachni/browser/javascript/scripts/dom_monitor.js +39 -0
- data/lib/arachni/browser_cluster.rb +11 -25
- data/lib/arachni/element/capabilities/analyzable/differential.rb +4 -0
- data/lib/arachni/element/capabilities/analyzable/timeout.rb +4 -0
- data/lib/arachni/element/capabilities/auditable/dom.rb +1 -0
- data/lib/arachni/element/capabilities/mutable.rb +0 -9
- data/lib/arachni/element/capabilities/with_auditor/output.rb +2 -0
- data/lib/arachni/element/cookie.rb +9 -4
- data/lib/arachni/element/form.rb +6 -6
- data/lib/arachni/element/header.rb +1 -1
- data/lib/arachni/framework.rb +1 -0
- data/lib/arachni/http/client.rb +1 -0
- data/lib/arachni/option_groups.rb +3 -0
- data/lib/arachni/option_groups/paths.rb +63 -6
- data/lib/arachni/option_groups/snapshot.rb +4 -0
- data/lib/arachni/session.rb +73 -17
- data/lib/arachni/state/audit.rb +2 -0
- data/lib/version +1 -1
- data/spec/arachni/browser/javascript_spec.rb +20 -0
- data/spec/arachni/browser_spec.rb +51 -0
- data/spec/arachni/element/cookie_spec.rb +22 -1
- data/spec/arachni/element/form_spec.rb +19 -9
- data/spec/arachni/framework_spec.rb +17 -0
- data/spec/arachni/option_groups/paths_spec.rb +109 -8
- data/spec/arachni/option_groups/snapshot_spec.rb +17 -0
- data/spec/arachni/session_spec.rb +54 -26
- data/spec/components/plugins/autologin_spec.rb +59 -0
- data/spec/spec_helper.rb +1 -3
- data/spec/support/factories/element/body.rb +3 -0
- data/spec/support/factories/element/generic_dom.rb +6 -0
- data/spec/support/factories/element/path.rb +3 -0
- data/spec/support/factories/element/server.rb +3 -0
- data/spec/support/factories/page/dom/transition.rb +21 -0
- data/spec/support/helpers/resets.rb +1 -0
- data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +15 -0
- data/ui/cli/framework.rb +5 -0
- data/ui/cli/framework/option_parser.rb +1 -1
- data/ui/cli/option_parser.rb +3 -0
- data/ui/cli/output.rb +45 -19
- metadata +10 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: fe4bb5082b1faba59faf683145dd95841b0299a2
         | 
| 4 | 
            +
              data.tar.gz: 83374892882e248fb3334b7651c7f166c99c3bc2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: def0427f488fd60f1aed48b0a38d2cb3e3deff96c3270305f27a5a7e2e9a00d01074716b492169ef6e1f45984ac84f89ef39e81b1d155791b73aa560c40f5e33
         | 
| 7 | 
            +
              data.tar.gz: 0edb331de9533d6f5b9a63d3fdcb3f4a7f64697ce6f0f3a98f610a53d41629037d50c01cee7037d28afa9b2489edaa82fd97dee4311c9c4188f4336e78815600
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,62 @@ | |
| 1 1 | 
             
            # ChangeLog
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 1.0.3 _(October 3, 2014)_
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Added overrides for system write directories in `config/write_paths.yml`.
         | 
| 6 | 
            +
            - `OptionGroups`
         | 
| 7 | 
            +
                - `Paths`
         | 
| 8 | 
            +
                    - Added `.config` -- Parsing `config/write_paths.yml`.
         | 
| 9 | 
            +
                    - `#logs` -- Can now be set via `.config`.
         | 
| 10 | 
            +
                    - `#snapshots` -- Can now be set via `.config`.
         | 
| 11 | 
            +
                - `Snapshot`
         | 
| 12 | 
            +
                    - `#save_path` -- Can now be set via `Paths.config`.
         | 
| 13 | 
            +
            - `UI::Output`
         | 
| 14 | 
            +
                - Moved default error log under `OptionGroups::Paths.logs`.
         | 
| 15 | 
            +
                - Optimized file descriptor handling.
         | 
| 16 | 
            +
            - `UI::CLI`
         | 
| 17 | 
            +
                - `OptionParser`
         | 
| 18 | 
            +
                    - Set default report location save-dir from `OptionGroups::Paths.config`.
         | 
| 19 | 
            +
                - `Framework`
         | 
| 20 | 
            +
                    - Print the error-log location at the end of the scan if there were errors.
         | 
| 21 | 
            +
            - `Framework`
         | 
| 22 | 
            +
                - Use `OptionGroups::Scope#extend_paths` to seed the crawl.
         | 
| 23 | 
            +
            - `Browser`
         | 
| 24 | 
            +
                - `ElementLocator.supported_element_attributes_for`
         | 
| 25 | 
            +
                    - Fixed `nil`-error when dealing with unknown attributes.
         | 
| 26 | 
            +
                - Added `:ignore_scope` option, allowing the browser to roam completely
         | 
| 27 | 
            +
                    unrestricted.
         | 
| 28 | 
            +
                - Capped `setTimeout` waiting period to `OptionGroups::HTTP#request_timeout`.
         | 
| 29 | 
            +
                - Fixed issue resulting in multiple cookies with the same name being sent
         | 
| 30 | 
            +
                    to the web application.
         | 
| 31 | 
            +
                - Assigned unique custom IDs to DOM elements without ID attributes.
         | 
| 32 | 
            +
            - `BrowserCluster`
         | 
| 33 | 
            +
                - Spawn browsers in series instead of in parallel to make it easier on
         | 
| 34 | 
            +
                    low resource systems.
         | 
| 35 | 
            +
            - `Session`
         | 
| 36 | 
            +
                - Fallback to `Framework` DOM Level 1 handlers when no `Browser` is available.
         | 
| 37 | 
            +
                - When `OptionGroups::Scope#dom_depth_limit` is 0 don't use the `Browser`.
         | 
| 38 | 
            +
                - Configured its `Browser` with `:ignore_scope` to allow for SSO support.
         | 
| 39 | 
            +
                - `#logged_in?` -- Follow redirections for login check HTTP request.
         | 
| 40 | 
            +
            - `Element`
         | 
| 41 | 
            +
                - `Cookie`
         | 
| 42 | 
            +
                    - Added `#data` -- Providing access to raw cookie data.
         | 
| 43 | 
            +
                - `Capabilities`
         | 
| 44 | 
            +
                    - `Analyzable`
         | 
| 45 | 
            +
                        - `Differential` -- Forcibly disable `OptionGroups::Audit#cookies_extensively`.
         | 
| 46 | 
            +
                        - `Timeout` -- Forcibly disable `OptionGroups::Audit#cookies_extensively`.
         | 
| 47 | 
            +
                    - `Mutable`
         | 
| 48 | 
            +
                        - `#each_mutation` -- Removed obsolete method-switch with default inputs.
         | 
| 49 | 
            +
            - Plugins
         | 
| 50 | 
            +
                - `uncommon_headers`
         | 
| 51 | 
            +
                    - Added `keep-alive` and `content-disposition` in the common list.
         | 
| 52 | 
            +
                    - Ignore out-of-scope responses.
         | 
| 53 | 
            +
                - `content_types`
         | 
| 54 | 
            +
                    - Ignore out-of-scope responses.
         | 
| 55 | 
            +
                - `cookie_collector`
         | 
| 56 | 
            +
                    - `Set-Cookie` header is now always an `Array`.
         | 
| 57 | 
            +
                - `autologin`
         | 
| 58 | 
            +
                    - Don't modify `OptionGroups::Session` (login-check) options if already set.
         | 
| 59 | 
            +
             | 
| 3 60 | 
             
            ## 1.0.2 _(September 13, 2014)_
         | 
| 4 61 |  | 
| 5 62 | 
             
            - `UI::Output` -- Updated null output interface with placeholder debugging methods.
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,17 +1,9 @@ | |
| 1 | 
            -
            **NOTICE**:
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            * Arachni's license has changed, please see the _LICENSE_ file before working
         | 
| 4 | 
            -
                with the project.
         | 
| 5 | 
            -
            * v1.0 is not backwards compatible with v0.4.
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            <hr/>
         | 
| 8 | 
            -
             | 
| 9 1 | 
             
            # Arachni - Web Application Security Scanner Framework
         | 
| 10 2 |  | 
| 11 3 | 
             
            <table>
         | 
| 12 4 | 
             
                <tr>
         | 
| 13 5 | 
             
                    <th>Version</th>
         | 
| 14 | 
            -
                    <td>1.0. | 
| 6 | 
            +
                    <td>1.0.3</td>
         | 
| 15 7 | 
             
                </tr>
         | 
| 16 8 | 
             
                <tr>
         | 
| 17 9 | 
             
                    <th>Homepage</th>
         | 
    
        data/bin/arachni_script
    CHANGED
    
    
| @@ -68,7 +68,7 @@ Injects JS taint code and checks to see if it gets executed as proof of vulnerab | |
| 68 68 | 
             
                        version:     '0.1',
         | 
| 69 69 |  | 
| 70 70 | 
             
                        issue:       {
         | 
| 71 | 
            -
                            name:            %q{DOM-based Cross-Site Scripting (XSS)},
         | 
| 71 | 
            +
                            name:            %q{DOM-based Cross-Site Scripting (XSS) in script context},
         | 
| 72 72 | 
             
                            description:     %q{
         | 
| 73 73 | 
             
            Client-side scripts are used extensively by modern web applications.
         | 
| 74 74 | 
             
            They perform from simple functions (such as the formatting of text) up to full
         | 
| @@ -94,7 +94,7 @@ class Arachni::Checks::XssEvent < Arachni::Check::Base | |
| 94 94 | 
             
                        version:     '0.1.5',
         | 
| 95 95 |  | 
| 96 96 | 
             
                        issue:       {
         | 
| 97 | 
            -
                            name:            %q{Cross-Site Scripting in event tag of HTML element},
         | 
| 97 | 
            +
                            name:            %q{Cross-Site Scripting (XSS) in event tag of HTML element},
         | 
| 98 98 | 
             
                            description:     %q{
         | 
| 99 99 | 
             
            Client-side scripts are used extensively by modern web applications.
         | 
| 100 100 | 
             
            They perform from simple functions (such as the formatting of text) up to full
         | 
| @@ -139,7 +139,7 @@ Injects JS taint code and check to see if it gets executed as proof of vulnerabi | |
| 139 139 | 
             
                        version:     '0.2',
         | 
| 140 140 |  | 
| 141 141 | 
             
                        issue:       {
         | 
| 142 | 
            -
                            name:            %q{Cross-Site Scripting in  | 
| 142 | 
            +
                            name:            %q{Cross-Site Scripting (XSS) in script context},
         | 
| 143 143 | 
             
                            description:     %q{
         | 
| 144 144 | 
             
            Client-side scripts are used extensively by modern web applications.
         | 
| 145 145 | 
             
            They perform from simple functions (such as the formatting of text) up to full
         | 
| @@ -48,8 +48,8 @@ class Arachni::Plugins::AutoLogin < Arachni::Plugin::Base | |
| 48 48 | 
             
                        return
         | 
| 49 49 | 
             
                    end
         | 
| 50 50 |  | 
| 51 | 
            -
                    framework.options.session.check_url      | 
| 52 | 
            -
                    framework.options.session.check_pattern  | 
| 51 | 
            +
                    framework.options.session.check_url     ||= response.url
         | 
| 52 | 
            +
                    framework.options.session.check_pattern ||= @verifier
         | 
| 53 53 |  | 
| 54 54 | 
             
                    if !session.logged_in?
         | 
| 55 55 | 
             
                        register_results(
         | 
| @@ -9,7 +9,6 @@ | |
| 9 9 | 
             
            # Logs content-types of all server responses.
         | 
| 10 10 | 
             
            #
         | 
| 11 11 | 
             
            # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
         | 
| 12 | 
            -
            # @version 0.1.6
         | 
| 13 12 | 
             
            class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base
         | 
| 14 13 |  | 
| 15 14 | 
             
                is_distributable
         | 
| @@ -29,7 +28,7 @@ class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base | |
| 29 28 | 
             
                end
         | 
| 30 29 |  | 
| 31 30 | 
             
                def run
         | 
| 32 | 
            -
                     | 
| 31 | 
            +
                    http.on_complete do |response|
         | 
| 33 32 | 
             
                        next if skip?( response )
         | 
| 34 33 |  | 
| 35 34 | 
             
                        type = response.headers.content_type
         | 
| @@ -47,8 +46,8 @@ class Arachni::Plugins::ContentTypes < Arachni::Plugin::Base | |
| 47 46 | 
             
                end
         | 
| 48 47 |  | 
| 49 48 | 
             
                def skip?( response )
         | 
| 50 | 
            -
                    logged?( response ) || | 
| 51 | 
            -
                        !log?( response )
         | 
| 49 | 
            +
                    response.scope.out? || logged?( response ) ||
         | 
| 50 | 
            +
                        response.headers.content_type.to_s.empty? || !log?( response )
         | 
| 52 51 | 
             
                end
         | 
| 53 52 |  | 
| 54 53 | 
             
                def log?( response )
         | 
| @@ -97,7 +96,7 @@ It can help you categorize and identify publicly available file-types which in | |
| 97 96 | 
             
            turn can help you identify accidentally leaked files.
         | 
| 98 97 | 
             
            },
         | 
| 99 98 | 
             
                        author:      'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
         | 
| 100 | 
            -
                        version:     '0.1. | 
| 99 | 
            +
                        version:     '0.1.7',
         | 
| 101 100 | 
             
                        options:     [
         | 
| 102 101 | 
             
                            Options::String.new( :exclude,
         | 
| 103 102 | 
             
                                description: 'Exclude content-types that match this regular expression.',
         | 
| @@ -9,7 +9,7 @@ | |
| 9 9 | 
             
            # Simple cookie collector
         | 
| 10 10 | 
             
            #
         | 
| 11 11 | 
             
            # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
         | 
| 12 | 
            -
            # @version 0.2
         | 
| 12 | 
            +
            # @version 0.2.1
         | 
| 13 13 | 
             
            class Arachni::Plugins::CookieCollector < Arachni::Plugin::Base
         | 
| 14 14 |  | 
| 15 15 | 
             
                is_distributable
         | 
| @@ -42,9 +42,12 @@ class Arachni::Plugins::CookieCollector < Arachni::Plugin::Base | |
| 42 42 | 
             
                    response_hash.delete( :body )
         | 
| 43 43 | 
             
                    response_hash.delete( :headers_string )
         | 
| 44 44 |  | 
| 45 | 
            +
                    r_h = response_hash.my_stringify_keys
         | 
| 46 | 
            +
                    r_h['headers']['Set-Cookie'] = [r_h['headers']['Set-Cookie']].flatten.compact
         | 
| 47 | 
            +
             | 
| 45 48 | 
             
                    @cookies << {
         | 
| 46 49 | 
             
                        'time'     => Time.now.to_s,
         | 
| 47 | 
            -
                        'response' =>  | 
| 50 | 
            +
                        'response' => r_h,
         | 
| 48 51 | 
             
                        'cookies'  => cookies
         | 
| 49 52 | 
             
                    }
         | 
| 50 53 | 
             
                end
         | 
| @@ -81,7 +84,7 @@ thousands of results leading to a huge report, highly increased memory | |
| 81 84 | 
             
            consumption and CPU usage.
         | 
| 82 85 | 
             
            },
         | 
| 83 86 | 
             
                        author:      'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
         | 
| 84 | 
            -
                        version:     '0.2',
         | 
| 87 | 
            +
                        version:     '0.2.1',
         | 
| 85 88 | 
             
                        options:     [
         | 
| 86 89 | 
             
                            Options::String.new( :filter,
         | 
| 87 90 | 
             
                                description: 'Pattern to use to determine which cookies to ' +
         | 
| @@ -32,7 +32,9 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base | |
| 32 32 | 
             
                     'proxy-authenticate',
         | 
| 33 33 | 
             
                     'set-cookie',
         | 
| 34 34 | 
             
                     'trailer',
         | 
| 35 | 
            -
                     'transfer-encoding'
         | 
| 35 | 
            +
                     'transfer-encoding',
         | 
| 36 | 
            +
                     'keep-alive',
         | 
| 37 | 
            +
                     'content-disposition'
         | 
| 36 38 | 
             
                ])
         | 
| 37 39 |  | 
| 38 40 | 
             
                def prepare
         | 
| @@ -52,6 +54,8 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base | |
| 52 54 |  | 
| 53 55 | 
             
                def run
         | 
| 54 56 | 
             
                    http.on_complete do |response|
         | 
| 57 | 
            +
                        next if response.scope.out?
         | 
| 58 | 
            +
             | 
| 55 59 | 
             
                        headers = response.headers.
         | 
| 56 60 | 
             
                            select { |name, _| !COMMON.include?( name.to_s.downcase ) }
         | 
| 57 61 | 
             
                        next if headers.empty?
         | 
| @@ -85,7 +89,7 @@ class Arachni::Plugins::UncommonHeaders < Arachni::Plugin::Base | |
| 85 89 | 
             
                        name:        'Uncommon headers',
         | 
| 86 90 | 
             
                        description: %q{Intercepts HTTP responses and logs uncommon headers.},
         | 
| 87 91 | 
             
                        author:      'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
         | 
| 88 | 
            -
                        version:     '0.1. | 
| 92 | 
            +
                        version:     '0.1.3'
         | 
| 89 93 | 
             
                    }
         | 
| 90 94 | 
             
                end
         | 
| 91 95 |  | 
    
        data/lib/arachni/browser.rb
    CHANGED
    
    | @@ -126,6 +126,8 @@ class Browser | |
| 126 126 | 
             
                    super()
         | 
| 127 127 | 
             
                    @options = options.dup
         | 
| 128 128 |  | 
| 129 | 
            +
                    @ignore_scope = options[:ignore_scope]
         | 
| 130 | 
            +
             | 
| 129 131 | 
             
                    @width  = options[:width]  || 1600
         | 
| 130 132 | 
             
                    @height = options[:height] || 1200
         | 
| 131 133 |  | 
| @@ -310,6 +312,8 @@ class Browser | |
| 310 312 | 
             
                        wait_for_timers
         | 
| 311 313 |  | 
| 312 314 | 
             
                        wait_for_pending_requests
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                        javascript.set_element_ids
         | 
| 313 317 | 
             
                    end
         | 
| 314 318 |  | 
| 315 319 | 
             
                    if @add_request_transitions
         | 
| @@ -828,7 +832,12 @@ class Browser | |
| 828 832 | 
             
                def wait_for_timers
         | 
| 829 833 | 
             
                    delay = load_delay
         | 
| 830 834 | 
             
                    return if !delay
         | 
| 831 | 
            -
             | 
| 835 | 
            +
             | 
| 836 | 
            +
                    sleep [Options.http.request_timeout, delay].min / 1000.0
         | 
| 837 | 
            +
                end
         | 
| 838 | 
            +
             | 
| 839 | 
            +
                def skip_path?( path )
         | 
| 840 | 
            +
                    enforce_scope? && super( path )
         | 
| 832 841 | 
             
                end
         | 
| 833 842 |  | 
| 834 843 | 
             
                def response
         | 
| @@ -998,6 +1007,13 @@ class Browser | |
| 998 1007 | 
             
                        kill_process
         | 
| 999 1008 | 
             
                    end
         | 
| 1000 1009 |  | 
| 1010 | 
            +
                    # Something went really bad, the browser couldn't be spawned even
         | 
| 1011 | 
            +
                    # after our valiant efforts.
         | 
| 1012 | 
            +
                    #
         | 
| 1013 | 
            +
                    # Bail out for now and count on the BrowserCluster to retry to boot
         | 
| 1014 | 
            +
                    # another process ass needed.
         | 
| 1015 | 
            +
                    return if !@process
         | 
| 1016 | 
            +
             | 
| 1001 1017 | 
             
                    begin
         | 
| 1002 1018 | 
             
                        @pid = @process.pid
         | 
| 1003 1019 | 
             
                    # Not supported on JRuby on MS Windows.
         | 
| @@ -1077,6 +1093,8 @@ class Browser | |
| 1077 1093 |  | 
| 1078 1094 | 
             
                    set_cookies = {}
         | 
| 1079 1095 | 
             
                    HTTP::Client.cookie_jar.for_url( url ).each do |cookie|
         | 
| 1096 | 
            +
                        cookie = cookie.dup
         | 
| 1097 | 
            +
                        cookie.data.delete :domain
         | 
| 1080 1098 | 
             
                        set_cookies[cookie.name] = cookie
         | 
| 1081 1099 | 
             
                    end
         | 
| 1082 1100 | 
             
                    cookies.each do |name, value|
         | 
| @@ -1181,7 +1199,7 @@ class Browser | |
| 1181 1199 | 
             
                        transition.complete
         | 
| 1182 1200 | 
             
                    end
         | 
| 1183 1201 |  | 
| 1184 | 
            -
                    return if response.scope.out?
         | 
| 1202 | 
            +
                    return if enforce_scope? && response.scope.out?
         | 
| 1185 1203 |  | 
| 1186 1204 | 
             
                    intercept response
         | 
| 1187 1205 | 
             
                    save_response response
         | 
| @@ -1203,6 +1221,8 @@ class Browser | |
| 1203 1221 | 
             
                end
         | 
| 1204 1222 |  | 
| 1205 1223 | 
             
                def ignore_request?( request )
         | 
| 1224 | 
            +
                    return if !enforce_scope?
         | 
| 1225 | 
            +
             | 
| 1206 1226 | 
             
                    # Only allow CSS and JS resources to be loaded from out-of-scope domains.
         | 
| 1207 1227 | 
             
                    !['css', 'js'].include?( request.parsed_url.resource_extension ) &&
         | 
| 1208 1228 | 
             
                        request.scope.out? || request.scope.redundant?
         | 
| @@ -1304,6 +1324,10 @@ class Browser | |
| 1304 1324 | 
             
                    end
         | 
| 1305 1325 | 
             
                end
         | 
| 1306 1326 |  | 
| 1327 | 
            +
                def enforce_scope?
         | 
| 1328 | 
            +
                    !@ignore_scope
         | 
| 1329 | 
            +
                end
         | 
| 1330 | 
            +
             | 
| 1307 1331 | 
             
                def normalize_watir_url( url )
         | 
| 1308 1332 | 
             
                    normalize_url( ::URI.encode( url, ';' ) ).gsub( '%3B', '%253B' )
         | 
| 1309 1333 | 
             
                end
         | 
| @@ -127,8 +127,15 @@ class ElementLocator | |
| 127 127 | 
             
                #   List of attributes supported by Watir.
         | 
| 128 128 | 
             
                def self.supported_element_attributes_for( tag_name )
         | 
| 129 129 | 
             
                    @supported_element_attributes_for ||= {}
         | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 130 | 
            +
             | 
| 131 | 
            +
                    tag_name = tag_name.to_sym
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    if (klass = Watir.tag_to_class[tag_name])
         | 
| 134 | 
            +
                        @supported_element_attributes_for[tag_name] ||=
         | 
| 135 | 
            +
                            Set.new( klass.attribute_list )
         | 
| 136 | 
            +
                    else
         | 
| 137 | 
            +
                        @supported_element_attributes_for[tag_name] ||= Set.new
         | 
| 138 | 
            +
                    end
         | 
| 132 139 | 
             
                end
         | 
| 133 140 |  | 
| 134 141 | 
             
            end
         | 
| @@ -228,6 +228,12 @@ class Javascript | |
| 228 228 | 
             
                    taint_tracer.flush_data_flow_sinks
         | 
| 229 229 | 
             
                end
         | 
| 230 230 |  | 
| 231 | 
            +
                # Sets a custom ID attribute to elements with events but without a proper ID.
         | 
| 232 | 
            +
                def set_element_ids
         | 
| 233 | 
            +
                    return '' if !supported?
         | 
| 234 | 
            +
                    dom_monitor.setElementIds
         | 
| 235 | 
            +
                end
         | 
| 236 | 
            +
             | 
| 231 237 | 
             
                # @return   [String]
         | 
| 232 238 | 
             
                #   Digest of the current DOM tree (i.e. node names and their attributes
         | 
| 233 239 | 
             
                #   without text-nodes).
         | 
| @@ -142,5 +142,44 @@ var _tokenDOMMonitor = _tokenDOMMonitor || { | |
| 142 142 | 
             
                registerEvent: function ( element, event, handler ) {
         | 
| 143 143 | 
             
                    if( !('events' in element) ) element['events'] = [];
         | 
| 144 144 | 
             
                    element['events'].push( [event, handler] );
         | 
| 145 | 
            +
                },
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                // Sets a unique enough custom ID attribute to elements that lack proper IDs.
         | 
| 148 | 
            +
                // This gets called externally (by the Browser) once the page is settled.
         | 
| 149 | 
            +
                setElementIds: function() {
         | 
| 150 | 
            +
                    var elements = document.getElementsByTagName("*");
         | 
| 151 | 
            +
                    var length   = elements.length;
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    for( var i = 0; i < length; i++ ) {
         | 
| 154 | 
            +
                        var element = elements[i];
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                        // Window and others don't have attributes.
         | 
| 157 | 
            +
                        if( typeof( element.getAttribute ) !== 'function' ||
         | 
| 158 | 
            +
                            typeof( element.setAttribute) !== 'function' ) continue;
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                        // If the element has an ID we're cool, move on.
         | 
| 161 | 
            +
                        if( element.getAttribute('id') ) continue;
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                        // Skip invisible elements.
         | 
| 164 | 
            +
                        if( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) continue;
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                        // We don't care about elements without events.
         | 
| 167 | 
            +
                        if( !element.events || element.events.length == 0 ) continue;
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                        element.setAttribute( 'data-arachni-id', _tokenDOMMonitor.hashCode( element.innerHTML ) );
         | 
| 170 | 
            +
                    }
         | 
| 171 | 
            +
                },
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                hashCode: function( str ) {
         | 
| 174 | 
            +
                    var hash = 0;
         | 
| 175 | 
            +
                    if( str.length == 0 ) return hash;
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    for( var i = 0; i < str.length; i++ ) {
         | 
| 178 | 
            +
                        var char = str.charCodeAt( i );
         | 
| 179 | 
            +
                        hash = ((hash << 5) - hash) + char;
         | 
| 180 | 
            +
                        hash = hash & hash; // Convert to 32bit integer
         | 
| 181 | 
            +
                    }
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                    return hash;
         | 
| 145 184 | 
             
                }
         | 
| 146 185 | 
             
            };
         | 
| @@ -384,32 +384,18 @@ class BrowserCluster | |
| 384 384 | 
             
                def initialize_workers
         | 
| 385 385 | 
             
                    print_status "Initializing #{pool_size} browsers..."
         | 
| 386 386 |  | 
| 387 | 
            -
                    # Calculate maximum HTTP connection concurrency for each worker based on
         | 
| 388 | 
            -
                    # the HTTP request concurrency setting of the framework.
         | 
| 389 | 
            -
                    #
         | 
| 390 | 
            -
                    # Ideally, we'd throttle the collective connections of all browsers
         | 
| 391 | 
            -
                    # for optimal concurrency, but that would require all browsers sharing
         | 
| 392 | 
            -
                    # the same proxy which would make things **really** dirty and complicated
         | 
| 393 | 
            -
                    # so let's avoid that for as long as possible.
         | 
| 394 | 
            -
                    #concurrency = [(Options.http.request_concurrency / pool_size).to_i, 1].max
         | 
| 395 | 
            -
             | 
| 396 387 | 
             
                    @workers = []
         | 
| 397 | 
            -
                     | 
| 398 | 
            -
             | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 401 | 
            -
                             | 
| 402 | 
            -
             | 
| 403 | 
            -
             | 
| 404 | 
            -
             | 
| 405 | 
            -
             | 
| 406 | 
            -
             | 
| 407 | 
            -
             | 
| 408 | 
            -
                        end
         | 
| 409 | 
            -
                    end
         | 
| 410 | 
            -
             | 
| 411 | 
            -
                    pool_size.times do
         | 
| 412 | 
            -
                        @workers << workers.pop.tap { |b| @consumed_pids << b.pid }
         | 
| 388 | 
            +
                    pool_size.times do |i|
         | 
| 389 | 
            +
                        worker = Worker.new(
         | 
| 390 | 
            +
                            javascript_token: @javascript_token,
         | 
| 391 | 
            +
                            master:           self,
         | 
| 392 | 
            +
                            width:            Options.browser_cluster.screen_width,
         | 
| 393 | 
            +
                            height:           Options.browser_cluster.screen_height
         | 
| 394 | 
            +
                        )
         | 
| 395 | 
            +
                        @workers << worker
         | 
| 396 | 
            +
                        @consumed_pids << worker.pid
         | 
| 397 | 
            +
             | 
| 398 | 
            +
                        print_status "Spawned ##{i+1} with PID #{worker.pid}."
         | 
| 413 399 | 
             
                    end
         | 
| 414 400 | 
             
                    @consumed_pids.compact!
         | 
| 415 401 |  |