-
-
Notifications
You must be signed in to change notification settings - Fork 30.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable TLS certificate validation by default for SMTP/IMAP/FTP/POP/NNTP protocols #91826
Comments
Another data point: Based on some searches (IMAP, SMTP), almost nobody seems to pass a context or certificate. This includes various high-profle projects such as:
Are all those projects aware that they are vulnerable to MitM attacks for their mails? I somewhat doubt it. But the problem is so widespread I don't even know where to start with reporting this to affected projects. |
This is a really widespread and misunderstood problem. It reminds me of Operation ORCHESTRA (a satire) where defaults are so opaque so that they intentionally undermine security. Thank you for your extensive research @The-Compiler. To me, it's clear that this is a problem best fixed in python, because it's a reasonable thing for clients to assume that enabling SSL makes them secure. We shouldn't need such extensive research to convince anyone to do something about it. But if that's what it takes, thank you for laying it all out. I hope it moves the needle. |
Here's a workaround to be run before your app's
|
Correction: Python does neither verify certificate nor hostname for SMTP, IMAP, and so on. Applications are subject to man-in-the-middle attacks for these protocols unless they pass an explicit context.
The recommended workaround is:
or
This enables cert validation and hostname verification for all stdlib modules. I'll discuss the matter with the other core devs at PyCon next week. |
If this change breaks an application, what is the easiest way to opt-out and get back the old behavior: don't check anything?
I'm not sure that we can treat all protocols the same way. I suggest to look for statistics on TLS usage of each protocol, especially look if x509 certs are usually checked for these protocols. For example, I expect that SMTP and IMAP check x509 certs, since these protocols are widely used, security matters and spam is a major issue. I see FTP and NNTP as legacy protocols, I expect them to be used on cheap hardware with weak security. For POP3, I have no idea. Anyway, for me the most important is to properly document how to opt-out to keep access to servers which use invalid x509 certs (self signed, outdated, etc.) |
I'm strongly against doing yet another incomplete switch to TLS cert validation. If we are going to switch, then the entire stdlib should verify certificates correctly. The easiest and best way to solve broken applications is to get proper TLS certificates for your servers. Right now your application would be broken anyway. The change would turn the silent error into a loud error. |
Another question is if we do the change only in Python 3.11 (or 3.12), or also in all supported Python versions (currently: 3.7, 3.8, 3.9, 3.10): https://devguide.python.org/#status-of-python-branches I would prefer to only do the switch in the most recent Python version, and maybe provide an option to opt-in in previous Python versions. |
Since this change is going to impact many users, maybe a PEP would be helpful to properly announce the change and communicate the rationale to (impacted) users. |
I only plan to address the issue in 3.11. Applications can opt-in already by passing a SSLContext object. |
That hardware would then typically use plain FTP and NNTP (i.e. without TLS), no? If someone explicitly opts in to using the secure variant (i.e. FTPS, NNTPS), given that usage is somewhat exotic¹, I think it's fine to expect them to have a proper certificate. ¹ Note that FTPS (FTP with TLS) != the probably more commonly used SFTP (SSH file transfer protocol)
I can try writing one if nobody beats me to it, but I have a lot on my plate with my own projects currently, so I'm not sure if I can get around to it anytime soon. If someone else wants to write one, feel free to use the information from my posts above.
Fair. As evident above, very few projects are apparently aware of this, though. Perhaps the other versions should at least have a big visible warning in the docs that the default behaviour is only marginally better than no encryption at all? E.g. the
which then mentions:
yet, evidently, not enough people actually seem to be reading that (or just not reading docs at all...). |
Feature freeze for 3.11 is in 11 days and I will be traveling around PyCon most of the time. I won't have time to write a PEP. |
Would it help to get this into 3.11 still if I had a quick PEP cooked up (based on my opening post here) ~tomorrow or so? |
In terms of development cycle, I would prefer to make this change at the beginning of the 3.12 development cycle, to have longer time to test it. Is there any urgency to change the default? The latest major change (PEP 476) was in 2014: 8 years ago. |
Given the impact the current defaults seem to have on applications using Python, I see this as a security issue rather than a new feature (the only problem being that it's backwards incompatible). It seems to me that postponing this by a year (?) results in a year more of insecure application code, which is bad even if things were that way for 8 years now. Sure, best practices around TLS security changed in those 8 years, but nowadays, it really leaves a sour aftertaste (and insecure downstream code). |
Thank you, this is a much better workaround than my attempt. You can see why I didn't notice it, thought, seeing as it involves a private function.
I'm confused, why does using
Thank you for taking this to heart :) |
Thanks to some help by @mzollin I was able to gather some data for public IMAP/SMTP servers using shodan: IMAP
Thus, around 98% encrypted, but of those, 13% expired and 30% probably self-signed. SMTP
So only 56% encrypted, of those, 12% expired and 35% probably self-signed. HTTP
So around 30% encrypted (huh?), and of those, 9% expired and 24% probably self-signed. Based on the history of CVEs above, I still believe it would be good for Python to verify those by default, however. I've now also contacted the projects listed earlier, and at least Electrum has already pushed a fix: spesmilo/electrum@cac4b6f |
Odoo has replied with a valuable perspective on this from the POV of someone who'd rather not have that change. Here is it quoted in full, with permission:
|
To follow this ticket |
Changing the default is always complicated. If someone wants to change the default, I suggest to propose a migration plan. Example:
The migration can be different for each protocol depending on the feedback, on how it goes. |
Preventing users to access their IMAP server because of security can be dismissive and people may stick to an old Python version or use a different programming language, if there is no good doc and no easy way to opt-out from cert validation. |
@Neustradamus Note you can just click the "Subscribe" button in the sidebar of an issue: Contrary to a comment, this won't send notifications to thousands of people. @vstinner I definitely agree that it's a double-edged sword and it's not as easy as "just change a single value". But I think a loud failure without security impact is probably still better than a silent failure with security impact, all things considered. Would be interesting to see what other programming languages do by default, in fact. |
We tried to get this problem fixed in some projects that were doing it wrong. Apart from that, mainly repeating in our blog post what @The-Compiler already said here: https://www.pentagrid.ch/en/blog/python-mail-libraries-certificate-verification/ |
If someone wants to lead an initiative to change the default, I suggest writing down a PEP, I can be your sponsor for that. |
@vstinner Okay. I never wrote a PEP before, but why not give it a try: python/peps#3537 |
I see many names in this issue. It would be nice if it could be a collaborative work. @The-Compiler: You created the issue, do you want to be part of this adventure? |
The Python docs say:¹ _ssl_context_ is a `ssl.SSLContext` object which allows bundling SSL configuration options, certificates and private keys into a single (potentially long-lived) structure. Please read Security considerations for best practices. … For client use, if you don’t have any special requirements for your security policy, it is highly recommended that you use the `create_default_context()` function to create your SSL context. It will load the system’s trusted CA certificates, enable certificate validation and hostname checking, and try to choose reasonably secure protocol and cipher settings. … By contrast, if you create the SSL context by calling the `SSLContext` constructor yourself, it will not have certificate validation nor hostname checking enabled by default. While this is clear, it is counter-intuitive behaviour of which I was unaware. I only learned of this through an oss-sec posting.² This issue seems to have a long history and we are not the only software affected by it.³ ¹ https://docs.python.org/3/library/imaplib.html#imaplib.IMAP4_SSL ² https://www.openwall.com/lists/oss-security/2024/02/01/4 ³ python/cpython#91826, https://peps.python.org/pep-0476/, python/cpython#91875, https://www.pentagrid.ch/en/blog/python-mail-libraries-certificate-verification/, python/peps#3537
Feature or enhancement
I was surprised that Python does not verify hostnames by default for the stdlib modules for SMTP, IMAP, FTP, POP and NNTP. I believe the "insecure by default" behavior is no longer appropriate even for those protocol (at least SMTP and IMAP, I'm not too familiar with the rest - but even there, I suppose if an user asks for a secure connection, it should be secure).
In PEP 476 (Enabling certificate verification by default for stdlib http clients, 2014), certificate verification was enabled by default for HTTPS, with the rationale that:
and
and
That PEP only improved that situation for HTTPS, stating that:
Unfortunately, it seems to be very difficult to find more recent data about how many SMTP/IMAP/... servers in the wild present an invalid certificate - all I could find is that according to Google, adoption of outgoing encryption in general went up from ~75% when the PEP was written to ~90% now, and inbound encryption went up from ~57% to ~87%.
However, there seems to be a strong consensus to treat this kind of thing as a vulnerability in clients, even as early as 2007. Some examples:
and more recently:
Previous discussion
Linked PRs
The text was updated successfully, but these errors were encountered: