使用PHPMailer和javax.mail发送电子邮件

7

我有两段相似的代码,分别用Java和PHP编写。由于证书错误,PHP无法发送电子邮件 -

  Connection failed. Error #2: stream_socket_enable_crypto():
  Peer certificate CN=*.hosting.com' did not match
  expected CN=smtp.anotherhosting.com'

但是Java代码可以轻松发送电子邮件,我不明白为什么。(从各个方面来看,我都看到了这样的问题 - 如何使用Java跳过SSL检查?
以下是代码:
PHP:
<?php
    require './PHPMailer.php';
    require './SMTP.php';

    use PHPMailer\PHPMailer\PHPMailer;

    $mail = new PHPMailer(true);
    try {
        $mail->SMTPDebug = 4;
        $mail->isSMTP();
        $mail->Host = 'smtp.anotherhosting.com';
        $mail->SMTPAuth = true;
        $mail->Username = 'username@anotherhosting.com';
        $mail->Password = 'password';
        $mail->SMTPSecure = 'tls';
        $mail->Port = 587;

        //Recipients
        $mail->setFrom('from@company.com');
        $mail->addAddress('myemail@company.com');
        $mail->isHTML(true);
        $mail->Subject = 'Here is the subject12';
        $mail->Body    = 'This is the HTML message bo22dy <b>in bold!</b>';

        $mail->send();
        echo 'Message has been sent';
    } catch (Exception $e) {
        echo 'Message could not be sent. Mailer Error: ', $mail->ErrorInfo;
    }
    try {
        $mail->smtpClose();
    } catch (Exception $e) {
        echo $e->getTraceAsString();
    }

和Java:

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

public class Main {
    public static void main(String[] args) {
        final String username = "username@anotherhosting.com";
        final String password = "password";

        Properties props = new Properties();
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.host", "smtp.anotherhosting.com");
        props.put("mail.smtp.port", "587");

        Session session = Session.getInstance(props,
                new javax.mail.Authenticator() {
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(username, password);
                    }
                });

        session.setDebug(true);
        try {

            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress("from@company.com"));
            message.setRecipients(Message.RecipientType.TO,
                    InternetAddress.parse("myemail@company.com"));
            message.setSubject("Testing Subject");
            message.setText("Dear Mail Crawler,"
                    + "\n\n No spam to my email, please!");

            Transport.send(message);

            System.out.println("Done");

        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}

我的任务是使用php代码实现电子邮件发送功能。从我的当前角度来看,它失败了,因为smtp重定向从一个主机到另一个主机。最有可能的情况是,phpmailer获得了host1,接收到从host1重定向到host2,取出host2的证书并将此证书与host1进行比较。同时,Java客户端一切正常。如果有人知道如何解决这个问题,请告诉我。

此外,在尝试调用stream_socket_enable_crypto时,php代码在line 402处失败。

以下是日志文件: java:

DEBUG: setDebug: JavaMail version 1.4ea
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc]
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: useEhlo true, useAuth true
DEBUG SMTP: trying to connect to host "smtp.anotherhosting.com", port 587, isSSL false
220 mailpod.hosting.com ESMTP
DEBUG SMTP: connected to host "smtp.anotherhosting.com", port: 587

