web-dev-qa-db-ja.com

STARTTLSを使用してPythonからメールを送信する

Pythonスクリプトを使用して、Pythonのsmtplibを使用してメールを送信したい。

サーバーへの暗号化された接続を確立できる場合、スクリプトは電子メールを送信するだけです。ポート587への接続を暗号化するには、STARTTLSを使用します。

いくつかの例を使用して、次のコードを記述しました。

_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、Host、port、user、passwordはスクリプトの変数です。 2つの質問があります。

  • 接続は常に暗号化されていますか、それともSTRIPTLS攻撃に対して脆弱ですか( https://en.wikipedia.org/wiki/STARTTLS )。
  • SMTPオブジェクトのehlo()メソッドを使用する必要がありますか?いくつかの例では、starttls()を呼び出す前後に明示的に呼び出されます。一方、smptlibのドキュメントには、必要に応じてsendmail()がそれを呼び出すように記述されています。

[編集]

@tintinは、ssl.create_default_context()が安全でない接続を引き起こす可能性があると説明しました。したがって、次のようにいくつかの例を使用してコードを変更しました。

__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()の最近のバージョンのコードを使用しました。これらの設定は適切ですか?

注:元の質問のコードには1つの間違いがあります。関連する行の正しいバージョンは次のとおりです。

_smtp_server.starttls(context=context)
_

[\ Edit]

8
user5415068

接続は常に暗号化されていますか、それともSTRIPTLS攻撃に対して脆弱ですか( https://en.wikipedia.org/wiki/STARTTLS )。

短い話:.starttls()の応答コードを確認しない場合、starttlsはsmtplib <= py3.5.1rc1 <= py2.7.10から削除できます

  • 悪意のあるMitMがSTARTTLSコマンドを取り除き、220以外の応答を偽造してそれをサポートするSMTPサーバーで明示的に.starttls()を呼び出すと、sslをネゴシエートしません。また、例外を発生させないため、通信を暗号化しないでください-手動で確認しない限り、それを実行するはstriptlsに対して脆弱です.starttls()[0]==220または内部.sockへの応答は、sslでラップされています。

    これはpython 2.7.9 smtplib通信で、サーバーまたはMitMに999 NOSTARTTLSの代わりに200を応答させることでstarttlsのネゴシエーションに失敗した例に似ています。クライアントスクリプトで200応答コードを明示的にチェックせず、starttlsの試行が失敗したことによる例外がないため、メール転送は暗号化されません。

    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が発生します。以下のコードを参照してください。

  • 一般的な注意:暗号化は、構成された暗号仕様、つまり、ssl.create_default_context()によって作成されたSSLContextにも依存します。認証するが暗号化しないcipherspecsを許可するようにSSLContextを構成することは完全に有効であることに注意してください(サーバーとクライアントの両方で提供/許可されている場合)。例えば。 TLS_RSA_WITH_NULL_SHA256

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

  • によれば、この答え pythonpre 2.7.9/3.4.3[〜#〜] not [〜#〜]は、デフォルトのsslコンテキストに対して証明書の検証を強制しようとするため、sslインターセプトに対して脆弱です。 Python2.7.9/3.4.3で始まる証明書の検証は、デフォルトのコンテキストに対して実施されます。これは、pre 2.7.9/3.4.3の証明書検証を手動で有効にする必要があることも意味します(カスタムsslcontextを作成することにより)。信頼されていない証明書が受け入れられる可能性があります。

SMTPオブジェクトのehlo()メソッドを使用する必要がありますか?いくつかの例では、starttls()を呼び出す前後に明示的に呼び出されます。一方、smptlibのドキュメントには、必要に応じてsendmail()がそれを呼び出すように記述されています。

  • .sendmail().send_message、および.starttls()は暗黙的に.ehlo_or_helo_if_needed()を呼び出すため、明示的に再度呼び出す必要はありません。これもまた

以下の source :: smtplib :: starttls(cpython、inofficial 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)
13
tintin