PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/nogotofail/mitm/connection/connection.py

https://gitlab.com/gaurav1981/nogotofail
Python | 595 lines | 592 code | 0 blank | 3 comment | 0 complexity | 1575b461e5a16d58a536179846f4e9c6 MD5 | raw file
Possible License(s): Apache-2.0
  1. r'''
  2. Copyright 2014 Google Inc. All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. '''
  13. from OpenSSL import SSL
  14. from OpenSSL import crypto
  15. import logging
  16. import select
  17. import socket
  18. import struct
  19. from nogotofail.mitm.util import tls, ssl2
  20. from nogotofail.mitm.util import close_quietly
  21. import time
  22. import uuid
  23. class ConnectionWrapper(object):
  24. """Wrapper around OpenSSL's Connection object to make recv act like socket.recv()
  25. """
  26. def __init__(self, connection):
  27. self._connection = connection
  28. def __getattr__(self, name):
  29. return getattr(self._connection, name)
  30. def recv(self, size):
  31. """Wrapper around pyOpenSSL's Connection.recv
  32. PyOpenSSL doesn't return "" on error like socket.recv does,
  33. instead it throws a SSL.ZeroReturnError or (-1, "Unexpected EOF") erorrs.
  34. Wrap recv so we don't have to deal with that noise.
  35. """
  36. buf = ""
  37. try:
  38. buf = self._connection.recv(size)
  39. except SSL.SysCallError as e:
  40. if e.args != (-1, "Unexpected EOF"):
  41. raise e
  42. except SSL.Error as e:
  43. if e.args != (-1, "Unexpected EOF"):
  44. raise e
  45. except SSL.ZeroReturnError:
  46. pass
  47. return buf
  48. def stub_verify(conn, cert, errno, errdepth, code):
  49. """We don't verify the server when we attempt a MiTM.
  50. If the client was connecting to a host with a bad cert
  51. we still want to connect and MiTM them.
  52. Hypothetically someone could MiTM our MiTM and intercept what we intercept,
  53. use caution in what data you send through a MiTM'd connection if you don't trust
  54. the rest of your path to the real endpoint.
  55. """
  56. return True
  57. class BaseConnection(object):
  58. """Handles the creation and bridging of both sides of the network connection
  59. and passing data and events to the handler provided by handler_selector.
  60. Depending on handler.proxy the connection can act as a simple pass through
  61. proxy or as an SSL terminator.
  62. Connections should subclass this and implement start and _get_client_remote_name
  63. in order to set up the remote socket correctly.
  64. """
  65. handler = None
  66. data_handlers = []
  67. ssl_handler_selector = None
  68. client_socket = None
  69. raw_client_socket = None
  70. client_addr, client_port = None, None
  71. server_socket = None
  72. raw_server_socket = None
  73. server_addr, server_port = None, None
  74. server_cert_path = None
  75. app_blame = None
  76. _applications = None
  77. logger = None
  78. server = None
  79. ssl = False
  80. last_used = None
  81. id = None
  82. hostname = None
  83. closed = False
  84. SSL_TIMEOUT = 2
  85. def __init__(
  86. self, server, client_socket, handler_selector,
  87. ssl_handler_selector, data_handler_selector, app_blame):
  88. self.id = uuid.uuid4()
  89. self.client_addr, self.client_port = client_socket.getpeername()[:2]
  90. self.server = server
  91. self.app_blame = app_blame
  92. self.ssl_handler_selector = ssl_handler_selector
  93. self.client_socket = client_socket
  94. self.raw_client_socket = client_socket
  95. self.logger = logging.getLogger("nogotofail.mitm")
  96. self.last_used = time.time()
  97. self.handler = handler_selector(self, app_blame)(self)
  98. data_handler_classes = data_handler_selector(self, app_blame)
  99. self.data_handlers = [handler_class(self)
  100. for handler_class in data_handler_classes]
  101. @staticmethod
  102. def setup_server_socket(sock):
  103. """Do any additional pre-bind setup needed on the local socket.
  104. This can be used to set sockopts as needed"""
  105. pass
  106. def start(self):
  107. """Setup the remote end of the connection and client connection
  108. to be ready to start bridging traffic.
  109. This method should be implemented based on how connections are routed to nogotofail.mitm
  110. such as iptables redirect or proxies.
  111. This should call handler.on_select and handler.on_establish when appropriate
  112. and set server_addr and server_port to the remote endpoint's address.
  113. Returns if setup was successful.
  114. """
  115. raise NotImplemented()
  116. def connect_ssl(self, client_hello):
  117. """Sets up both ends of SSL termination.
  118. Note that client_socket MUST have the TLS ClientHello as the first thing
  119. recv() returns
  120. or else pyOpenSSL will bail.
  121. Returns setup success.
  122. """
  123. server_name = client_hello.extensions.get("server_name")
  124. if server_name:
  125. server_name = server_name.data
  126. self.hostname = server_name
  127. try:
  128. # Send our own client hello to the other side
  129. self._setup_server_connection(server_name)
  130. server_cert = self.server_socket.get_peer_certificate()
  131. handler_cert = self.handler.on_certificate(server_cert)
  132. ciphers_list = self.handler.on_server_cipher_suites(client_hello)
  133. context = SSL.Context(SSL.SSLv23_METHOD)
  134. context.set_verify(SSL.VERIFY_NONE, stub_verify)
  135. if ciphers_list is not None:
  136. context.set_cipher_list(ciphers_list)
  137. if handler_cert is not None:
  138. context.use_certificate_chain_file(handler_cert)
  139. context.use_privatekey_file(handler_cert)
  140. # Required for anonymous/ephemeral DH cipher suites
  141. context.load_tmp_dh("./dhparam")
  142. # Required for anonymous/ephemeral ECDH cipher suites
  143. # The API is not available in the old version of pyOpenSSL which we
  144. # currently use. Without the code below, anonymous and ephemeral
  145. # ECDH cipher suites will not be used.
  146. if hasattr(context, "set_tmp_ecdh"):
  147. curve = crypto.get_elliptic_curve("prime256v1")
  148. context.set_tmp_ecdh(curve)
  149. # Send our ServerHello to the Client. Note that the Client's ClientHello
  150. # MUST be the first thing that self.client_socket.recv() returns
  151. self.client_socket.setblocking(False)
  152. connection = SSL.Connection(context, self.client_socket)
  153. connection.set_accept_state()
  154. self._do_ssl_handshake(connection)
  155. self.client_socket.setblocking(True)
  156. self.client_socket = ConnectionWrapper(connection)
  157. # Let the server know our sockets have changed
  158. self.server.update_sockets(self)
  159. except SSL.Error as e:
  160. self.handler.on_ssl_error(e)
  161. return False
  162. self.handler.on_ssl_establish()
  163. self.ssl = True
  164. return True
  165. def _setup_server_connection(self, servername=None):
  166. context = SSL.Context(SSL.SSLv23_METHOD)
  167. context.set_verify(SSL.VERIFY_NONE, stub_verify)
  168. self.server_socket.setblocking(False)
  169. connection = SSL.Connection(context, self.server_socket)
  170. if servername:
  171. connection.set_tlsext_host_name(servername)
  172. connection.set_connect_state()
  173. self._do_ssl_handshake(connection)
  174. self.server_socket.setblocking(True)
  175. # OpenSSL connections are socket like, so we can use them as if they
  176. # were a socket(once wrapped for compat)
  177. self.server_socket = ConnectionWrapper(connection)
  178. def _do_ssl_handshake(self, connection):
  179. start = time.time()
  180. while True:
  181. try:
  182. connection.do_handshake()
  183. break
  184. except (SSL.WantReadError, SSL.WantWriteError):
  185. now = time.time()
  186. if now - start > BaseConnection.SSL_TIMEOUT:
  187. raise socket.timeout
  188. remaining = BaseConnection.SSL_TIMEOUT - (now - start)
  189. r, w, x = select.select(
  190. [connection], [connection], [], remaining)
  191. if not r and not w:
  192. raise socket.timeout
  193. def bridge(self, sock):
  194. """Handle bridging data from sock to the other party.
  195. Returns if the connection should continue.
  196. """
  197. self.last_used = time.time()
  198. if (sock == self.client_socket):
  199. return self._bridge_client()
  200. else:
  201. return self._bridge_server()
  202. def close(self, handler_initiated=True):
  203. """Close the connection. Does nothing if the connection is already closed.
  204. handler_initiated: If a handler is requesting a close versus the connection
  205. being closed by one of the endpoints.
  206. """
  207. if self.closed:
  208. return
  209. self.closed = True
  210. close_quietly(self.server_socket)
  211. close_quietly(self.client_socket)
  212. close_quietly(self.raw_client_socket)
  213. close_quietly(self.raw_server_socket)
  214. self.handler.on_close(handler_initiated)
  215. for handler in self.data_handlers:
  216. handler.on_close(handler_initiated)
  217. def _check_for_ssl(self, client_request):
  218. """ Check for a client_hello in client_request and handle setting up handlers and any mitm.
  219. Returns if client_request was used(and should not be sent to the server)
  220. """
  221. # check for a TLS Client Hello
  222. record = tls.parse_tls(client_request)
  223. client_hello = None
  224. if record:
  225. first = record.messages[0]
  226. if isinstance(first, tls.types.HandshakeMessage)\
  227. and isinstance(first.obj, tls.types.ClientHello):
  228. client_hello = first.obj
  229. else:
  230. # Check for an SSLv2 Client Hello
  231. record = ssl2.parse_ssl2(client_request)
  232. if record and isinstance(record.message.obj, ssl2.types.ClientHello):
  233. client_hello = record.message.obj
  234. if not client_hello:
  235. return False
  236. return self._handle_hello(client_hello)
  237. def _handle_hello(self, client_hello):
  238. """ Handles the changing of handlers on a TLS client hello and optional mitm
  239. Returns if a MiTM was created
  240. """
  241. # Check for a server name and set our hostname
  242. if not self.hostname:
  243. server_name = client_hello.extensions.get("server_name")
  244. if server_name:
  245. server_name = server_name.data
  246. self.hostname = server_name
  247. # Swap to a new handler if needed.
  248. handler_class = self.ssl_handler_selector(
  249. self, client_hello, self.app_blame)
  250. if handler_class:
  251. handler = handler_class(self)
  252. self.handler.on_remove()
  253. self.handler = handler
  254. self.handler.on_select()
  255. # Check if we should start mitming this connection
  256. should_mitm = self.handler.on_ssl(client_hello)
  257. if should_mitm:
  258. self.connect_ssl(client_hello)
  259. return True
  260. return False
  261. def _bridge_client(self):
  262. try:
  263. # Check for a TLS client hello we might need to intercept
  264. if not self.ssl:
  265. client_request = self.client_socket.recv(65536, socket.MSG_PEEK)
  266. if not client_request:
  267. return False
  268. # If a MiTM was attempted discard client_request, we used it
  269. # for establishing a MiTM with the client.
  270. if self._check_for_ssl(client_request):
  271. return not self.closed
  272. client_request = self.client_socket.recv(65536)
  273. if not client_request:
  274. return False
  275. client_request = self.handler.on_request(client_request)
  276. for handler in self.data_handlers:
  277. client_request = handler.on_request(client_request)
  278. if client_request == "":
  279. return not self.closed
  280. self.server_socket.sendall(client_request)
  281. except SSL.Error as e:
  282. self.handler.on_ssl_error(e)
  283. return False
  284. except socket.error:
  285. return False
  286. return not self.closed
  287. def _bridge_server(self):
  288. try:
  289. server_response = self.server_socket.recv(65536)
  290. if not server_response:
  291. return False
  292. server_response = self.handler.on_response(server_response)
  293. for handler in self.data_handlers:
  294. server_response = handler.on_response(server_response)
  295. if server_response == "":
  296. break
  297. self.client_socket.sendall(server_response)
  298. except SSL.Error as e:
  299. self.handler.on_ssl_error(e)
  300. return False
  301. except socket.error:
  302. return False
  303. return not self.closed
  304. def _get_client_remote_name(self):
  305. """Get the addr, port of the what the client thinks is their remote
  306. This is used for blame, so this should correspond to some tcp connection
  307. on the client
  308. """
  309. raise NotImplemented()
  310. def applications(self, cached_only=False):
  311. """Returns the result of nogotofail.mitm.blame.Server.get_applications on demand
  312. with caching to avoid needless delays.
  313. See the docs for nogotofail.mitm.blame.Server.get_applications more information.
  314. """
  315. if not self.app_blame:
  316. return None
  317. if self._applications or cached_only:
  318. return self._applications
  319. addr, port = self._get_client_remote_name()
  320. self._applications = (
  321. self.app_blame.get_applications(
  322. self.client_addr,
  323. self.client_port,
  324. addr,
  325. port))
  326. self.client_info = self.app_blame.clients.get(self.client_addr)
  327. return self._applications
  328. def vuln_notify(self, type):
  329. """Notify the client of the connection that a vulnerability was found.
  330. Arguments:
  331. type: A nogotofail.mitm.util.vuln.* to notify the client of.
  332. Returns if the client was notified successfully.
  333. """
  334. if not self.app_blame:
  335. return False
  336. applications = self.applications()
  337. if applications is None:
  338. return False
  339. client, apps = applications
  340. destination = self.hostname if self.hostname else self.server_addr
  341. return self.app_blame.vuln_notify(
  342. self.client_addr, destination, self.server_port, self.id, type,
  343. apps)
  344. def inject_request(self, request):
  345. """Inject a request to the server.
  346. """
  347. request = self.handler.on_inject_request(request)
  348. for handler in self.data_handlers:
  349. request = handler.on_inject_request(request)
  350. if request == "":
  351. break
  352. self.server_socket.sendall(request)
  353. def inject_response(self, response):
  354. """Inject a response to the client.
  355. """
  356. response = self.handler.on_inject_response(response)
  357. for handler in self.data_handlers:
  358. response = handler.on_inject_response(response)
  359. if response == "":
  360. break
  361. self.client_socket.sendall(response)
  362. class RedirectConnection(BaseConnection):
  363. """Connection based on getting traffic from iptables redirect rules"""
  364. def start(self):
  365. self.server_addr, self.server_port = (
  366. self._get_original_dest(self.client_socket))
  367. self.handler.on_select()
  368. for handler in self.data_handlers:
  369. handler.on_select()
  370. try:
  371. # Python's socket.create_connection will handle the socket family correctly
  372. # based on server_addr
  373. self.server_socket = socket.create_connection((self.server_addr, self.server_port),
  374. BaseConnection.SSL_TIMEOUT)
  375. self.raw_server_socket = self.server_socket
  376. except socket.error:
  377. return False
  378. self.handler.on_establish()
  379. for handler in self.data_handlers:
  380. handler.on_establish()
  381. return True
  382. def _get_original_dest(self, sock):
  383. SO_ORIGINAL_DST = 80
  384. dst = sock.getsockopt(socket.SOL_IP, SO_ORIGINAL_DST, 28)
  385. family = struct.unpack_from("H", dst)[0]
  386. # Parse the raw_ip and raw port from the struct sockaddr_in/in6
  387. if family == socket.AF_INET:
  388. raw_port, raw_ip = struct.unpack_from("!2xH4s", dst)
  389. elif family == socket.AF_INET6:
  390. raw_port, raw_ip = struct.unpack_from("!2xH4x16s", dst)
  391. else:
  392. raise ValueError("Unsupported sa_family_t %d" % family)
  393. return socket.inet_ntop(family, raw_ip), int(raw_port)
  394. def _get_client_remote_name(self):
  395. return self.server_addr, self.server_port
  396. class TproxyConnection(RedirectConnection):
  397. """Connection based on getting traffic from iptables TPROXY"""
  398. @staticmethod
  399. def setup_server_socket(sock):
  400. # Required for Tproxy mode
  401. IP_TRANSPARENT = 19
  402. sock.setsockopt(socket.SOL_IP, IP_TRANSPARENT, 1)
  403. def _get_client_remote_name(self):
  404. return self.server_addr, self.server_port
  405. def _get_original_dest(self, sock):
  406. # In tproxy the socket's name is that of the remote endpoint
  407. return sock.getsockname()[:2]
  408. class SocksConnection(BaseConnection):
  409. """Connection that acts as a socks proxy for connection setup"""
  410. SOCKS_CONNECT = 0x01
  411. ATYPE_IP = 0x01
  412. ATYPE_DNS = 0x03
  413. ATYPE_IP6 = 0x04
  414. RESP_SUCCESS = 0x00
  415. RESP_GENERAL_ERROR = 0x01
  416. RESP_NETWORK_UNREACHABLE = 0x03
  417. RESP_COMMAND_UNSUPPORTED = 0x07
  418. def _get_client_remote_name(self):
  419. return self.client_remote_addr, self.client_remote_port
  420. def start(self):
  421. # Save the remote used for blaming
  422. self.client_remote_addr, self.client_remote_port = self.client_socket.getsockname()[:2]
  423. # Do the handshake to get the destination
  424. self.client_socket.settimeout(1)
  425. try:
  426. self.server_addr, self.server_port = (
  427. self._get_original_dest(self.client_socket))
  428. except (ValueError, struct.error, socket.error) as e:
  429. self.client_socket.close()
  430. return False
  431. self.handler.on_select()
  432. for handler in self.data_handlers:
  433. handler.on_select()
  434. # Try and connect to the endpoint
  435. try:
  436. self.server_socket = socket.create_connection((self.server_addr, self.server_port),
  437. BaseConnection.SSL_TIMEOUT)
  438. self.raw_server_socket = self.server_socket
  439. except socket.error:
  440. # Send a generic connection error and bail
  441. self.client_socket.sendall(self._build_error_response(
  442. SocksConnection.RESP_NETWORK_UNREACHABLE))
  443. self.client_socket.close()
  444. return False
  445. # Send the OK message
  446. self.client_socket.sendall(self._build_response())
  447. # At this point the connection is ready to go
  448. self.handler.on_establish()
  449. for handler in self.data_handlers:
  450. handler.on_establish()
  451. return True
  452. def _build_response(self):
  453. """Build the OK SOCKS5 connection response"""
  454. addr, port = self.client_socket.getsockname()[:2]
  455. family = self.client_socket.family
  456. addr_str = socket.inet_pton(family, addr)
  457. if family == socket.AF_INET:
  458. atype = chr(SocksConnection.ATYPE_IP)
  459. elif family == socket.AF_INET6:
  460. atype = chr(SocksConnection.ATYPE_IP6)
  461. else:
  462. raise ValueError("Bad socket family")
  463. return ("\x05\x00\x00" + atype + addr_str +
  464. struct.pack("!H", port))
  465. def _build_error_response(self, response):
  466. """Build a SOCKS5 error response"""
  467. return "\x05" + chr(response) + "\x00\x01\x00\x00\x00\x00\x00\x00"
  468. def _get_original_dest(self, sock):
  469. """Does the SOCKS5 handshake and returns the address, port of the destination
  470. Can raise a socket.error, ValueError, and struct.error if the other side isn't
  471. speaking SOCKS5 or times out"""
  472. message = sock.recv(1024)
  473. version, nmethods = struct.unpack_from("BB", message)
  474. if version != 0x5:
  475. raise ValueError("Bad version in handshake")
  476. methods = struct.unpack_from("%dB" % nmethods, message, 2)
  477. if len(methods) != nmethods:
  478. raise ValueError("Methods mismatch")
  479. # Ingore methods, we just do unauth'd
  480. sock.sendall("\x05\x00")
  481. request = sock.recv(1024)
  482. ver, cmd, atype = struct.unpack_from("BBxB", request)
  483. if ver != 0x5:
  484. raise ValueError("Bad version in handshake")
  485. if cmd != SocksConnection.SOCKS_CONNECT:
  486. sock.sendall(self._build_error__response(SocksConnection.RESP_COMMAND_UNSUPPORTED))
  487. raise ValueError("Unsupported command")
  488. if atype == SocksConnection.ATYPE_IP:
  489. addr = request[4:8]
  490. addr = socket.inet_ntop(socket.AF_INET, addr)
  491. port = struct.unpack_from("!H", request, 8)[0]
  492. elif atype == SocksConnection.ATYPE_DNS:
  493. length = struct.unpack_from("B", request, 4)
  494. addr = request[5:5 + length]
  495. port = struct.unpack_from("!H", request, 5 + length)[0]
  496. elif atype == SocksConnection.ATYPE_IP6:
  497. addr = request[4:20]
  498. addr = socket.inet_ntop(socket.AF_INET6, addr)
  499. port = struct.unpack_from("!H", request, 20)[0]
  500. else:
  501. sock.sendall(self._build_error_response(SocksConnection.RESP_GENERAL_ERROR))
  502. raise ValueError("Unknown ATYP")
  503. return addr, port