Class: Arachni::Checks::Emails

Inherits:
Arachni::Check::Base show all
Defined in:
components/checks/passive/grep/emails.rb

Overview

Looks for and logs e-mail addresses.

Author:

Constant Summary collapse

PATTERN =
/[A-Z0-9._%+-]+(?:@|\s*\[at\]\s*)[A-Z0-9.-]+(?:\.|\s*\[dot\]\s*)[A-Z]{2,4}/i
MIN_THREADS =
0
MAX_THREADS =
10

Constants included from Arachni::Check::Auditor

Arachni::Check::Auditor::DOM_ELEMENTS_WITH_INPUTS, Arachni::Check::Auditor::ELEMENTS_WITH_INPUTS, Arachni::Check::Auditor::FILE_SIGNATURES, Arachni::Check::Auditor::FILE_SIGNATURES_PER_PLATFORM, Arachni::Check::Auditor::Format, Arachni::Check::Auditor::SOURCE_CODE_SIGNATURES_PER_PLATFORM

Constants included from Arachni

BANNER, Arachni::Cookie, Form, Header, JSON, Link, LinkTemplate, NestedCookie, Severity, UIForm, UIInput, VERSION, WEBSITE, WIKI, XML

Instance Attribute Summary

Attributes included from Arachni::Check::Auditor

#framework, #page

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Arachni::Check::Base

#browser_cluster, #clean_up, elements, exempt_platforms, has_exempt_platforms?, has_platforms?, #initialize, platforms, #plugins, prefer, #preferred, preferred, #prepare, #session, supports_platforms?

Methods included from Arachni::Check::Auditor

#audit, #audit_differential, #audit_signature, #audit_timeout, #audited, #audited?, #buffered_audit, #each_candidate_dom_element, #each_candidate_element, has_timeout_candidates?, #http, #initialize, #log, #log_issue, #log_remote_file, #log_remote_file_if_exists, #match_and_log, #max_issues, #preferred, reset, #skip?, timeout_audit_run, #trace_taint, #with_browser, #with_browser_cluster

Methods inherited from Arachni::Component::Base

author, description, fullname, #shortname, shortname, shortname=, version

Methods included from Arachni::Component::Output

#depersonalize_output, #depersonalize_output?, #intercept_print_message

Methods included from UI::Output

#caller_location, #debug?, #debug_level, #debug_level_1?, #debug_level_2?, #debug_level_3?, #debug_level_4?, #debug_off, #debug_on, #disable_only_positives, #error_buffer, #error_log_fd, #error_logfile, #has_error_log?, #included, #log_error, #mute, #muted?, #only_positives, #only_positives?, #print_bad, #print_debug, #print_debug_backtrace, #print_debug_exception, #print_debug_level_1, #print_debug_level_2, #print_debug_level_3, #print_debug_level_4, #print_error, #print_error_backtrace, #print_exception, #print_info, #print_line, #print_ok, #print_status, #print_verbose, #reroute_to_file, #reroute_to_file?, reset_output_options, #set_error_logfile, #unmute, #verbose?, #verbose_off, #verbose_on

Methods included from Arachni::Component::Utilities

#read_file

Methods included from Utilities

#available_port, available_port_mutex, #bytes_to_kilobytes, #bytes_to_megabytes, #caller_name, #caller_path, #cookie_decode, #cookie_encode, #cookies_from_file, #cookies_from_parser, #cookies_from_response, #exception_jail, #exclude_path?, #follow_protocol?, #form_decode, #form_encode, #forms_from_parser, #forms_from_response, #full_and_absolute_url?, #generate_token, #get_path, #hms_to_seconds, #html_decode, #html_encode, #include_path?, #links_from_parser, #links_from_response, #normalize_url, #page_from_response, #page_from_url, #parse_set_cookie, #path_in_domain?, #path_too_deep?, #port_available?, #rand_port, #random_seed, #redundant_path?, #regexp_array_match, #remove_constants, #request_parse_body, #seconds_to_hms, #skip_page?, #skip_path?, #skip_resource?, #skip_response?, #to_absolute, #uri_decode, #uri_encode, #uri_parse, #uri_parse_query, #uri_parser, #uri_rewrite

Methods included from Arachni

URI, collect_young_objects, #get_long_win32_filename, jruby?, null_device, profile?, windows?

Constructor Details

This class inherits a constructor from Arachni::Check::Base

Class Method Details

.cacheHash<String, Bool>

Returns Cached results for domain resolutions through the entire scan.

Returns:

  • (Hash<String, Bool>)

    Cached results for domain resolutions through the entire scan.



22
23
24
# File 'components/checks/passive/grep/emails.rb', line 22

def self.cache
    @cache ||= Concurrent::Hash.new
