Skip to content

Commit 63cccde

Browse files
committed
Adds SSL support to the postgres_login module
1 parent b3176f0 commit 63cccde

File tree

8 files changed

+91
-16
lines changed

8 files changed

+91
-16
lines changed

lib/metasploit/framework/login_scanner/postgres.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
require 'metasploit/framework/login_scanner/base'
2+
require 'metasploit/framework/login_scanner/rex_socket'
3+
require 'metasploit/framework/tcp/client'
24
require 'postgres_msf'
35

46
module Metasploit
@@ -10,6 +12,8 @@ module LoginScanner
1012
# and attempting them. It then saves the results.
1113
class Postgres
1214
include Metasploit::Framework::LoginScanner::Base
15+
include Metasploit::Framework::LoginScanner::RexSocket
16+
include Metasploit::Framework::Tcp::Client
1317

1418
# @returns [Boolean] If a login is successful and this attribute is true - a Msf::Db::PostgresPR::Connection instance is used as proof,
1519
# and the socket is not immediately closed
@@ -45,7 +49,14 @@ def attempt_login(credential)
4549
pg_conn = nil
4650

4751
begin
48-
pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri,proxies)
52+
pg_conn = Msf::Db::PostgresPR::Connection.new(
53+
db_name,
54+
credential.public,
55+
credential.private,
56+
uri,
57+
proxies,
58+
ssl
59+
)
4960
rescue ::RuntimeError => e
5061
case e.to_s.split("\t")[1]
5162
when "C3D000"

lib/metasploit/framework/tcp/client.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ def connect(global = true, opts={})
8080
dossl = ssl
8181
end
8282

83+
# For Postgres, always connect with SSL disabled; SSL is enabled after the initial connection is made
84+
if defined?(self) && self.class.name =~ /Postgres/
85+
dossl = false
86+
end
87+
8388
nsock = Rex::Socket::Tcp.create(
8489
'PeerHost' => opts['RHOST'] || rhost,
8590
'PeerHostname' => opts['SSLServerNameIndication'] || opts['RHOSTNAME'],

lib/msf/core/exploit/remote/postgres.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ module Exploit::Remote::Postgres
1212

1313
require 'postgres_msf'
1414
require 'base64'
15+
require 'metasploit/framework/tcp/client'
1516
include Msf::Db::PostgresPR
17+
include Exploit::Remote::Tcp
1618

1719
# @!attribute [rw] postgres_conn
1820
# @return [::Msf::Db::PostgresPR::Connection]

lib/postgres/postgres-pr/connection.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ def transaction_status
5656
end
5757
end
5858

59-
def initialize(database, user, password=nil, uri = nil, proxies = nil)
59+
def initialize(database, user, password=nil, uri = nil, proxies = nil, ssl = nil)
6060
uri ||= DEFAULT_URI
6161

6262
@transaction_status = nil
6363
@params = { 'username' => user, 'database' => database }
64-
establish_connection(uri, proxies)
64+
establish_connection(uri, proxies, ssl)
6565

6666
# Check if the password supplied is a Postgres-style md5 hash
6767
md5_hash_match = password.match(/^md5([a-f0-9]{32})$/)
@@ -343,16 +343,33 @@ def handle_server_error_message(server_error_message)
343343

344344
# tcp://localhost:5432
345345
# unix:/tmp/.s.PGSQL.5432
346-
def establish_connection(uri, proxies)
346+
def establish_connection(uri, proxies, ssl = nil)
347347
u = URI.parse(uri)
348348
case u.scheme
349349
when 'tcp'
350350
@conn = Rex::Socket.create(
351-
'PeerHost' => (u.host || DEFAULT_HOST).gsub(/[\[\]]/, ''), # Strip any brackets off (IPv6)
352-
'PeerPort' => (u.port || DEFAULT_PORT),
353-
'proto' => 'tcp',
354-
'Proxies' => proxies
355-
)
351+
'PeerHost' => (u.host || DEFAULT_HOST).gsub(/\[|\]/, ''),
352+
'PeerPort' => (u.port || DEFAULT_PORT),
353+
'proto' => 'tcp',
354+
'Proxies' => proxies
355+
)
356+
if ssl
357+
# Send SSLRequest packet
358+
ssl_request = [8, 80877103].pack('N2')
359+
@conn.write(ssl_request)
360+
response = @conn.read(1)
361+
if response == 'S'
362+
ssl_context = OpenSSL::SSL::SSLContext.new
363+
ssl_socket = OpenSSL::SSL::SSLSocket.new(@conn, ssl_context)
364+
ssl_socket.sync_close = true
365+
ssl_socket.connect
366+
@conn = ssl_socket
367+
elsif response == 'N'
368+
# Server does not support SSL, continue with plain TCP
369+
else
370+
raise "Unexpected response to SSLRequest: #{response.inspect}"
371+
end
372+
end
356373
when 'unix'
357374
@conn = UNIXSocket.new(u.path)
358375
else