EHLO degr [most probably my computer name]
250-mailpod.hosting.com
250-STARTTLS
250-PIPELINING
250-8BITMIME
250-SIZE 65000000
250 AUTH LOGIN PLAIN CRAM-MD5
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "SIZE", arg "65000000"
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN CRAM-MD5"
STARTTLS
220 ready for tls
EHLO degr
250-mailpod.hosting.com
250-PIPELINING
250-8BITMIME
250-SIZE 65000000
250 AUTH LOGIN PLAIN CRAM-MD5
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "SIZE", arg "65000000"
DEBUG SMTP: Found extension "AUTH", arg "LOGIN PLAIN CRAM-MD5"
DEBUG SMTP: Attempt to authenticate
AUTH LOGIN
334 [auth hash here]
[auth hash here]
334 [auth hash here]
[auth hash here]
235 ok, go ahead (#2.0.0)
DEBUG SMTP: use8bit false
MAIL FROM:<from@company.com>
250 ok
RCPT TO:<myemail@company.com>
250 ok
DEBUG SMTP: Verified Addresses
DEBUG SMTP:   myemail@company.com
DATA
354 go ahead
From: from@company.com
To: myemail@company.com
Message-ID: <1338668845.01537892151523.JavaMail.myemail@company.com>
Subject: Testing Subject
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Dear Mail Crawler,

 No spam to my email, please!
.
250 ok 1537892155 qp 173024
QUIT
221 mailpod.hosting.com
Done

PHP:

2018-09-25 16:26:35 Connection: opening to smtp.anotherhosting.com:587, timeout=300, options=array()
2018-09-25 16:26:35 Connection: opened
2018-09-25 16:26:35 SMTP INBOUND: "220 mailpod.hosting.com ESMTP"
2018-09-25 16:26:35 SERVER -> CLIENT: 220 mailpod.hosting.com ESMTP
2018-09-25 16:26:37 CLIENT -> SERVER: EHLO localhost
2018-09-25 16:26:38 SMTP INBOUND: "250-mailpod.hosting.com"
2018-09-25 16:26:38 SMTP INBOUND: "250-STARTTLS"
2018-09-25 16:26:38 SMTP INBOUND: "250-PIPELINING"
2018-09-25 16:26:38 SMTP INBOUND: "250-8BITMIME"
2018-09-25 16:26:38 SMTP INBOUND: "250-SIZE 65000000"
2018-09-25 16:26:38 SMTP INBOUND: "250 AUTH LOGIN PLAIN CRAM-MD5"
2018-09-25 16:26:38 SERVER -> CLIENT: 250-mailpod.hosting.com250-STARTTLS250-PIPELINING250-8BITMIME250-SIZE 65000000250 AUTH LOGIN PLAIN CRAM-MD5
2018-09-25 16:26:38 CLIENT -> SERVER: STARTTLS
2018-09-25 16:26:38 SMTP INBOUND: "220 ready for tls"
2018-09-25 16:26:38 SERVER -> CLIENT: 220 ready for tls
2018-09-25 16:26:38 Connection failed. Error #2: stream_socket_enable_crypto(): Peer certificate CN=*.hosting.com' did not match expected CN=smtp.anotherhosting.com' [C:\project\SMTP.php line 402]
SMTP Error: Could not connect to SMTP host.
2018-09-25 16:26:39 CLIENT -> SERVER: QUIT
2018-09-25 16:26:39 
2018-09-25 16:26:39 
2018-09-25 16:26:39 
2018-09-25 16:26:39 Connection: closed
SMTP Error: Could not connect to SMTP host.
Message could not be sent. Mailer Error: SMTP Error: Could not connect to SMTP host.

PS smpt服务由https://www.networksolutions.com/提供。


问题出在SMTP服务器的证书上。一个适合生产环境使用的SMTP服务器应该有一个有效的证书,而不是一个自签名的证书。 - Raptor
谢谢您的评论,但这实际上是两年前的事了。当时是由第三方公司提供的生产就绪的SMTP服务器,不幸的是我无法记住确切的名称。 - degr
但是错误信息却说了另外一件事,它说证书中的CN名称不匹配。希望问题现在已经解决了。 - Raptor
2个回答

1

没错。因此,你的Java代码存在漏洞,容易受到中间人攻击。在PHP中,它通过执行TLS设计的操作成功地防止了这种攻击。

发生的是ISP防火墙上的TCP重定向,对客户端都不可见。你可以禁用证书检查(如故障排除指南所述),但你真的不应该这样做。要么明确连接到正确的名称(mailpod.hosting.com),要么使用不会篡改你的流量的托管提供商。


这是一个很好的逻辑结论,但我无法相信javax.mail在所有Java安全限制和规则下“默认情况下”包含中间人攻击漏洞。但是,php的stream_socket_enable_crypto是可靠的函数,所以我有点困惑。此外,我尝试将mailpod.hosting.com用作主机,但在两种情况下都遇到了身份验证错误。 - degr
我可以。所有主要的编程语言多年来都存在同样的漏洞,包括Ruby、Python和Java。使用openssl进行检查。是的,更改主机名将修复TLS,但会破坏身份验证——因为您现在正在与错误的服务器进行身份验证,该服务器不知道您是谁。大多数ISP在其内部服务器上不使用身份验证,因此请尝试禁用它。请注意,即使这样成功了,您仍然可能在交付时遇到SPF问题(因为您现在正在从未知来源发送),因此最好的解决方案仍然是使用不篡改流量的ISP。 - Synchro
公平地说,问题并不在于Java邮件组件本身,而是它下面的TLS实现。Java以安全功能落后于其他语言而闻名。 - Synchro
尝试将Session属性mail.smtp.ssl.checkserveridentity设置为true。 - Bill Shannon

0

这可能是您的一个临时解决方案。

$mail->SMTPOptions = array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
        'allow_self_signed' => true
    )
);

您可以通过SMTPOptions属性允许不安全的连接,该属性在PHPMailer 5.2.10中引入(在早期版本中可以通过子类化SMTP类来实现),但不建议这样做,因为这将使使用安全传输的目的大打折扣。

有关SMTPOptions的更多信息,请参见Wiki


谢谢,我知道。它甚至可以与array( 'verify_peer' => true, 'verify_peer_name' => false, 'allow_self_signed' => false )一起工作。但我正在寻找更安全的解决方案,可以保护我免受中间人攻击。不确定是否可能。 - degr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接