Class: Arachni::Element::Form

Inherits:
Base show all
Includes:
Capabilities::Analyzable, Capabilities::Auditable, Capabilities::Mutable, Capabilities::Refreshable, Capabilities::Submittable, Capabilities::WithDOM
Defined in:
lib/arachni/element/form.rb,
lib/arachni/element/form/dom.rb,
lib/arachni/element/form/capabilities/mutable.rb,
lib/arachni/element/form/capabilities/with_dom.rb,
lib/arachni/element/form/capabilities/auditable.rb,
lib/arachni/element/form/capabilities/submittable.rb

Overview

Represents an auditable form element

Author:

Defined Under Namespace

Modules: Capabilities Classes: DOM, Error

Constant Summary collapse

ORIGINAL_VALUES =
'__original_values__'
SAMPLE_VALUES =
'__sample_values__'

Constants included from Capabilities::Mutable

Capabilities::Mutable::EXTRA_NAME, Capabilities::Mutable::FUZZ_NAME, Capabilities::Mutable::FUZZ_NAME_VALUE, Capabilities::Mutable::MUTATION_OPTIONS

Constants included from Capabilities::Auditable

Capabilities::Auditable::OPTIONS

Constants included from Capabilities::Inputtable

Capabilities::Inputtable::INPUTTABLE_CACHE

Constants included from Capabilities::Analyzable::Differential

Capabilities::Analyzable::Differential::DIFFERENTIAL_OPTIONS

Constants included from Capabilities::Analyzable::Timeout

Capabilities::Analyzable::Timeout::TIMEOUT_OPTIONS

Constants included from Capabilities::Analyzable::Taint

Capabilities::Analyzable::Taint::TAINT_CACHE, Capabilities::Analyzable::Taint::TAINT_OPTIONS

Constants inherited from Base

Base::MAX_SIZE

Instance Attribute Summary collapse

Attributes included from Capabilities::Mutable

#affected_input_name, #format, #seed

Attributes included from Capabilities::Auditable

#audit_options

Attributes included from Capabilities::WithAuditor

#auditor

Attributes included from Capabilities::Inputtable

#default_inputs, #inputs

Attributes included from Capabilities::WithDOM

#dom, #skip_dom

Attributes included from Capabilities::Analyzable::Differential

#differential_analysis_options

Attributes included from Capabilities::Analyzable::Timeout

#timing_attack_remark_data

Attributes inherited from Base

#initialization_options, #page

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Capabilities::Mutable

#affected_input_value, #affected_input_value=, #each_mutation, #immutables, #inspect, #mutation?, #mutations, #reset, #switch_method, #to_h, #to_rpc_data

Methods included from Capabilities::Submittable

#action, #action=, #http, #id, #method, #method=, #platforms, #submit, #to_h

Methods included from Capabilities::Auditable

#audit, #audit_id, #audit_status_message, #audit_status_message_action, #audit_verbose_message, #coverage_hash, #coverage_id, #matches_skip_like_blocks?, #reset, reset, skip_like

Methods included from Capabilities::WithAuditor

#marshal_dump, #orphan?, #prepare_for_report, #remove_auditor

Methods included from Capabilities::Inputtable

#[], #[]=, #changes, #has_inputs?, #inputtable_id, #reset, #to_h, #try_input, #update, #valid_input_data?, #valid_input_name?, #valid_input_name_data?, #valid_input_value?, #valid_input_value_data?

Methods included from Utilities

#available_port, #bytes_to_kilobytes, #bytes_to_megabytes, #caller_name, #caller_path, #cookie_decode, #cookie_encode, #cookies_from_document, #cookies_from_file, #cookies_from_response, #exception_jail, #exclude_path?, #follow_protocol?, #form_decode, #form_encode, #forms_from_document, #forms_from_response, #full_and_absolute_url?, #generate_token, #get_path, #hms_to_seconds, #html_decode, #html_encode, #include_path?, #links_from_document, #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 Capabilities::WithDOM