lib/postgres/postgres-pr/message.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,40 @@ def read_exactly_n_bytes(n)
1818
end
1919
end
2020

21+
# Monkeypatch for OpenSSL::SSL::SSLSocket to support read_exactly_n_bytes
22+
class OpenSSL::SSL::SSLSocket
23+
# Reads exactly n bytes from the SSL socket.
24+
#
25+
# @param n [Integer] The number of bytes to read from the socket.
26+
# @return [String] The read bytes. If the socket is closed before reading n bytes, raises EOFError.
27+
# @raise [EOFError] If the socket is closed before reading n bytes.
28+
def read_exactly_n_bytes(n)
29+
buf = read(n)
30+
raise EOFError if buf == nil
31+
return buf if buf.size == n
32+
33+
n -= buf.size
34+
35+
while n > 0
36+
str = read(n)
37+
raise EOFError if str == nil
38+
buf << str
39+
n -= str.size
40+
end
41+
buf
42+
end
43+
44+
# @return [String] The remote IP address that the Mysql server is running on
45+
def peerhost
46+
io.remote_address.ip_address
47+
end
48+
49+
# @return [Integer] The remote port that the Mysql server is running on
50+
def peerport
51+
io.remote_address.ip_port
52+
end
53+
end
54+
2155
# Namespace for Metasploit branch.
2256
module Msf
2357
module Db

modules/auxiliary/scanner/postgres/postgres_login.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,14 @@ def run_host(ip)
113113
stop_on_success: datastore['STOP_ON_SUCCESS'],
114114
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
115115
connection_timeout: 30,
116+
max_send_size: (datastore['TCP::max_send_size']),
117+
send_delay: (datastore['TCP::send_delay']),
116118
framework: framework,
117119
framework_module: self,
120+
ssl: datastore['SSL'],
121+
ssl_version: datastore['SSLVersion'],
122+
ssl_verify_mode: datastore['SSLVerifyMode'],
123+
ssl_cipher: datastore['SSLCipher'],
118124
use_client_as_proof: create_session?
119125
)
120126
)

spec/lib/metasploit/framework/login_scanner/postgres_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
context 'when there is no realm on the credential' do
4242
it 'uses template1 as the default realm' do
43-
expect(Msf::Db::PostgresPR::Connection).to receive(:new).with('template1', 'root', 'toor', 'tcp://:', nil)
43+
expect(Msf::Db::PostgresPR::Connection).to receive(:new).with('template1', 'root', 'toor', 'tcp://:', nil, nil)
4444
login_scanner.attempt_login(cred_no_realm)
4545
end
4646
end

spec/lib/msf/core/rhosts_walker_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -972,9 +972,9 @@ def create_tempfile(content)
972972
it 'enumerates postgres schemes' do
973973
postgres_mod.datastore['RHOSTS'] = 'postgres://postgres:@example.com "postgres://user:a b [email protected]/" "postgres://user:a b [email protected]:9001/database_name"'
974974
expected = [
975-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 5432, 'USERNAME' => 'postgres', 'PASSWORD' => '', 'DATABASE' => 'template1' },
976-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 5432, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'template1' },
977-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 9001, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'database_name' }
975+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 5432, 'SSL' => false, 'USERNAME' => 'postgres', 'PASSWORD' => '', 'DATABASE' => 'template1' },
976+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 5432,'SSL' => false, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'template1' },
977+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => '192.0.2.2', 'RPORT' => 9001, 'SSL' => false, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'database_name' }
978978
]
979979
expect(each_error_for(postgres_mod)).to be_empty
980980
expect(each_host_for(postgres_mod)).to have_datastore_values(expected)
@@ -984,9 +984,9 @@ def create_tempfile(content)
984984
postgres_mod.datastore['RHOSTS'] = 'postgres://postgres:@example.com "postgres://user:a b [email protected]/" "postgres://user:a b [email protected]:9001/database_name"'
985985
postgres_mod.datastore['PROXIES'] = 'socks5h:198.51.100.1:1080'
986986
expected = [
987-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 5432, 'USERNAME' => 'postgres', 'PASSWORD' => '', 'DATABASE' => 'template1' },
988-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 5432, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'template1' },
989-
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 9001, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'database_name' }
987+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 5432, 'SSL' => false, 'USERNAME' => 'postgres', 'PASSWORD' => '', 'DATABASE' => 'template1' },
988+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 5432, 'SSL' => false, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'template1' },
989+
{ 'RHOSTNAME' => 'example.com', 'RHOSTS' => 'example.com', 'RPORT' => 9001, 'SSL' => false, 'USERNAME' => 'user', 'PASSWORD' => 'a b c', 'DATABASE' => 'database_name' }
990990
]
991991
expect(each_error_for(postgres_mod)).to be_empty
992992
expect(each_host_for(postgres_mod)).to have_datastore_values(expected)

0 commit comments

Comments
 (0)