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

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

Defined Under Namespace

Classes: Dialect

Constant Summary collapse

DIALECTS =

Maps easy-to-use Symbols to a struct that keeps both the name and the internal identifier used by libzen.

{
  common: Dialect.new(name: "SQL", internals_key: 0),
  mysql: Dialect.new(name: "MySQL", internals_key: 8),
  postgresql: Dialect.new(name: "PostgreSQL", internals_key: 9),
  sqlite: Dialect.new(name: "SQLite", internals_key: 12)
}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query, input, dialect) ⇒ SQLInjectionScanner

Returns a new instance of SQLInjectionScanner.



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

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

Class Method Details

.call(query:, dialect:, 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)
  • context (Aikido::Zen::Context)
  • sink (Aikido::Zen::Sink)

    the Sink that is running the scan.

  • dialect (Symbol)

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

  • 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.

Raises:



24
25
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
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 24

def self.call(query:, dialect:, sink:, context:, operation:)
  # FIXME: This assumes queries executed outside of an HTTP request are
  # safe, but this is not the case. For example, if an HTTP request
  # enqueues a background job, passing user input verbatim, the job might
  # pass that input to a query without having a current request in scope.
  return if context.nil?

  dialect = DIALECTS.fetch(dialect) do
    Aikido::Zen.config.logger.warn "Unknown SQL dialect #{dialect.inspect}"
    DIALECTS[:common]
  end

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

    return Attacks::SQLInjectionAttack.new(
      sink: sink,
      query: query,
      input: payload,
      dialect: dialect,
      context: context,
      operation: "#{sink.operation}.#{operation}"
    )
  end

  nil
end

Instance Method Details

#attack?Boolean

Returns:

  • (Boolean)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/aikido/zen/scanners/sql_injection_scanner.rb', line 58

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 /\A[[:alnum:]_]+\z/i.match?(@input)

  # If the input is a comma-separated list of numbers, ignore it.
  return false if /\A(?:\d+(?:,\s*)?)+\z/i.match?(@input)

  Internals.detect_sql_injection(@query, @input, @dialect)
end