#skip_dom?

Methods included from Capabilities::Refreshable

#refresh, #refresh_id

Methods included from Capabilities::Analyzable

has_timeout_candidates?, reset, timeout_audit_run

Methods included from Capabilities::Analyzable::Differential

#differential_analysis, reset, #to_rpc_data

Methods included from Capabilities::Analyzable::Timeout

add_phase_2_candidate, candidates_include?, deduplicate, deduplicate?, do_not_deduplicate, #ensure_responsiveness, has_candidates?, payload_delay_from_options, reset, run, #timeout_analysis, timeout_from_options, #timeout_id, #timing_attack_probe, #timing_attack_verify, #to_rpc_data

Methods included from Capabilities::Analyzable::Taint

#taint_analysis

Methods inherited from Base

#==, #action, from_rpc_data, #hash, #id, #marshal_dump, #marshal_load, #persistent_hash, #prepare_for_report, #reset, #to_h, #to_hash, #to_rpc_data, too_big?, type, #type, #url, #url=

Methods included from Capabilities::WithScope

#scope

Constructor Details

#initialize(options) ⇒ Form

Returns a new instance of Form.

Parameters:

Options Hash (options):

  • :name (String)

    Form name.

  • :id (String)

    Form ID.

  • :method (String) — default: :get

    Form method.

  • :url (String)

    URL of the page which includes the form.

  • :action (String)

    Form action – defaults to ‘:url`.

  • :inputs (Hash)

    Form inputs, can either be simple ‘name => value` pairs or more a more detailed representation such as:

    {
        'my_token'  => {
            type:  :hidden,
            value: 'token-value'
        }
    }
    


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/arachni/element/form.rb', line 79

def initialize( options )
    super( options )

    @name = options[:name]
    @id   = options[:id]

    @input_details = {}

    cinputs = (options[:inputs] || {}).inject({}) do |h, (name, value_or_info)|
         if value_or_info.is_a? Hash
             value_or_info             = value_or_info.my_symbolize_keys
             h[name]                   = value_or_info[:value]
             @input_details[name.to_s] = value_or_info
         else
             h[name] = value_or_info
         end
            h
        end

    self.inputs = (method == :get ?
        (self.inputs || {}).merge(cinputs) : cinputs )

    @default_inputs = self.inputs.dup.freeze
end

Instance Attribute Details

#nameString?

Returns Name of the form, if it has one.

Returns:

  • (String, nil)

    Name of the form, if it has one.



56
57
58
# File 'lib/arachni/element/form.rb', line 56

def name
  @name
end

#nonce_nameString

Returns The name of the input name that holds the nonce.

Returns:

  • (String)

    The name of the input name that holds the nonce.



52
53
54
# File 'lib/arachni/element/form.rb', line 52

def nonce_name
  @nonce_name
end

Class Method Details

.attributes_to_hash(attributes) ⇒ Object



386
387
388
# File 'lib/arachni/element/form.rb', line 386

def attributes_to_hash( attributes )
    attributes.inject( {} ){ |h, (k, v)| h[k.to_sym] = v.to_s; h }
end

.decode(string) ⇒ String

Decodes a String encoded for an HTTP request’s body.

Parameters:

Returns:



405
406
407
# File 'lib/arachni/element/form.rb', line 405

def decode( string )
    ::URI.decode_www_form_component string.to_s
end

.encode(string) ⇒ String

Encodes a String‘s reserved characters in order to prepare it to be included in a request body.

Parameters:

Returns:



396
397
398
# File 'lib/arachni/element/form.rb', line 396

def encode( string )
    Arachni::HTTP::Request.encode string
end

.from_document(url, document, ignore_scope = false) ⇒ Array<Form>

Extracts forms from an HTML document.

