Skip to content

Commit dbb631f

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

File tree

8 files changed

+103
-22
lines changed

8 files changed

+103
-22
lines changed

lib/metasploit/framework/login_scanner/postgres.rb

Lines changed: 19 additions & 6 deletions
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"
@@ -90,13 +101,15 @@ def attempt_login(credential)
90101

91102
::Metasploit::Framework::LoginScanner::Result.new(result_options)
92103
end
93-
end
94104

95-
def set_sane_defaults
96-
self.connection_timeout ||= 30
97-
self.port ||= DEFAULT_PORT
98-
end
105+
def set_sane_defaults
106+
self.connection_timeout ||= 30
107+
self.port ||= DEFAULT_PORT
108+
self.max_send_size ||= 0
109+
self.send_delay ||= 0
110+
end
99111

112+
end
100113
end
101114
end
102115
end

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: 30 additions & 9 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})$/)
@@ -231,7 +231,11 @@ def detect_platform_and_arch
231231

232232
def close
233233
raise "connection already closed" if @conn.nil?
234-
@conn.shutdown
234+
if @conn.respond_to?(:shutdown)
235+
@conn.shutdown
236+
elsif @conn.respond_to?(:close)
237+
@conn.close
238+
end
235239
@conn = nil
236240
end
237241

@@ -343,16 +347,33 @@ def handle_server_error_message(server_error_message)
343347

344348
# tcp://localhost:5432
345349
# unix:/tmp/.s.PGSQL.5432
346-
def establish_connection(uri, proxies)
350+
def establish_connection(uri, proxies, ssl = nil)
347351
u = URI.parse(uri)
348352
case u.scheme
349353
when 'tcp'
350354
@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-
)
355+
'PeerHost' => (u.host || DEFAULT_HOST).gsub(/\[|\]/, ''),
356+
'PeerPort' => (u.port || DEFAULT_PORT),
357+
'proto' => 'tcp',
358+
'Proxies' => proxies
359+
)
360+
if ssl
361+
# Send SSLRequest packet
362+
ssl_request = [8, 80877103].pack('N2')
363+
@conn.write(ssl_request)
364+
response = @conn.read(1)
365+
if response == 'S'
366+
ssl_context = OpenSSL::SSL::SSLContext.new
367+
ssl_socket = OpenSSL::SSL::SSLSocket.new(@conn, ssl_context)
368+
ssl_socket.sync_close = true
369+
ssl_socket.connect
370+
@conn = ssl_socket
371+
elsif response == 'N'
372+
# Server does not support SSL, continue with plain TCP
373+
else
374+
raise "Unexpected response to SSLRequest: #{response.inspect}"
375+
end
376+
end
356377
when 'unix'
357378
@conn = UNIXSocket.new(u.path)
358379
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)