Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/postgres/postgres-pr/connection.rb
Views: 11777
# -*- coding: binary -*-1#2# Author:: Michael Neumann3# Copyright:: (c) 2005 by Michael Neumann4# License:: Same as Ruby's or BSD5#67require 'postgres_msf'8require 'postgres/postgres-pr/message'9require 'postgres/postgres-pr/version'10require 'postgres/postgres-pr/scram_sha_256'11require 'uri'12require 'rex/socket'1314# Namespace for Metasploit branch.15module Msf16module Db1718module PostgresPR1920PROTO_VERSION = 3 << 16 #1966082122class AuthenticationMethodMismatch < StandardError23end2425class Connection2627# Allow easy access to these instance variables28attr_reader :conn, :params, :transaction_status2930# A block which is called with the NoticeResponse object as parameter.31attr_accessor :notice_processor3233#34# Returns one of the following statuses:35#36# PQTRANS_IDLE = 0 (connection idle)37# PQTRANS_INTRANS = 2 (idle, within transaction block)38# PQTRANS_INERROR = 3 (idle, within failed transaction)39# PQTRANS_UNKNOWN = 4 (cannot determine status)40#41# Not yet implemented is:42#43# PQTRANS_ACTIVE = 1 (command in progress)44#45def transaction_status46case @transaction_status47when ?I48049when ?T50251when ?E52353else54455end56end5758def initialize(database, user, password=nil, uri = nil, proxies = nil)59uri ||= DEFAULT_URI6061@transaction_status = nil62@params = { 'username' => user, 'database' => database }63establish_connection(uri, proxies)6465# Check if the password supplied is a Postgres-style md5 hash66md5_hash_match = password.match(/^md5([a-f0-9]{32})$/)6768write_message(StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database))6970loop do71msg = Message.read(@conn)7273case msg74when AuthentificationClearTextPassword75raise ArgumentError, "no password specified" if password.nil?76raise AuthenticationMethodMismatch, "Server expected clear text password auth" if md5_hash_match77write_message(PasswordMessage.new(password))78when AuthentificationCryptPassword79raise ArgumentError, "no password specified" if password.nil?80raise AuthenticationMethodMismatch, "Server expected crypt password auth" if md5_hash_match81write_message(PasswordMessage.new(password.crypt(msg.salt)))82when AuthentificationMD5Password83raise ArgumentError, "no password specified" if password.nil?84require 'digest/md5'8586if md5_hash_match87m = md5_hash_match[1]88else89m = Digest::MD5.hexdigest(password + user)90end91m = Digest::MD5.hexdigest(m + msg.salt)92m = 'md5' + m9394write_message(PasswordMessage.new(m))9596when AuthenticationSASL97negotiate_sasl(msg, user, password)98when UnknownAuthType99raise "unknown auth type '#{msg.auth_type}' with buffer content:\n#{Rex::Text.to_hex_dump(msg.buffer.content)}"100101when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential102raise "unsupported authentication"103104when AuthentificationOk105when ErrorResponse106handle_server_error_message(msg)107when NoticeResponse108@notice_processor.call(msg) if @notice_processor109when ParameterStatus110@params[msg.key] = msg.value111when BackendKeyData112# TODO113#p msg114when ReadyForQuery115@transaction_status = msg.backend_transaction_status_indicator116break117else118raise "unhandled message type"119end120end121end122123def peerhost124@conn.peerhost125end126127def peerport128@conn.peerport129end130131def peerinfo132"#{peerhost}:#{peerport}"133end134135def current_database136@params['database']137end138139# List of supported PostgreSQL platforms & architectures:140# https://postgrespro.com/docs/postgresql/16/supported-platforms141def map_compile_os_to_platform(compile_os)142return Msf::Platform::Unknown.realname if compile_os.blank?143144compile_os = compile_os.downcase.encode(::Encoding::BINARY)145146if compile_os.match?('linux')147platform = Msf::Platform::Linux148elsif compile_os.match?(/(darwin|mac|osx)/)149platform = Msf::Platform::OSX150elsif compile_os.match?('win')151platform = Msf::Platform::Windows152elsif compile_os.match?('free')153platform = Msf::Platform::FreeBSD154elsif compile_os.match?('net')155platform = Msf::Platform::NetBSD156elsif compile_os.match?('open')157platform = Msf::Platform::OpenBSD158elsif compile_os.match?('solaris')159platform = Msf::Platform::Solaris160elsif compile_os.match?('aix')161platform = Msf::Platform::AIX162elsif compile_os.match?('hpux')163platform = Msf::Platform::HPUX164elsif compile_os.match?('irix')165platform = Msf::Platform::Irix166else167# Return the query result if the value can't be mapped168return compile_os169end170171platform.realname172end173174# List of supported PostgreSQL platforms & architectures:175# https://postgrespro.com/docs/postgresql/16/supported-platforms176def map_compile_arch_to_architecture(compile_arch)177return '' if compile_arch.blank?178179compile_arch = compile_arch.downcase.encode(::Encoding::BINARY)180181if compile_arch.match?('sparc')182if compile_arch.include?('64')183arch = ARCH_SPARC64184else185arch = ARCH_SPARC186end187elsif compile_arch.include?('mips')188arch = ARCH_MIPS189elsif compile_arch.include?('ppc')190arch = ARCH_PPC191elsif compile_arch.match?('arm')192if compile_arch.match?('64')193arch = ARCH_AARCH64194elsif compile_arch.match?('arm')195arch = ARCH_ARMLE196end197elsif compile_arch.match?('64')198arch = ARCH_X86_64199elsif compile_arch.match?('86') || compile_arch.match?('i686')200arch = ARCH_X86201else202# Return the query result if the value can't be mapped203arch = compile_arch204end205206arch207end208209# @return [Hash] Detect the platform and architecture of the PostgreSQL server:210# * :arch [String] The server architecture.211# * :platform [String] The server platform.212def detect_platform_and_arch213result = {}214215query_result = query('select version()').rows[0][0]216match_platform_and_arch = query_result.match(/on (?<architecture>\w+)-\w+-(?<platform>\w+)/)217218if match_platform_and_arch.nil?219arch = platform = query_result220else221arch = match_platform_and_arch[:architecture]222platform = match_platform_and_arch[:platform]223end224225result[:arch] = map_compile_arch_to_architecture(arch)226result[:platform] = map_compile_os_to_platform(platform)227228result229end230231def close232raise "connection already closed" if @conn.nil?233@conn.shutdown234@conn = nil235end236237class Result238attr_accessor :rows, :fields, :cmd_tag239def initialize(rows=[], fields=[])240@rows, @fields = rows, fields241end242end243244def query(sql)245write_message(Query.new(sql))246247result = Result.new248errors = []249250loop do251msg = Message.read(@conn)252case msg253when DataRow254result.rows << msg.columns255when CommandComplete256result.cmd_tag = msg.cmd_tag257when ReadyForQuery258@transaction_status = msg.backend_transaction_status_indicator259break260when RowDescription261result.fields = msg.fields262when CopyInResponse263when CopyOutResponse264when EmptyQueryResponse265when ErrorResponse266# TODO267errors << msg268when NoticeResponse269@notice_processor.call(msg) if @notice_processor270else271# TODO272end273end274275raise errors.map{|e| e.field_values.join("\t") }.join("\n") unless errors.empty?276277result278end279280281# @param [AuthenticationSASL] msg282# @param [String] user283# @param [String,nil] password284def negotiate_sasl(msg, user, password = nil)285if msg.mechanisms.include?('SCRAM-SHA-256')286scram_sha_256 = ScramSha256.new287# Start negotiating scram, additionally wrapping in SASL and unwrapping the SASL responses288scram_sha_256.negotiate(user, password) do |state, value|289if state == :client_first290sasl_initial_response_message = SaslInitialResponseMessage.new(291mechanism: 'SCRAM-SHA-256',292value: value293)294295write_message(sasl_initial_response_message)296297sasl_continue = Message.read(@conn)298raise handle_server_error_message(sasl_continue) if sasl_continue.is_a?(ErrorResponse)299raise AuthenticationMethodMismatch, "Did not receive AuthenticationSASLContinue - instead got #{sasl_continue}" unless sasl_continue.is_a?(AuthenticationSASLContinue)300301server_first_string = sasl_continue.value302server_first_string303elsif state == :client_final304sasl_initial_response_message = SASLResponseMessage.new(305value: value306)307308write_message(sasl_initial_response_message)309310server_final = Message.read(@conn)311raise handle_server_error_message(server_final) if server_final.is_a?(ErrorResponse)312raise AuthenticationMethodMismatch, "Did not receive AuthenticationSASLFinal - instead got #{server_final}" unless server_final.is_a?(AuthenticationSASLFinal)313314server_final_string = server_final.value315server_final_string316else317raise AuthenticationMethodMismatch, "Unexpected negotiation state #{state}"318end319end320else321raise AuthenticationMethodMismatch, "unsupported SASL mechanisms #{msg.mechanisms.inspect}"322end323end324325DEFAULT_PORT = 5432326DEFAULT_HOST = 'localhost'327DEFAULT_PATH = '/tmp'328DEFAULT_URI =329if RUBY_PLATFORM.include?('win')330'tcp://' + DEFAULT_HOST + ':' + DEFAULT_PORT.to_s331else332'unix:' + File.join(DEFAULT_PATH, '.s.PGSQL.' + DEFAULT_PORT.to_s)333end334335private336337# @param [ErrorResponse] server_error_message338# @raise [RuntimeError]339def handle_server_error_message(server_error_message)340raise server_error_message.field_values.join("\t")341end342343# tcp://localhost:5432344# unix:/tmp/.s.PGSQL.5432345def establish_connection(uri, proxies)346u = URI.parse(uri)347case u.scheme348when 'tcp'349@conn = Rex::Socket.create(350'PeerHost' => (u.host || DEFAULT_HOST).gsub(/[\[\]]/, ''), # Strip any brackets off (IPv6)351'PeerPort' => (u.port || DEFAULT_PORT),352'proto' => 'tcp',353'Proxies' => proxies354)355when 'unix'356@conn = UNIXSocket.new(u.path)357else358raise 'unrecognized uri scheme format (must be tcp or unix)'359end360end361362# @param [Message] message363# @return [Numeric] The byte count successfully written to the currently open connection364def write_message(message)365@conn << message.dump366end367end368369end # module PostgresPR370371end372end373374375