Parameters:

  • url (String)

    URL of the document – used for path normalization purposes.

  • document (String, Nokogiri::HTML::Document)

Returns:



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/arachni/element/form.rb', line 264

def from_document( url, document, ignore_scope = false )
    if !document.is_a?( Nokogiri::HTML::Document )
        document = document.to_s

        return [] if !(document =~ /<\s*form/i)

        document = Nokogiri::HTML( document )
    end

    base_url = (document.search( '//base[@href]' )[0]['href'] rescue url)
    base_url = to_absolute( base_url, url )

    document.search( '//form' ).map do |node|
        next if !(forms = from_node( base_url, node, ignore_scope ))
        next if forms.empty?

        forms.each do |form|
            form.url = url.freeze
            form
        end
    end.flatten.compact
end

.from_node(url, node, ignore_scope = false) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/arachni/element/form.rb', line 287

def from_node( url, node, ignore_scope = false )
    options          = attributes_to_hash( node.attributes )
    options[:url]    = url.freeze
    options[:action] = to_absolute( options[:action], url ).freeze
    options[:inputs] = {}
    options[:source] = node.to_html.freeze

    if (parsed_url = Arachni::URI( options[:action] ))
        return if !ignore_scope && parsed_url.scope.out?
    end

    # Forms can have many submit inputs with identical names but different
    # values, to act as a sort of multiple choice.
    # However, each Arachni Form can have only unique input names, so
    # we keep track of this here and create a new form for each choice.
    multiple_choice_submits = {}

    %w(textarea input select button).each do |attr|
        options[attr] ||= []
        node.search( ".//#{attr}" ).each do |elem|
            elem_attrs = attributes_to_hash( elem.attributes )
            elem_attrs[:type] = elem_attrs[:type].to_sym if elem_attrs[:type]

            name = elem_attrs[:name] || elem_attrs[:id]
            next if !name

            # Handle the easy stuff first...
            if elem.name != 'select'
                options[:inputs][name] = elem_attrs

                if elem_attrs[:type] == :submit
                    multiple_choice_submits[name] ||= Set.new
                    multiple_choice_submits[name] << elem_attrs[:value]
                end

                options[:inputs][name][:type]  ||= :text
                options[:inputs][name][:value] ||= ''

                if too_big?( options[:inputs][name][:value] )
                    options[:inputs][name][:value] = ''
                end

                next
            end

            # If the select has options figure out which to use.
            if elem.children.css('option').any?
                elem.children.css('option').each do |child|
                    h = attributes_to_hash( child.attributes )
                    h[:type]    = :select
                    h[:value] ||= child.text

                    if too_big?( h[:value] )
                        h[:value] = ''
                    end

                    # Prefer the selected or first option.
                    if h[:selected]
                        options[:inputs][name] = h
                    else
                        options[:inputs][name] ||= h
                    end
                end

            # The select has no options, use an empty string.
            else
                options[:inputs][name] = {
                    type:  :select,
                    value: ''
                }
            end
        end
    end

    return [new( options )] if multiple_choice_submits.empty?

    # If there are multiple submit with the same name but different values,
    # create forms for each value.
    multiple_choice_submits.map do |name, values|
        values.map.with_index do |value, i|

            o = options
            if values.size > 1
                o = options.deep_clone
                o[:inputs][name][:value] = value

                # We need to add this here because if the forms have the
                # same input names only the first one will be audited.
                o[:inputs]["_#{name}_#{i}"] = {
                    type: :fake,
                    value: value
                }
            end

            new( o )
        end
    end.flatten.compact
end

.from_response(response, ignore_scope = false) ⇒ Array<Form>

Extracts forms by parsing the body of an HTTP response.

Parameters:

Returns:



253
254
255
# File 'lib/arachni/element/form.rb', line 253

def from_response( response, ignore_scope = false )
    from_document( response.url, response.body, ignore_scope )
end

Instance Method Details

#decode(str) ⇒ Object

