Exploit-DB #43500
Description: Python smtplib 2.7.11 / 3.4.4 / 3.5.1 - Man In The Middle StartTLS Stripping
Type: local
Platform: multiple
Date: 2016-07-03
Author: tintinweb

Exploit:
VuNote
============

	Author:		<github.com/tintinweb>
	Version: 	0.2
	Date: 		Nov 25th, 2015
	
	Tag:		python smtplib starttls stripping (mitm)

Overview
--------

	Name:			python 
	Vendor:			python software foundation
	References:		* https://www.python.org/ [1]
	
	Version:		2.7.11, 3.4.4, 3.5.1
	Latest Version:	2.7.11, 3.4.4, 3.5.1 [2]
	Other Versions:	2.2 [3] (~14 years ago) <= affected <= 2.7.11
					3.0 [3] (~7  years ago) <= affected <= 3.4.4
														   3.5.1
	Platform(s):	cross
	Technology:		c/python

	Vuln Classes:	Selection of Less-Secure Algorithm During Negotiation (CWE-757)
	Origin:			remote/mitm
	Min. Privs.:	-

	CVE:			CVE-2016-0772


Description
---------

quote wikipedia [4]

>Python is a widely used high-level, general-purpose, interpreted, dynamic programming language. Its design philosophy emphasizes code readability, and its syntax allows programmers to express concepts in fewer lines of code than would be possible in languages such as C++ or Java.[24][25] The language provides constructs intended to enable clear programs on both a small and large scale.


Summary 
-------

python smtplib does not seem to raise an exception when the remote 
end (smtp server) is capable of negotiating starttls (as seen in the 
response to ehlo) but fails to respond with 220 (ok) to an explicit 
call of `SMTP.starttls()`. This may allow a malicious mitm to perform a 
starttls stripping attack if the client code does not explicitly check 
the response code for starttls, which is rarely done as one might 
expect that it raises an exception when starttls negotiation fails 
(like when calling starttls on a server that does not support it or 
when it fails to negotiate tls due to an ssl exception/cipher 
mismatch/auth fail).

Quoting the PSRT with an extended analysis

> It is a surprising and potential dangerous behavior. It also violates Python's documentation. states that all SMTP commands after starttls() are encrypted. That's clearly not true in case of response != 200. I also had a look how the other stdlib libraries handle starttls problems. nntplib's and imaplib's starttls() method raise an error when the starttls handshake fails.

Checking on how `smtplib.starttls()` is actually being used by open-source projects underlines that `smtplib.starttls()` is generally expected to throw an exception if the starttls protocol was not executed correctly. Therefore this issue may have an impact on some major projects like Django, web2py. Apart from that the current `smtplib.starttls()` behavior is different to `nntplib.starttls()`, `imaplib.starttls()`

PoC see [6]
patch attached.

Details
------

The vulnerable code is located in `lib/smtplib.py` [3] line 646 (2.7 branch) and 
fails to raise an exception if `resp!=220`.

The documentation [7] suggests that `starttls()` either encrypts all communication
or throws an exception if it was not able to negotiate tls.

	SMTP.starttls([keyfile[, certfile]])
	Put the SMTP connection in TLS (Transport Layer Security) mode. All SMTP commands that follow will be encrypted. You should then call ehlo() again.
	
	If keyfile and certfile are provided, these are passed to the socket module�s ssl() function.
	
	If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first.
	
	Changed in version 2.6.
	
	SMTPHeloError
	The server didn�t reply properly to the HELO greeting.
	SMTPException
	The server does not support the STARTTLS extension.
	Changed in version 2.6.
	
	RuntimeError
	SSL/TLS support is not available to your Python interpreter.
	

Code `lib/smtplib.py`:

Inline annotations are prefixed with `//#!`

	    def starttls(self, keyfile=None, certfile=None):
	        """Puts the connection to the SMTP server into TLS mode.
	        If there has been no previous EHLO or HELO command this session, this
	        method tries ESMTP EHLO first.
	        If the server supports TLS, this will encrypt the rest of the SMTP
	        session. If you provide the keyfile and certfile parameters,
	        the identity of the SMTP server and client can be checked. This,
	        however, depends on whether the socket module really checks the
	        certificates.
	        This method may raise the following exceptions:
	         SMTPHeloError            The server didn't reply properly to
	                                  the helo greeting.
	        """
	        self.ehlo_or_helo_if_needed()
	        if not self.has_extn("starttls"):
	            raise SMTPException("STARTTLS extension not supported by server.")
	        (resp, reply) = self.docmd("STARTTLS")
	        if resp == 220:														//#! with a server not responding 220 it wont even try to negotiate tls
	            if not _have_ssl:												//#! silently stays unencrypted
	                raise RuntimeError("No SSL support included in this Python")
	            self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
	            self.file = SSLFakeFile(self.sock)
	            # RFC 3207:
	            # The client MUST discard any knowledge obtained from
	            # the server, such as the list of SMTP service extensions,
	            # which was not obtained from the TLS negotiation itself.
	            self.helo_resp = None
	            self.ehlo_resp = None
	            self.esmtp_features = {}
	            self.does_esmtp = 0
	        return (resp, reply)												//#! to actually detect this a client would have to manually check resp==220
																				//#! or that the socket was turned into an SSLSock object

Proof of Concept
----------------

1. start `striptls.py` proxy

		#> python striptls/striptls.py -l 0.0.0.0:9999 -r remote.mailserver.tld:25 -x SMTP.StripWithInvalidResponseCode
		
		  - INFO     - <Proxy 0x1f04910 listen=('0.0.0.0', 9999) target=('remote.mailserver.tld', 25)> ready.
		  - DEBUG    - * a