end

.infoObject



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'components/checks/passive/grep/emails.rb', line 150

def self.info
    {
        name:        'E-mail address',
        description: %q{Greps pages for disclosed e-mail addresses.},
        elements:    [ Element::Body ],
        author:      'Tasos "Zapotek" Laskos <[email protected]>',
        version:     '0.3',

        issue:       {
            name:            %q{E-mail address disclosure},
            description:     %q{
Email addresses are typically found on "Contact us" pages, however, they can also
be found within scripts or code comments of the application. They are used to
provide a legitimate means of contacting an organisation.

As one of the initial steps in information gathering, cyber-criminals will spider
a website and using automated methods collect as many email addresses as possible,
that they may then use in a social engineering attack.

Using the same automated methods, Arachni was able to detect one or more email
addresses that were stored within the affected page.
},
            cwe:             200,
            severity:        Severity::INFORMATIONAL,
            remedy_guidance: %q{E-mail addresses should be presented in such
                a way that it is hard to process them automatically.}
        }
    }
end

Instance Method Details

#deobfuscate(email) ⇒ Object



122
123
124
125
126
127
128
# File 'components/checks/passive/grep/emails.rb', line 122

def deobfuscate( email )
    email = email.dup
    email.gsub!( '[at]', '@' )
    email.gsub!( '[dot]', '.' )
    email.gsub!( ' ', '' )
    email
end

#if_exists(email, &block) ⇒ Object

Checks whether or not an e-mail exists by resolving the domain. #resolve will need to be called after all callbacks have been queued.

Parameters:

  • email (String)

    E-mail to check.

  • block (Block)

    Callback to notify, will only be called if the e-mail exists.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'components/checks/passive/grep/emails.rb', line 62

def if_exists( email, &block )
    print_info "Verifying: #{email}"

    domain = deobfuscate( email ).split( '@', 2 ).last

    # Same domain as the current page, gets a pass.
    if page.parsed_url.host == domain
        block.call true
        return
    end

    # In the cache, yay!
    if self.class.cache.include?( domain )
        if self.class.cache[domain]
            print_info "Resolved: #{domain}"
            block.call
            return
        end

        print_info "Could not resolve: #{domain}"
        return
    end

    wait_for( domain, &block )
end

#pool(min_threads = nil) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
# File 'components/checks/passive/grep/emails.rb', line 43

def pool( min_threads = nil )
    @pool ||= Concurrent::ThreadPoolExecutor.new(
        # Only spawn threads when necessary, not from the get go.
        min_threads: min_threads || MIN_THREADS,

        max_threads: MAX_THREADS,

        # No bounds on the amount of domains to be checked.
        max_queue:   0
    )
end

#resolveObject

Process the #if_exists queue.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'components/checks/passive/grep/emails.rb', line 89

def resolve
    return if waiting.empty?

    p = pool( [waiting.size, MAX_THREADS].min )

    waiting.each do |domain, _|
        p.post do
            begin
                Resolv.getaddress domain

                print_info "Resolved: #{domain}"
                self.class.cache[domain] = true
            rescue Resolv::ResolvError
                print_info "Could not resolve: #{domain}"
                self.class.cache[domain] = false
            end
        end
    end

    http.after_run do
        pool.shutdown
        pool.wait_for_termination

        waiting.each do |domain, callbacks|
            next if !self.class.cache[domain]

            while (cb = callbacks.pop)
                cb.call
            end
        end
    end
end

#runObject



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'components/checks/passive/grep/emails.rb', line 130

def run
    body = Element::Body.new( self.page.url ).tap { |b| b.auditor = self }

    page.body.scan( PATTERN ).flatten.uniq.compact.each do |email|
        next if audited?( email )

        if_exists email do
            log(
                signature: PATTERN,
                proof:     email,
                vector:    body
            )

            audited( email )
        end
    end

    resolve
end

#wait_for(domain, &block) ⇒ Object

If there are multiple checks for the same domain queue them, we can perform only one and notify the rest.

Parameters:

  • domain (String)
  • block (Block)

    Callback to notify, will only be called if the domain exists.



38
39
40
41
# File 'components/checks/passive/grep/emails.rb', line 38

def wait_for( domain, &block )
    waiting[domain] ||= []
    waiting[domain] << block
end

#waitingHash<String, Array<Block>] Callers waiting for the results of a resolution per domain.

Returns Hash] Callers waiting for the results of a resolution per domain.

Returns:

  • (Hash<String, Array<Block>] Callers waiting for the results of a resolution per domain.)

    Hash] Callers waiting for the results of a resolution per domain.



28
29
30
# File 'components/checks/passive/grep/emails.rb', line 28

def waiting
    @waiting ||= {}
end