Class: Aikido::Zen::Scanners::SQLInjectionScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/aikido/zen/scanners/sql_injection_scanner.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query, input, dialect) ⇒ SQLInjectionScanner

Returns a new instance of SQLInjectionScanner.



55
56
57
58
59
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 55

def initialize(query, input, dialect)
  @query = query.downcase
  @input = input.downcase
  @dialect = dialect
end

Instance Attribute Details

#failed_to_tokenizeObject (readonly)

Returns the value of attribute failed_to_tokenize.



53
54
55
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 53

def failed_to_tokenize
  @failed_to_tokenize
end

Class Method Details

.call(query:, dialect:, scan:, sink:, context:, operation:) ⇒ Aikido::Zen::Attack?

Checks if the given SQL query may have dangerous user input injected, and returns an Attack if so, based on the current request.

Parameters:

  • query (String)
  • dialect (Symbol)

    one of :mysql, :postgesql, or :sqlite.

  • scan (Aikido::Zen::Scan)

    the running scan.

  • sink (Aikido::Zen::Sink)

    the Sink that is running the scan.

  • context (Aikido::Zen::Context)
  • operation (Symbol, String)

    name of the method being scanned. Expects sink.operation being set to get the full module/name combo.

Returns:

  • (Aikido::Zen::Attack, nil)

    an Attack if any user input is detected to be attempting a SQL injection, or nil if this is safe.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 26

def self.call(query:, dialect:, scan:, sink:, context:, operation:)
  dialect = Aikido::Zen::SQL::Dialects.fetch(dialect)

  context.payloads.each do |payload|
    scanner = new(query, payload.value.to_s, dialect)
    next unless scanner.attack?

    return Attacks::SQLInjectionAttack.new(
      sink: sink,
      query: query,
      input: payload,
      dialect: dialect,
      context: context,
      operation: "#{sink.operation}.#{operation}",
      stack: Aikido::Zen.clean_stack_trace,
      failed_to_tokenize: scanner.failed_to_tokenize
    )
  rescue Aikido::Zen::InternalsError => error
    Aikido::Zen.config.logger.warn(error.message)
    scan.track_error(error, self)
  rescue => error
    scan.track_error(error, self)
  end

  nil
end

.skips_on_nil_context?Boolean

Returns:

  • (Boolean)


9
10
11
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 9

def self.skips_on_nil_context?
  true
end

Instance Method Details

#attack?Boolean

Returns:

  • (Boolean)


61
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
87
88
89
90
91
92
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 61

def attack?
  # Ignore single char inputs since they shouldn't be able to do much harm
  return false if @input.length <= 1

  # If the input is longer than the query, then it is not part of it
  return false if @input.length > @query.length

  # If the input is not included in the query at all, then we are safe
  return false unless @query.include?(@input)

  # If the input is solely alphanumeric, we can ignore it
  return false if Aikido::Zen::Helpers.regexp_with_timeout(/\A[[:alnum:]_]+\z/i).match?(@input)

  # If the input is a comma-separated list of numbers, ignore it.
  return false if Aikido::Zen::Helpers.regexp_with_timeout(/\A[ ,]*\d[ ,\d]*\z/).match?(@input)

  result = Internals.detect_sql_injection(@query, @input, @dialect)

  case result
  when 0
    false
  when 1
    true
  when 3
    @failed_to_tokenize = true
    Aikido::Zen.config.block_invalid_sql?
  end
rescue => err
  return true if defined?(Regexp::TimeoutError) && err.is_a?(Regexp::TimeoutError)

  raise err
end