class Pentest::SqliChecker

Constants

CRACKER_PAYLOAD
SQLI_PAYLOADS

Public Class Methods

new(endpoint, params) click to toggle source
Calls superclass method Pentest::BaseChecker::new
# File lib/pentest/checkers/sqli_checker.rb, line 18
def initialize(endpoint, params)
  super(endpoint, params)
  @queries = []
  @parser = GDA::SQL::Parser.new
end

Public Instance Methods

attack(param, injection_point, ingredients) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 50
def attack(param, injection_point, ingredients)
  preattack_payloads = generate_preattack_payloads(@params, ingredients, injection_point)

  errors = []

  penetrated_payload = nil
  preattack_payloads.shuffle.each do |payload|
    request, response, err = dispatch(payload)
    status = get_status(err) || response.status

    Pentest::Logger.put_progress (status / 100).to_s

    errors << normalize_error(err, payload)

    if ::ActiveRecord::StatementInvalid === err
      payload.penetration_type = 'SQL Injection Vulnerability'
      payload.penetration_confidence = :preattack
      penetrated_payload = payload
      break
    end
  end

  # preattack not succeeded. skipping
  return [nil, errors] if penetrated_payload.nil?

  # attack
  attack_payloads = generate_attack_payloads(@params, penetrated_payload.values, injection_point)

  Pentest::SqlProxy.enable!(self.method(:handle_query))

  attack_payloads.shuffle.each do |payload|
    request, response, err = dispatch(payload)
    status = get_status(err) || response.status

    Pentest::Logger.put_progress (status / 100).to_s

    errors << normalize_error(err, payload)

    check_attack_result(payload, err);

    if payload.penetration_confidence == :attack
      penetrated_payload = payload
      break
    end
  end

  Pentest::SqlProxy.disable!(self.method(:handle_query))

  [penetrated_payload, errors]
end
check_attack_result(payload, err) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 116
def check_attack_result(payload, err)
  @queries.each do |query, trace|
    begin
      stmt = @parser.parse(query.tr('`?', '"0'))
    rescue RuntimeError => e
      next
    end

    if query.include?(payload.injection) && !(GDA::Nodes::Unknown === stmt.ast)
      tokens = extract_values(stmt.ast)
      if tokens.all? {|token| !token.force_encoding("UTF-8").include? payload.injection}
        callsites = Callsite.parse(trace)
        project_call = callsites.find do |callsite|
          callsite.filename.starts_with?(@app_path)
        end
        unless project_call.nil?
          line = File.read(project_call.filename).lines[project_call.line - 1].rstrip
        end

        payload.penetration_type = 'SQL Injection Vulnerability'
        payload.penetration_confidence = :attack
        payload.penetration_message = [
          *(line.nil? ? [] : [
            "#{project_call.filename}:#{project_call.line}",
            "> #{line}",
          ]),
          "Issued query: #{query.gsub(payload.injection, Term::ANSIColor.red(payload.injection))}",
        ].join("\n")
        break
      end
    end
  end
end
extract_values(stmt) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 150
def extract_values(stmt)
  ret = []

  if stmt.nil?
    # nop
  elsif stmt.is_a?(String)
    ret << stmt
  elsif stmt.is_a?(Integer)
    ret << stmt.to_s
  elsif stmt.is_a?(Array)
    stmt.each do |s|
      ret += extract_values(s)
    end
  elsif GDA::Nodes::Table === stmt
    ret += extract_values(stmt.table_name)
  elsif GDA::Nodes::Field === stmt
    ret += extract_values(stmt.field_name)
  elsif GDA::Nodes::Expr === stmt
    ret += extract_values(stmt.func)
    ret += extract_values(stmt.cond)
    ret += extract_values(stmt.select)
    ret += extract_values(stmt.case_s)
    ret += extract_values(stmt.param_spec)
    ret += extract_values(stmt.cast_as)
    ret << stmt.value
  elsif GDA::Nodes::Select === stmt
    ret += extract_values(stmt.distinct_expr)
    ret += extract_values(stmt.expr_list)
    ret += extract_values(stmt.from)
    ret += extract_values(stmt.where_cond)
    ret += extract_values(stmt.group_by)
    ret += extract_values(stmt.having_cond)
    ret += extract_values(stmt.order_by)
    ret += extract_values(stmt.limit_count)
    ret += extract_values(stmt.limit_offset)
  elsif GDA::Nodes::SelectField === stmt
    ret += extract_values(stmt.expr)
    ret += extract_values(stmt.field_name)
    ret += extract_values(stmt.table_name)
    ret += extract_values(stmt.as)
  elsif GDA::Nodes::From === stmt
    ret += extract_values(stmt.targets)
    ret += extract_values(stmt.joins)
  elsif GDA::Nodes::Operation === stmt
    ret += extract_values(stmt.operands)
  elsif GDA::Nodes::Target === stmt
    ret += extract_values(stmt.expr)
    ret += extract_values(stmt.table_name)
    ret += extract_values(stmt.as)
  elsif GDA::Nodes::Function === stmt
    ret += extract_values(stmt.args_list)
    ret += extract_values(stmt.function_name)
  elsif GDA::Nodes::Order === stmt
    ret += extract_values(stmt.expr)
    ret += extract_values(stmt.collation_name)
  elsif GDA::Nodes::Insert === stmt
    ret += extract_values(stmt.table)
    ret += extract_values(stmt.fields_list)
    ret += extract_values(stmt.expr_list)
    ret += extract_values(stmt.cond)
    ret += extract_values(stmt.conflict)
  elsif GDA::Nodes::Delete === stmt
    ret += extract_values(stmt.table)
    ret += extract_values(stmt.cond)
  elsif GDA::Nodes::Join === stmt
    ret += extract_values(stmt.expr)
    ret += extract_values(stmt.use)
    ret += extract_values(stmt.position)
  elsif GDA::Nodes::Compound === stmt
    ret += extract_values(stmt.compound_type)
  elsif GDA::Nodes::Unknown === stmt
    ret += extract_values(stmt.expressions)
  end

  ret
end
generate_attack_payloads(params, values, injection_point) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 101
def generate_attack_payloads(params, values, injection_point)
  SQLI_PAYLOADS.map do |injection|
    new_values = values.dup
    new_values[injection_point] = injection

    Pentest::Payload.new(
      params: params,
      route: @route,
      values: new_values,
      injection_point: injection_point,
      injection: injection,
    )
  end
end
generate_preattack_payloads(params, seeds, injection_point) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 24
def generate_preattack_payloads(params, seeds, injection_point)
  values_list = if params.size - 1 <= 0
    [[]]
  elsif params.size - 1 == 1
    seeds.map {|s| [s]}
  else
    Pairwise.combinations(*([seeds] * (params.size - 1)))
  end

  values_list.map do |values|
    values.insert(injection_point, CRACKER_PAYLOAD)

    Pentest::Payload.new(
      params: params,
      route: @route,
      values: values,
      injection_point: injection_point,
      injection: CRACKER_PAYLOAD,
    )
  end.take(50)
end
handle_query(sql) click to toggle source
# File lib/pentest/checkers/sqli_checker.rb, line 46
def handle_query(sql)
  @queries << [sql, caller]
end