module Arachni::Element::Capabilities::Mutable

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constants

EXTRA_NAME
FUZZ_NAME
FUZZ_NAME_VALUE
MUTATION_OPTIONS

Default formatting and mutation options.

Attributes

affected_input_name[RW]

@return [String]

Name of the mutated parameter.
format[RW]
seed[RW]

@return [String]

Original seed used for the {#mutations}.

Protected Class Methods

mutable_id( method, inputs, raw_inputs ) click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 335
def self.mutable_id( method, inputs, raw_inputs )
    "#{method}:#{Arachni::Element::Capabilities::Inputtable.inputtable_id( inputs, raw_inputs )}"
end

Public Instance Methods

affected_input_name=( name ) click to toggle source

@param [String] name

Sets the name of the fuzzed input.
# File lib/arachni/element/capabilities/mutable.rb, line 123
def affected_input_name=( name )
    @affected_input_name = name.to_s
end
affected_input_value() click to toggle source

@return [nil, String]

`nil` if no input has been fuzzed, the `String` value of the fuzzed
input.
# File lib/arachni/element/capabilities/mutable.rb, line 110
def affected_input_value
    return if !affected_input_name
    self[affected_input_name].to_s
end
affected_input_value=( value ) click to toggle source

@param [String] value

Sets the value for the fuzzed input.
# File lib/arachni/element/capabilities/mutable.rb, line 117
def affected_input_value=( value )
    self[affected_input_name] = value
end
dup() click to toggle source
Calls superclass method
# File lib/arachni/element/capabilities/mutable.rb, line 321
def dup
    copy_mutable( super )
end
each_mutation( payload, options = {}, &block ) click to toggle source

@note Vector names in {#immutables} will be excluded.

Injects the ‘payload` in self’s values according to formatting options and returns an array of mutations of self.

@param [String] payload

String to inject.

@param [Hash] options

{MUTATION_OPTIONS}

@yield [mutation]

Each generated mutation.

@yieldparam [Mutable]

@see immutables

# File lib/arachni/element/capabilities/mutable.rb, line 167
def each_mutation( payload, options = {}, &block )
    return if self.inputs.empty?

    if !valid_input_data?( payload )
        print_debug_level_2 "Payload not supported by #{self}: #{payload.inspect}"
        return
    end

    print_debug_trainer( options )
    print_debug_formatting( options )

    options          = prepare_mutation_options( options )
    generated        = Support::LookUp::HashSet.new
    filled_in_inputs = Options.input.fill( @inputs )

    if options[:parameter_values]
        @inputs.keys.each do |name|
            # Don't let parameter name pollution from an old audit of an
            # input name trick us into doing the same for elements without
            # that option.
            next if name == EXTRA_NAME
            next if immutables.include?( name )

            each_formatted_payload(
                payload, options[:format], filled_in_inputs[name]
            ) do |format, formatted_payload|

                elem = create_and_yield_if_unique(
                    generated, filled_in_inputs, payload, name,
                    formatted_payload, format, &block
                )

                next if !elem

                if options[:with_raw_payloads]
                    yield_if_unique( elem.with_raw_payload, generated, &block )
                end

                if options[:with_both_http_methods]
                    yield_if_unique( elem.switch_method, generated, &block )
                end
            end
        end
    end

    if options[:with_extra_parameter]
        if valid_input_name?( EXTRA_NAME )
            each_formatted_payload( payload, options[:format] ) do |format, formatted_payload|

                elem = create_and_yield_if_unique(
                    generated, filled_in_inputs.merge( EXTRA_NAME => '' ),
                    payload, EXTRA_NAME, formatted_payload, format, &block
                )

                next if !elem || !options[:with_both_http_methods]
                yield_if_unique( elem.switch_method, generated, &block )
            end
        else
            print_debug_level_2 'Extra name not supported as input name by' <<
                                    " #{audit_id}: #{payload.inspect}"
        end
    end

    if options[:parameter_names]
        if valid_input_name_data?( payload )
            elem                     = self.dup.update( filled_in_inputs )
            elem.affected_input_name = FUZZ_NAME
            elem[payload]            = FUZZ_NAME_VALUE
            elem.seed                = payload

            yield_if_unique( elem, generated, &block )
        else
            print_debug_level_2 'Payload not supported as input name by' <<
                                    " #{audit_id}: #{payload.inspect}"
        end
    end

    nil
end
immutables() click to toggle source

@return [Set]

Names of input vectors to be excluded from {#mutations}.
# File lib/arachni/element/capabilities/mutable.rb, line 141
def immutables
    @immutables ||= Set.new
end
inspect() click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 292
def inspect
    s = "#<#{self.class} (#{http_method}) "

    if !orphan?
        s << "auditor=#{auditor.class} "
    end

    s << "url=#{url.inspect} "
    s << "action=#{action.inspect} "

    s << "default-inputs=#{default_inputs.inspect} "
    s << "inputs=#{inputs.inspect} "
    s << "raw_inputs=#{raw_inputs.inspect} "

    if mutation?
        s << "seed=#{seed.inspect} "
        s << "affected-input-name=#{affected_input_name.inspect} "
        s << "affected-input-value=#{affected_input_value.inspect}"
    end

    s << '>'
end
mutation?() click to toggle source

@return [Bool]

`true` if the element has been mutated, `false` otherwise.
# File lib/arachni/element/capabilities/mutable.rb, line 135
def mutation?
    !!self.affected_input_name
end
mutations( payload, opts = {} ) click to toggle source

Injects the ‘payload` in self’s values according to formatting options and returns an array of mutations of self.

Vector names in {#immutables} will be excluded.

@param [String] payload

The string to inject.

@param [Hash] opts

{MUTATION_OPTIONS}

@return [Array]

@see immutables

# File lib/arachni/element/capabilities/mutable.rb, line 274
def mutations( payload, opts = {} )
    combo = []
    each_mutation( payload, opts ) { |m| combo << m }
    combo
end
parameter_name_audit?() click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 247
def parameter_name_audit?
    affected_input_name == FUZZ_NAME
end
reset() click to toggle source

Resets the inputs to their original format/values.

Calls superclass method
# File lib/arachni/element/capabilities/mutable.rb, line 100
def reset
    super
    @affected_input_name = nil
    @seed                = nil
    self
end
seed=( value ) click to toggle source

@param [String] value

Sets the value for the fuzzed input.
# File lib/arachni/element/capabilities/mutable.rb, line 129
def seed=( value )
    @seed = value.to_s
end
switch_method() click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 257
def switch_method
    self.dup.tap { |c| c.method = (c.method == :get ? :post : :get) }
end
to_h() click to toggle source
Calls superclass method
# File lib/arachni/element/capabilities/mutable.rb, line 280
def to_h
    h = super

    if mutation?
        h[:affected_input_name]  = self.affected_input_name
        h[:affected_input_value] = self.affected_input_value
        h[:seed]                 = self.seed
    end

    h
end
to_rpc_data() click to toggle source
Calls superclass method
# File lib/arachni/element/capabilities/mutable.rb, line 315
def to_rpc_data
    d = super
    d.delete 'immutables'
    d
end
with_raw_payload() click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 251
def with_raw_payload
    self.dup.tap do |c|
        c.raw_inputs << c.affected_input_name
    end
end
with_raw_payload?() click to toggle source

@return [Boolean]

`true` if the mutation's {#affected_input_value} has been set to skip
encoding, `false` otherwise.
# File lib/arachni/element/capabilities/mutable.rb, line 148
def with_raw_payload?
    raw_inputs.include? affected_input_name
end

Protected Instance Methods

mutable_id() click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 327
def mutable_id
    Arachni::Element::Capabilities::Mutable.mutable_id(
        method,
        inputs,
        raw_inputs
    )
end

Private Instance Methods

copy_mutable( other ) click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 367
def copy_mutable( other )
    if self.affected_input_name
        other.affected_input_name = self.affected_input_name.dup
    end

    other.seed       = self.seed.dup if self.seed
    other.format     = self.format

    # Carry over the immutables.
    other.immutables.merge self.immutables
    other
end
create_and_yield_if_unique( list, inputs, seed, input_name, input_value,format, &block ) click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 400
def create_and_yield_if_unique(
    list, inputs, seed, input_name, input_value,format, &block
)
    # We can check if it's unique prior to actually creating, so do it.
    return if list.include?(
        Arachni::Element::Capabilities::Mutable.mutable_id(
            self.method,
            inputs,
            []
        )
    )

    element = create_mutation( inputs, seed, input_name, input_value, format )
    return if !element

    yield_if_unique( element, list, &block )
    element
end
create_mutation( inputs, seed, input_name, input_value, format ) click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 380
def create_mutation( inputs, seed, input_name, input_value, format )
    if !valid_input_value_data?( input_value )
        print_debug_level_2 "Value not supported by #{audit_id}: #{input_value.inspect}"
        return
    end

    if !valid_input_name_data?( input_name )
        print_debug_level_2 "Name not supported by #{audit_id}: #{input_name.inspect}"
        return
    end

    elem                      = self.dup.update( inputs )
    elem.seed                 = seed
    elem.affected_input_name  = input_name
    elem.affected_input_value = input_value
    elem.format               = format

    elem
end
each_formatted_payload( payload, formats, default_value = '' ) { |format, format_str( payload, format, default_value )| ... } click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 428
def each_formatted_payload( payload, formats, default_value = '' )
    formats.each do |format|
        yield format, format_str( payload, format, default_value )
    end
end
format_str( payload, format, default_str = '' ) click to toggle source

Prepares an injection string following the specified formatting options as contained in the format bitfield.

@param [String] payload @param [String] default_str

Default value to be appended by the injection string if {Format::APPEND}
is set in 'format'.

@param [Integer] format

Bitfield describing formatting preferences.

@return [String]

@see Format

# File lib/arachni/element/capabilities/mutable.rb, line 447
def format_str( payload, format, default_str = '' )
    semicolon = null = append = nil

    null      = "\0"               if (format & Format::NULL)      != 0
    semicolon = ';'                if (format & Format::SEMICOLON) != 0
    append    = default_str        if (format & Format::APPEND)    != 0
    semicolon = append = null = '' if (format & Format::STRAIGHT)  != 0

    "#{semicolon}#{append}#{payload}#{null}"
end
prepare_mutation_options( options ) click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 341
def prepare_mutation_options( options )
    options = MUTATION_OPTIONS.merge( options )

    if options[:with_raw_payloads].nil?
        options[:with_raw_payloads] = Options.audit.with_raw_payloads?
    end

    if options[:parameter_values].nil?
        options[:parameter_values] = Options.audit.parameter_values?
    end

    if options[:parameter_names].nil?
        options[:parameter_names] = Options.audit.parameter_names?
    end

    if options[:with_extra_parameter].nil?
        options[:with_extra_parameter] = Options.audit.with_extra_parameter?
    end

    if options[:with_both_http_methods].nil?
        options[:with_both_http_methods] = Options.audit.with_both_http_methods?
    end

    options
end
print_debug_formatting( opts ) click to toggle source
print_debug_mutation( mutation ) click to toggle source
print_debug_trainer( opts ) click to toggle source
yield_if_unique( element, list ) { |element| ... } click to toggle source
# File lib/arachni/element/capabilities/mutable.rb, line 419
def yield_if_unique( element, list )
    return if list.include?( element.mutable_id )

    print_debug_mutation element
    list << element

    yield element
end