See Also:



231
232
233
# File 'lib/arachni/element/form.rb', line 231

def decode( str )
    self.class.decode( str )
end

#details_for(input) ⇒ Hash

Returns Information about the given input’s attributes.

Parameters:

  • input (String)

    Input name.

Returns:

  • (Hash)

    Information about the given input’s attributes.



113
114
115
# File 'lib/arachni/element/form.rb', line 113

def details_for( input )
    @input_details[input.to_s] || {}
end

#dupObject



235
236
237
238
239
240
241
242
243
244
# File 'lib/arachni/element/form.rb', line 235

def dup
    super.tap do |f|
        f.nonce_name = nonce_name.dup if nonce_name

        f.mutation_with_original_values if mutation_with_original_values?
        f.mutation_with_sample_values   if mutation_with_sample_values?

        f.requires_password = requires_password?
    end
end

#encode(str) ⇒ Object

See Also:



223
224
225
# File 'lib/arachni/element/form.rb', line 223

def encode( str )
    self.class.encode( str )
end

#fake_field?(name) ⇒ Boolean

Returns:

  • (Boolean)


215
216
217
# File 'lib/arachni/element/form.rb', line 215

def fake_field?( name )
    field_type_for( name ) == :fake
end

#field_type_for(name) ⇒ String

Retrieves a field type for the given field ‘name`.

Examples:

html_form = <<-HTML
<form>
    <input type='text' name='text-input' />
    <input type='password' name='passwd' />
    <input type='hidden' name='cant-see-this' />
</form>
HTML

f = Form.from_document( 'http://stuff.com', html_form ).first

p f.field_type_for 'text-input'
#=> :text

p f.field_type_for 'passwd'
#=> :password

p f.field_type_for 'cant-see-this'
#=> :hidden

Parameters:

  • name (String)

    Field name.

Returns:



211
212
213
# File 'lib/arachni/element/form.rb', line 211

def field_type_for( name )
    details_for( name )[:type] || :text
end

#force_train?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/arachni/element/form.rb', line 104

def force_train?
    mutation_with_original_values? || mutation_with_sample_values?
end

#has_nonce?Bool

Returns ‘true` if the form contains a nonce, `false` otherwise.

Returns:

  • (Bool)

    ‘true` if the form contains a nonce, `false` otherwise.



157
158
159
# File 'lib/arachni/element/form.rb', line 157

def has_nonce?
    !!nonce_name
end

#mirror_password_fieldsObject



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/arachni/element/form.rb', line 129

def mirror_password_fields
    return if !requires_password?

    # if there are two password type fields in the form there's a good
    # chance that it's a 'please retype your password' thing so make sure
    # that we have a variation which has identical password values
    password_fields = inputs.keys.
        select { |input| field_type_for( input ) == :password }

    return if password_fields.size != 2

    self[password_fields[0]] = self[password_fields[1]]

    nil
end

#name_or_idString

Returns Name of ID HTML attributes for this form.

Returns:

  • (String)

    Name of ID HTML attributes for this form.



119
120
121
# File 'lib/arachni/element/form.rb', line 119

def name_or_id
    name || @id
end

#requires_password?Bool

Checks whether or not the form contains 1 or more password fields.

Returns:

  • (Bool)

    ‘true` if the form contains passwords fields, `false` otherwise.



149
150
151
152
153
# File 'lib/arachni/element/form.rb', line 149

def requires_password?
    return @requires_password if !@requires_password.nil?
    inputs.each { |k, _| return @requires_password = true if field_type_for( k ) == :password }
    @requires_password = false
end

#simpleHash

Returns A simple representation of self including attributes and inputs.

Returns:

  • (Hash)

    A simple representation of self including attributes and inputs.



125
126
127
# File 'lib/arachni/element/form.rb', line 125

def simple
    @initialization_options.merge( url: url, action: action, inputs: inputs )
end