require 'msf/core' module Msf ### # # This module exposes methods for querying a remote PostgreSQL service. # ### module Exploit::Remote::Postgres require 'postgres_msf' include Msf::Db::PostgresPR attr_accessor :postgres_conn # # Creates an instance of a MSSQL exploit module. # def initialize(info = {}) super # Register the options that all Postgres exploits may make use of. register_options( [ Opt::RHOST, Opt::RPORT(5432), OptString.new('DATABASE', [ true, 'The database to authenticate against', 'template1']), OptString.new('USERNAME', [ true, 'The username to authenticate as', 'postgres']), OptString.new('PASSWORD', [ true, 'The password for the specified username', '']), OptBool.new('VERBOSE', [false, 'Enable verbose output', false]), OptString.new('SQL', [ false, 'The SQL query to execute', 'select version()']), OptBool.new('RETURN_ROWSET', [false, "Set to true to see query result sets", true]) ], Msf::Exploit::Remote::Postgres) register_autofilter_ports([ 5432 ]) register_autofilter_services(%W{ postgresql }) end # postgres_login takes a number of arguments (defaults to the datastore for # appropriate values), and will either populate self.postgres_conn and return # :connected, or will return :error, :error_databse, or :error_credentials # Fun fact: if you get :error_database, it means your username and password # was accepted (you just failed to guess a correct running database instance). # Note that postgres_login will first trigger postgres_logout if the module # is already connected. def postgres_login(args={}) postgres_logout if self.postgres_conn db = args[:database] || datastore['DATABASE'] username = args[:username] || datastore['USERNAME'] password = args[:password] || datastore['PASSWORD'] ip = args[:server] || datastore['RHOST'] port = args[:port] || datastore['RPORT'] uri = "tcp://#{ip}:#{port}" verbose = args[:verbose] || datastore['VERBOSE'] begin self.postgres_conn = Connection.new(db,username,password,uri) rescue RuntimeError => e case e.to_s.split("\t")[1] when "C3D000" print_error "#{ip}:#{port} Postgres - Bad database name: #{db} (Credentials '#{username}:#{password}' is OK)" if verbose return :error_database # Note this means the user:pass is good! when "C28000" print_error "#{ip}:#{port} Postgres - Bad username or password: '#{username}:#{password}'" if verbose return :error_credentials else print_error "#{ip}:#{port} Postgres - Error: #{e.inspect}" if verbose return :error end end if self.postgres_conn print_good "#{ip}:#{port} Postgres - Logged in to '#{db}' with '#{username}':'#{password}'" if verbose return :connected end end # Logs out of a database instance. def postgres_logout ip = datastore['RHOST'] port = datastore['RPORT'] if self.postgres_conn self.postgres_conn.close if(self.postgres_conn.kind_of?(Connection) && self.postgres_conn.instance_variable_get("@conn")) self.postgres_conn = nil end print_status "#{ip}:#{port} Postgres - Disconnected" if datastore['VERBOSE'] end # If not currently connected, postgres_query will attempt to connect. If an # error is encountered while executing the query, it will return with # :error ; otherwise, it will return with :complete. def postgres_query(sql=nil,doprint=false) ip = datastore['RHOST'] port = datastore['RPORT'] postgres_login unless self.postgres_conn unless self.postgres_conn print_error "#{ip}:#{port} Postgres - Could not connect! #{datastore['VERBOSE'] ? nil : "(Set VERBOSE to see login errors)"}" return :error end if self.postgres_conn sql ||= datastore['SQL'] print_status "#{ip}:#{port} Postgres - querying with '#{sql}'" if datastore['VERBOSE'] begin resp = self.postgres_conn.query(sql) rescue RuntimeError => e case e.to_s.split("\t")[1] # Deal with some common errors when "C42601" print_error "#{ip}:#{port} Postgres - Error: Bad SQL Syntax: '#{sql}'" when "C42P01" print_error "#{ip}:#{port} Postgres - Error: Table does not exist: '#{sql}'" when "C42703" print_error "#{ip}:#{port} Postgres - Error: Column does not exist: '#{sql}'" else # Let the user figure out the rest. print_error "#{ip}:#{port} Postgres - Error: SQL statement '#{sql}' returns #{e.inspect}" end return :error end postgres_print_reply(resp,sql) if doprint print_good "#{ip}:#{port} Postgres - Command complete." return :complete end end # If resp is not actually a Connection::Result object, then return # :error (but not an actual Exception, that's up to the caller. # Otherwise, create a rowset using Rex::Ui::Text::Table (if there's # more than 0 rows) and return :complete. def postgres_print_reply(resp=nil,sql=nil) ip = datastore['RHOST'] port = datastore['RPORT'] return :error unless resp.kind_of? Connection::Result if resp.rows and resp.fields print_status "#{ip}:#{port} Rows Returned: #{resp.rows.size}" if resp.rows.size > 0 tbl = Rex::Ui::Text::Table.new( 'Indent' => 4, 'Header' => "Query Text: '#{sql}'", 'Columns' => resp.fields.map {|x| x.name} ) resp.rows.each {|row| tbl << row.map { |x| x.nil? ? "NIL" : x } } print_line(tbl.to_s) end end return :complete end end end