本文介绍了使用STARTTLS从Python发送电子邮件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过使用Python的smtplib发送带有Python脚本的电子邮件.

I want to send emails with a Python script by using Python's smtplib.

如果可以建立到服务器的加密连接,则脚本仅应发送电子邮件.要加密与端口587的连接,我想使用STARTTLS.

The script should only send the email, if an encrypted connection to the server can be established.To encrypt the connection to port 587 I want to use STARTTLS.

使用一些示例,我编写了以下代码:

Using some examples I have written the following code:

smtp_server = smtplib.SMTP(host, port=port)
context = ssl.create_default_context()
smtp_server.starttls(context)
smtp_server.login(user, password)
smtp_server.send_message(msg)

msg,主机,端口,用户,密码是我的脚本中的变量.我有两个问题:

msg, host, port, user, password are variables in my script.I have two questions:

  • 该连接是始终加密的还是容易受到STRIPTLS攻击( https://en.wikipedia.org/wiki/STARTTLS ).
  • 我应该使用SMTP对象的ehlo()方法吗?在某些示例中,在调用starttls()之前和之后都对其进行显式调用.在smptlib文档的另一面中,如果需要的话,sendmail()会调用它.

@tintin解释说, ssl.create_default_context()可能会导致不安全的连接.因此,我通过以下示例使用一些示例更改了代码:

@tintin explained, that ssl.create_default_context() can possibly lead to insecure connections. Thus I have changed the code using some examples in the following way:

_DEFAULT_CIPHERS = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
'!eNULL:!MD5')

smtp_server = smtplib.SMTP(host, port=port)

# only TLSv1 or higher
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3

context.set_ciphers(_DEFAULT_CIPHERS)
context.set_default_verify_paths()
context.verify_mode = ssl.CERT_REQUIRED

if smtp_server.starttls(context=context)[0] != 220:
    return False # cancel if connection is not encrypted
smtp_server.login(user, password)

对于密码设置,我使用了 ssl.create_default_context()的最新版本的一些代码.这些设置合适吗?

For the cipher setting I used some code of a recent version of ssl.create_default_context(). Are these settings appropriate?

注意:在我原始问题的代码中有一个错误.这是有关行的正确版本:

Note: In the code of my original question is one mistake. Here is the correct version of the concerned line:

smtp_server.starttls(context=context)

[\ Edit]

推荐答案

长话短说:如果您不检查 .starttls()

long story short: starttls can be stripped from smtplib <=py3.5.1rc1 <=py2.7.10 if you do not check response codes for .starttls()

  • 在带有恶意MitM的smtp服务器上显式调用 .starttls(),这会剥离您的 STARTTLS 命令并伪造非 220 响应不会协商ssl,也不会引发异常,因此不会对您的通信进行加密-除非您手动验证对的响应,否则对它进行通信容易受到剥离攻击.starttls()[0] == 220 或内部的 .sock 被ssl包装.

  • explicitly calling .starttls() on smtp servers supporting it with a malicious MitM stripping your STARTTLS command and forging a non 220 response will NOT negotiate ssl, nor raise an exception and therefore leave your communication unencrypted - ergo it is vulnerable to striptls unless you manually verify that the response to .starttls()[0]==220 or the internal .sock got ssl wrapped.

这里是python 2.7.9 smtplib通信,与您的示例类似,该示例通过让服务器或MitM答复 999 NOSTARTTLS 而不是 200 未能协商starttls.没有显式检查客户端脚本中的200响应代码,由于starttls尝试失败而没有异常,因此邮件传输未加密:

Here's a python 2.7.9 smtplib communication with an example similar to yours that failed to negotiate starttls by having the server or a MitM reply 999 NOSTARTTLS instead of the 200. No explicit check for the 200 response code in the client script, no exception due to a failed starttls attempt therefore mail transport not encrypted:

220 xx ESMTP
250-xx
250-SIZE 20480000
250-AUTH LOGIN
250-STARTTLS
250 HELP
STARTTLS
999 NOSTARTTLS
mail FROM:<[email protected]> size=686
250 OK
rcpt TO:<[email protected]>
250 OK
data

  • 在不支持STARTTLS的smtp服务器上明确调用 .starttls()-或从服务器响应中剥离此功能的MitM-将引发 SMTPNotSupportedError .参见下面的代码.

  • explicitly calling .starttls() on smtp servers not supporting STARTTLS - or a MitM stripping this capability from the servers response - will raise SMTPNotSupportedError. see code below.

    一般说明:加密还取决于配置的密码规范,即您的SSLContext(在您的情况下是由 ssl.create_default_context()创建的).请注意,将SSLContext配置为允许进行身份验证但不加密的密码规范(如果由服务器和客户端共同提供/允许)是完全有效的.例如. TLS_RSA_WITH_NULL_SHA256 .

    general note: encryption also depends on the configured cipherspec i.e. your SSLContext which in your case is created by ssl.create_default_context(). Note that it is totally valid to configure your SSLContext to allow cipherspecs that authenticate but do not encrypt (if offered/allowed by both server and client). E.g. TLS_RSA_WITH_NULL_SHA256.

    NULL-SHA256 TLSv1.2 Kx = RSA Au = RSA Enc = None Mac = SHA256

    NULL-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=None Mac=SHA256

    根据此答案 python pre 2.7.9/3.4.3 会尝试对默认的ssl上下文强制执行证书验证,因此很容易受到ssl拦截的攻击.从Python开始, 2.7.9/3.4.3 对默认上下文强制执行证书验证.这也意味着,您必须为 2.7.9/3.4.3之前的版本手动启用证书验证(通过创建自定义sslcontext),否则可能会接受任何不受信任的证书.

    According to this answer python pre 2.7.9/3.4.3 does NOT attempt to enforce certificate validation for the default ssl context and therefore is vulnerable to ssl interception. Starting with Python 2.7.9/3.4.3 certificate validation is enforced for the default context. This also means, that you'll have to manually enable certificate validation for pre 2.7.9/3.4.3 (by creating a custom sslcontext) otherwise any untrusted certificate might be accepted.

    • .sendmail() .send_message .starttls()将隐式调用 .ehlo_or_helo_if_needed()因此,无需再次显式调用它.这也是
      • .sendmail(), .send_message and .starttls() will implicitly call .ehlo_or_helo_if_needed() therefore there is no need to explicitly call it again. This is also
      • 请参见 source :: smtplib :: starttls(cpython,非正式github)下方:

        def starttls(self, keyfile=None, certfile=None, context=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 SMTPNotSupportedError(
                    "STARTTLS extension not supported by server.")
            (resp, reply) = self.docmd("STARTTLS")
            if resp == 220:
                if not _have_ssl:
                    raise RuntimeError("No SSL support included in this Python")
                if context is not None and keyfile is not None:
                    raise ValueError("context and keyfile arguments are mutually "
                                     "exclusive")
                if context is not None and certfile is not None:
                    raise ValueError("context and certfile arguments are mutually "
                                     "exclusive")
                if context is None:
                    context = ssl._create_stdlib_context(certfile=certfile,
                                                         keyfile=keyfile)
                self.sock = context.wrap_socket(self.sock,
                                                server_hostname=self._host)
                self.file = None
                # 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)
        

        这篇关于使用STARTTLS从Python发送电子邮件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

  • 07-05 01:28