I have a MySQL server hosted at a particular IP and port. Now, I need to validate this connectivity whether the server is up or down. Also this server uses a self-signed SSL certificate.
Does anyone have any reference python snippet that would comply this connectivity validation along with SSL certificate verification?
Trye the same thing you'd do from a command line. E.g. when you ``telnetserver port, then you get "connection refused" if a server is not listening on the given port, or a timeout if server:port is behind a firewall, or "No route to host" etc. For a successful connection you get something.
Self signed certs can't be validate, AFAIK, and this will work for ssl connections too.
import telnetlib
server_ip = '192.168.2.1'
server_port = 80
timeout = 5
conn_ok = False
try:
tn = telnetlib.Telnet(server_ip, server_port, timeout)
conn_ok = True
except Exception as e:
print(e)
tn.close()
Related
I'm writing some Python code that needs to communicate with a remote host via a TLS connection. I set up an SSL context like this:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
cxt.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
Then, I connected to domain d over port p like this:
s = ctx.wrap_socket(socket.create_connection(d, p))
I was met with a protocol violation on an unexpected EOF. The fix was to create the socket like this:
s = ctx.wrap_socket(socket.create_connection(d, p), server_hostname=d)
As I know next to nothing about TLS, this is pretty confusing. Why would the server hostname be required for a successful connection?
If it matters, I tested a connection to domain d = 'drewdevault.com' on port p = 1965; I'm writing a Gemini client. This was not reproducible with all remote hosts.
The server_hostname argument will be used in the TLS handshake to provide the server with the expected hostname. It is not strictly required in TLS, but it is needed one servers which have multiple certificates for different domain but on the same IP address. Without this information the server does not know which certificate to provide to the client.
I am writing a tool to monitor server certificate expiration. I'm using python3 ssl and socket modules to get the server cert using a pretty basic method of creating a default context, disabling hostname validation and certificate verification, calling SSLSocket.connect(), then SSLSocket.getpeercert(), with the sole purpose of grabbing the server certificate, and that is all.
This is all within a private network and I am not concerned with validation.
I have some devices that require client certs signed by a private CA (which my tool doesn't have), so the handshake fails on SSLSocket.connect(), making SSLSocket.getpeercert() impossible.
I know that the server certificate is indeed being provided to my client (along with that pesky Certificate Request) during the handshake. I can see it in a packet capture, as well as just using the openssl s_client command line.
Here is my code.
def get_cert(self, host, port):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with ctx.wrap_socket(socket.socket(), server_hostname=host) as s:
s.settimeout(10)
s.connect((host, port))
binary_cert = s.getpeercert(True)
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, binary_cert)
pem_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode()
return pem_cert
Is there any way to get a little lower into the handshake messages to get the server cert, even though the handshake ultimately fails?
My current solution is to just run openssl s_client -connect host:port using subprocess.run() in the event of a ssl.SSLError.
You may catch exception that do_handshake() produced and then continue to process server certificate.
import OpenSSL
import socket
dst = ('10.10.10.10', 443)
sock = socket.create_connection(dst)
context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
connection = OpenSSL.SSL.Connection(context, sock)
connection.set_connect_state()
try:
connection.do_handshake()
except:
print(connection.get_peer_cert_chain())
Tested on python 2.7.17 and 3.8.5
It looks like there's unfortunately no way to do it with python's ssl module in versions < 3.10. In those versions, the only way to get the peer certificate that I can see is through the low-level _ssl.SSLSocket.getpeercert() method and that immediately throws exception if the handshake is not complete.
Since python 3.10, there's a new _ssl.SSLSocket.get_unverified_chain() method that does not do the handshake check, so perhaps something like this abomination could work?
ssock = context.wrap_socket(sock, do_handshake_on_connect=False)
try:
ssock.do_handshake()
except ssl.SSLError as e:
pass
certs = ssock._sslobj._sslobj.get_unverified_chain()
... but I have not tested it.
Has anyone found a way to specify a DNS server for OpenSSL connections on a Linux OS? We have internal and external DNS servers and I am building a monitor for SSL certificate usage. I need the ability to specify a DNS server to be used on hostname connections. It works just fine against the internal DNS, but I am having difficulty finding a way to tie in a DNS server. I am fairly new to changing networks through Python and am not sure where to begin. Is it possible to do this through the dns.resolver module's nameservers function?
This looks like a viable solution for Windows, but I am hoping to find something similar for Linux.
How to Change DNS Servers Programmatically in Windows?
Below is my code that works against the default DNS host.
def readCerts(self,host,port,cast):
"""readCerts prompts terminal for username.
Attributes:
host: Host or IP of SSL connection
port: Port of SSL connection
cast: Format of returned results (JSON currently only structure supported)
Response:
Returns certificate attributes in specified format
"""
sslContext = SSL.Context(SSL.SSLv23_METHOD)
apiSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sslConnection = SSL.Connection(sslContext,apiSocket)
try:
sslConnection.connect((host,port))
except Exception as e:
raise e
else:
#Block the socket
sslConnection.setblocking(1)
#Set the hostname field for servers that support SNI. Format must be in bytestring.
sslConnection.set_tlsext_host_name(host.encode('utf-8'))
try:
sslConnection.do_handshake()
except:
pass
else:
#print "handshake succeeded"
sslConnection.close()
if cast.upper()=='JSON':
attributes = self._FormatJSON(sslConnection.get_peer_cert_chain())
return attributes
I configured my windows machine's DNS server to 127.0.0.1 and on localhost I created a basic python server:
from socket import *
serverPort = 53
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('127.0.0.1', serverPort))
print "The server is ready to receive on port: {}".format(serverPort)
while 1:
try:
message, clientAddress = serverSocket.recvfrom(512)
except:
continue
print clientAddress, message
modifiedMessage = "127.0.0.1"
serverSocket.sendto(modifiedMessage, clientAddress)
PS :I know that DNS is a binary protocol and sending ASCII text won't do any good, but I am not trying to make a resolver, I am trying to see with transperancy that how the former works.
When I srarted the server, I am greated with the following output:
(('127.0.0.1', 53945), '.\x9c\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 53945), '.\x9c\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 53945), '.\x9c\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 61362), '\xefc\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 50065), '\xb5\xfc\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 61362), '\xefc\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 61362), '\xefc\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x06google\x03com\x00\x00\x01\x00\x01')
(('127.0.0.1', 52718), '\xc7\x15\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05tiles\x08services\x07mozilla\x03com\x00\x00\x01\x00\x01')
But unlike as I enticipated, I am still able to open websites. And Wireshark told me that I am making connection to 8.8.8.8(IDK how?).
I tried flushing the DNS cashe from my machine, nada.
What am I missing?
PPS: If I remove the try/catch clause I get this error(a few seconds after the execution of the program):
error: [Errno 10054] An existing connection was forcibly closed by the remote host
You probably have configured Googles 8.8.8.8 as a fallback DNS server.
And since you are destroying the DNS answers, whoever is receiving these broken answers is falling back to the secondary server. The whole path of DNS queries on a typical UN*X machine is quite complicated and the whole system is usually quite robust.
I am adapting a Python script to be OS independent and run on Windows. I have changed its ssh system calls to calls to paramiko functions. I am stuck with the issue of http proxy authentication. In Unix (actually Cygwin) environment I would use ~/.ssh/config
Host *
ProxyCommand corkscrew http-proxy.example.com 8080 %h %p
Is there a way to obtain the same using paramiko (or the Python ssh module) either using or not using corkscrew? This post seems to suggest that, but I don't know how.
Note: I am behind a firewall that allows me to use only port 80. I need to control Amazon ec2 instances so I configured the sshd server on those machines to listen to port 80. Everything is working fine in my cygwin+corkscrew prototype, but I would like to have a Python script that works without Cygwin.
You can use any pre-established session to paramiko via the sock parameter in SSHClient.connect(hostname,username,password,...,sock).
Below is a code-snippet that tunnels SSH via HTTP-Proxy-Tunnel (HTTP-CONNECT). At first the connection to the proxy is established and the proxy is instructed to connect to localhost:22. The result is a TCP tunnel over the established session that is usually used to tunnel SSL but can be used for any tcp based protocol.
This scenario works with a default installation of tinyproxy with Allow <yourIP> and ConnectPort 22 being set in /etc/tinyproxy.conf. The proxy and the sshd are running on the same host in my example but all you need is any proxy that allows you to CONNECT to your ssh port. Usually this is restricted to port 443 (hint: if you make your sshd listen on 443 this will work with most of the public proxies even thought I do not recommend to do this for interop and security reasons). If this ultimately allows you to bypass your firewall depends on what kind of firewall is employed. If there's no DPI/SSL-Interception features involved, you should be fine. If there's SSL-Interception involved you could still try to tunnel it via ssl or as part of HTTP payload :)
import paramiko
import socket
import logging
logging.basicConfig(loglevel=logging.DEBUG)
LOG = logging.getLogger("xxx")
def http_proxy_tunnel_connect(proxy, target,timeout=None):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect(proxy)
LOG.debug("connected")
cmd_connect = "CONNECT %s:%d HTTP/1.1\r\n\r\n"%target
LOG.debug("--> %s"%repr(cmd_connect))
sock.sendall(cmd_connect)
response = []
sock.settimeout(2) # quick hack - replace this with something better performing.
try:
# in worst case this loop will take 2 seconds if not response was received (sock.timeout)
while True:
chunk = sock.recv(1024)
if not chunk: # if something goes wrong
break
response.append(chunk)
if "\r\n\r\n" in chunk: # we do not want to read too far ;)
break
except socket.error, se:
if "timed out" not in se:
response=[se]
response = ''.join(response)
LOG.debug("<-- %s"%repr(response))
if not "200 connection established" in response.lower():
raise Exception("Unable to establish HTTP-Tunnel: %s"%repr(response))
return sock
if __name__=="__main__":
LOG.setLevel(logging.DEBUG)
LOG.debug("--start--")
sock = http_proxy_tunnel_connect(proxy=("192.168.139.128",8888),
target=("192.168.139.128",22),
timeout=50)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname="192.168.139.128",sock=sock, username="xxxx", password="xxxxx")
print "#> whoami \n%s"% ssh.exec_command("whoami")[1].read()
output:
DEBUG:xxx:--start--
DEBUG:xxx:connected
DEBUG:xxx:--> 'CONNECT 192.168.139.128:22 HTTP/1.1\r\n\r\n'
DEBUG:xxx:<-- 'HTTP/1.0 200 Connection established\r\nProxy-agent: tinyproxy/1.8.3\r\n\r\n'
#> whoami
root
here are some other resources on how to tunnel through proxies. Just do whatever is needed to establish your tunnel and pass the socket to SSHClient.connect(...,sock)
There's paraproxy, which implements proxy support for Paramiko.
The post you linked to suggets that Paramiko can operate over an arbitrary socket, but that doesn't appear to be the case. In fact, paraproxy works by completing replacing specific methods inside paramiko, since the existing code simply calls socket.socket() to obtain a socket and does not offer any way of hooking in a proxy.