Class: Arachni::Element::Form
- Includes:
- Capabilities::Analyzable, Capabilities::Refreshable, Capabilities::WithDOM, Capabilities::WithNode
- Defined in:
- lib/arachni/element/form.rb,
lib/arachni/element/form/dom.rb
Overview
Represents an auditable form element
Defined Under Namespace
Constant Summary collapse
- ORIGINAL_VALUES =
'__original_values__'
- SAMPLE_VALUES =
'__sample_values__'
Constants included from Capabilities::Analyzable::Differential
Capabilities::Analyzable::Differential::DIFFERENTIAL_OPTIONS
Constants included from Capabilities::Analyzable::Taint
Capabilities::Analyzable::Taint::TAINT_OPTIONS
Constants included from Capabilities::Auditable
Capabilities::Auditable::OPTIONS
Constants included from Capabilities::Mutable
Capabilities::Mutable::MUTATION_OPTIONS
Instance Attribute Summary collapse
-
#name ⇒ String?
Name of the form, if it has one.
-
#nonce_name ⇒ String
The name of the input name that holds the nonce.
Attributes included from Capabilities::Auditable
Attributes included from Capabilities::WithAuditor
Attributes included from Capabilities::Mutable
#affected_input_name, #format, #seed
Attributes included from Capabilities::Inputtable
Attributes included from Capabilities::WithNode
Attributes inherited from Base
#initialization_options, #page
Class Method Summary collapse
- .attributes_to_hash(attributes) ⇒ Object
-
.decode(str) ⇒ String
Decodes a String encoded for an HTTP request’s body.
-
.encode(str) ⇒ String
Encodes a String‘s reserved characters in order to prepare it to be included in a request body.
-
.from_document(url, document, ignore_scope = false) ⇒ Array<Form>
Extracts forms from an HTML document.
- .from_node(url, node, ignore_scope = false) ⇒ Object
-
.from_response(response, ignore_scope = false) ⇒ Array<Form>
Extracts forms by parsing the body of an HTTP response.
Instance Method Summary collapse
-
#action=(url) ⇒ Object
@@return (see Capabilities::Submittable#action=).
-
#audit_id(payload = nil) ⇒ String
ID string used to identify the Capabilities::Auditable#audit of ‘self` by its Capabilities::WithAuditor#auditor.
- #audit_status_message ⇒ Object
- #decode(str) ⇒ Object
-
#details_for(input) ⇒ Hash
Information about the given input’s attributes.
- #dom ⇒ DOM
- #dup ⇒ Object
-
#each_mutation(payload, opts = {}) {|elem| ... } ⇒ Object
Overrides Mutable#each_mutation adding support for mutations with:.
- #encode(str) ⇒ Object
-
#field_type_for(name) ⇒ String
Retrieves a field type for the given field ‘name`.
- #force_train? ⇒ Boolean
-
#has_nonce? ⇒ Bool
‘true` if the form contains a nonce, `false` otherwise.
-
#initialize(options) ⇒ Form
constructor
A new instance of Form.
- #mirror_password_fields ⇒ Object
- #mutation_with_original_values ⇒ Object
-
#mutation_with_original_values? ⇒ Bool
‘true` if the element has not been mutated, `false` otherwise.
- #mutation_with_sample_values ⇒ Object
-
#mutation_with_sample_values? ⇒ Bool
‘true` if the element has been populated with sample (OptionGroups::Input.fill) values, `false` otherwise.
-
#name_or_id ⇒ String
Name of ID HTML attributes for this form.
-
#requires_password? ⇒ Bool
Checks whether or not the form contains 1 or more password fields.
-
#simple ⇒ Hash
A simple representation of self including attributes and inputs.
Methods included from Capabilities::Refreshable
Methods included from Capabilities::Analyzable
has_timeout_candidates?, reset, timeout_audit_run
Methods included from Capabilities::Analyzable::Differential
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
Methods included from Capabilities::Analyzable::Taint
Methods included from Capabilities::Auditable
#audit, #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::Mutable
#affected_input_value, #affected_input_value=, #immutables, #mutation?, #mutations, #reset, #switch_method, #to_h
Methods included from Capabilities::Submittable
#action, #http, #id, #method, #method=, #platforms, #submit, #to_h
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, #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, #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?, #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::WithNode
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, type, #type, #url, #url=
Methods included from Capabilities::WithScope
Constructor Details
#initialize(options) ⇒ Form
Returns a new instance of Form.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/arachni/element/form.rb', line 71 def initialize( ) super( ) @name = [:name] @id = [:id] @input_details = {} cinputs = ([: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
#name ⇒ String?
Returns Name of the form, if it has one.
48 49 50 |
# File 'lib/arachni/element/form.rb', line 48 def name @name end |
#nonce_name ⇒ String
Returns The name of the input name that holds the nonce.
44 45 46 |
# File 'lib/arachni/element/form.rb', line 44 def nonce_name @nonce_name end |
Class Method Details
.attributes_to_hash(attributes) ⇒ Object
457 458 459 |
# File 'lib/arachni/element/form.rb', line 457 def attributes_to_hash( attributes ) attributes.inject( {} ){ |h, (k, v)| h[k.to_sym] = v.to_s; h } end |
.decode(str) ⇒ String
Decodes a String encoded for an HTTP request’s body.
479 480 481 |
# File 'lib/arachni/element/form.rb', line 479 def decode( str ) URI.decode( str.to_s.recode.gsub( '+', ' ' ) ) end |
.encode(str) ⇒ String
Encodes a String‘s reserved characters in order to prepare it to be included in a request body.
467 468 469 470 471 472 |
# File 'lib/arachni/element/form.rb', line 467 def encode( str ) ::URI.encode( ::URI.encode( str, '+%' ).recode.gsub( ' ', '+' ), ";&\\=\0" ) end |
.from_document(url, document, ignore_scope = false) ⇒ Array<Form>
Extracts forms from an HTML document.
389 390 391 392 393 394 395 396 397 398 |
# File 'lib/arachni/element/form.rb', line 389 def from_document( url, document, ignore_scope = false ) document = Nokogiri::HTML( document.to_s ) if !document.is_a?( Nokogiri::HTML::Document ) base_url = (document.search( '//base[@href]' )[0]['href'] rescue url) document.search( '//form' ).map do |node| next if !(form = from_node( base_url, node, ignore_scope )) form.url = url.freeze form end.compact end |
.from_node(url, node, ignore_scope = false) ⇒ Object
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
# File 'lib/arachni/element/form.rb', line 400 def from_node( url, node, ignore_scope = false ) = attributes_to_hash( node.attributes ) [:url] = url.freeze [:action] = to_absolute( [:action], url ).freeze [:inputs] = {} [:html] = node.to_html.freeze if (parsed_url = Arachni::URI( [:action] )) return if !ignore_scope && parsed_url.scope.out? end %w(textarea input select button).each do |attr| [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' [:inputs][name] = elem_attrs [:inputs][name][:type] ||= :text [:inputs][name][:value] ||= '' 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 # Prefer the selected or first option. if h[:selected] h[:value] ||= child.text [:inputs][name] = h else h[:value] ||= child.text [:inputs][name] ||= h end end # The select has no options, use an empty string. else [:inputs][name] = { type: :select, value: '' } end end end new end |
Instance Method Details
#action=(url) ⇒ Object
@@return (see Capabilities::Submittable#action=)
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/arachni/element/form.rb', line 109 def action=( url ) if self.method == :get rewritten = uri_parse( url ).rewrite self.inputs = rewritten.query_parameters.merge( self.inputs || {} ) super rewritten.without_query else super url end end |
#audit_id(payload = nil) ⇒ String
Returns ID string used to identify the Capabilities::Auditable#audit of ‘self` by its Capabilities::WithAuditor#auditor.
182 183 184 |
# File 'lib/arachni/element/form.rb', line 182 def audit_id( payload = nil ) force_train? ? id : super( payload ) end |
#audit_status_message ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/arachni/element/form.rb', line 164 def override = nil if mutation_with_original_values? override = 'original' elsif mutation_with_sample_values? override = 'sample' end if override "Submitting form with #{override} values for #{inputs.keys.join(', ')}" << " at '#{@action}'." else super end end |
#decode(str) ⇒ Object
356 357 358 |
# File 'lib/arachni/element/form.rb', line 356 def decode( str ) self.class.decode( str ) end |
#details_for(input) ⇒ Hash
Returns Information about the given input’s attributes.
125 126 127 |
# File 'lib/arachni/element/form.rb', line 125 def details_for( input ) @input_details[input.to_s] || {} end |
#dom ⇒ DOM
97 98 99 100 101 |
# File 'lib/arachni/element/form.rb', line 97 def dom return @dom if @dom return if !node || inputs.empty? super end |
#dup ⇒ Object
360 361 362 363 364 365 366 367 368 369 |
# File 'lib/arachni/element/form.rb', line 360 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 |
#each_mutation(payload, opts = {}) {|elem| ... } ⇒ Object
Overrides Mutable#each_mutation adding support for mutations with:
-
Sample values (filled by OptionGroups::Input.fill).
-
Original values.
-
Password fields requiring identical values (in order to pass server-side validation)
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/arachni/element/form.rb', line 209 def each_mutation( payload, opts = {} ) opts = MUTATION_OPTIONS.merge( opts ) generated = Arachni::Support::LookUp::HashSet.new( hasher: :mutable_id ) super( payload, opts ) do |elem| elem.mirror_password_fields yield elem if !generated.include?( elem ) generated << elem end return if opts[:skip_original] elem = self.dup elem.mutation_with_original_values elem.affected_input_name = ORIGINAL_VALUES yield elem if !generated.include?( elem ) generated << elem # Default values, in case they reveal new resources. if node inputs.keys.each do |input| next if field_type_for( input ) != :select node.xpath( "select[@name=\"#{input}\"]" ).css('option').each do |option| try_input do elem = self.dup elem.mutation_with_original_values elem.affected_input_name = input elem.affected_input_value = option['value'] || option.text yield elem if !generated.include?( elem ) generated << elem end end end end try_input do # Sample values, in case they reveal new resources. elem = self.dup elem.inputs = Arachni::Options.input.fill( inputs.dup ) elem.affected_input_name = SAMPLE_VALUES elem.mutation_with_sample_values yield elem if !generated.include?( elem ) generated << elem end end |
#encode(str) ⇒ Object
348 349 350 |
# File 'lib/arachni/element/form.rb', line 348 def encode( str ) self.class.encode( str ) end |
#field_type_for(name) ⇒ String
Retrieves a field type for the given field ‘name`.
340 341 342 |
# File 'lib/arachni/element/form.rb', line 340 def field_type_for( name ) details_for( name )[:type] || :text end |
#force_train? ⇒ Boolean
103 104 105 |
# File 'lib/arachni/element/form.rb', line 103 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.
286 287 288 |
# File 'lib/arachni/element/form.rb', line 286 def has_nonce? !!nonce_name end |
#mirror_password_fields ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/arachni/element/form.rb', line 258 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 |
#mutation_with_original_values ⇒ Object
147 148 149 |
# File 'lib/arachni/element/form.rb', line 147 def mutation_with_original_values @mutation_with_original_values = true end |
#mutation_with_original_values? ⇒ Bool
Returns ‘true` if the element has not been mutated, `false` otherwise.
143 144 145 |
# File 'lib/arachni/element/form.rb', line 143 def mutation_with_original_values? !!@mutation_with_original_values end |
#mutation_with_sample_values ⇒ Object
160 161 162 |
# File 'lib/arachni/element/form.rb', line 160 def mutation_with_sample_values @mutation_with_sample_values = true end |
#mutation_with_sample_values? ⇒ Bool
Returns ‘true` if the element has been populated with sample (OptionGroups::Input.fill) values, `false` otherwise.
156 157 158 |
# File 'lib/arachni/element/form.rb', line 156 def mutation_with_sample_values? !!@mutation_with_sample_values end |
#name_or_id ⇒ String
Returns Name of ID HTML attributes for this form.
131 132 133 |
# File 'lib/arachni/element/form.rb', line 131 def name_or_id name || @id end |
#requires_password? ⇒ Bool
Checks whether or not the form contains 1 or more password fields.
278 279 280 281 282 |
# File 'lib/arachni/element/form.rb', line 278 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 |
#simple ⇒ Hash
Returns A simple representation of self including attributes and inputs.
137 138 139 |
# File 'lib/arachni/element/form.rb', line 137 def simple @initialization_options.merge( url: url, action: action, inputs: inputs ) end |