java发送邮件的两种实现方式(包括如何伪造发件人及其原理) 您所在的位置:网站首页 发邮件怎么隐藏邮件地址 java发送邮件的两种实现方式(包括如何伪造发件人及其原理)

java发送邮件的两种实现方式(包括如何伪造发件人及其原理)

2024-03-01 11:30| 来源: 网络整理| 查看: 265

                         java发送邮件的两种通用方法

一、

本文讲解的是基于smtp协议,发送邮件的方法(一种是底层实现,一种是利用第三方jar包)。而关于smtp协议,不了解的可以在网上搜一下,有很多资料并且很容易懂;不过不了解也没关系,只需要知道,smtp协议存在一个安全漏洞,就是smtp协议允许你两次设置发件人和收件人信息。第一次发送命令行mail from:真正的发送邮件的源地址 ;第二次则是在发送data命令之后,开始写邮件内容。在写邮件内容时,还能再一次设置发件人、收件人、抄送者等信息(在data里面写的发件人、收件人、抄送人信息,只能显示,其实没有其他作用,比如你在设置收件人的命令里面没有写[email protected]这个邮件地址,但是你在data命令之后,抄送者里面输入了[email protected]这个地址,最后这封邮件并不会发给这个抄送人,只是在邮件的抄送者这一栏里面,有这么一个邮箱账号。所以要真的发送给这些人,只有在最开始设置发件源之后,设置收件源,可以多个)。

顺便说一下笔者最开始写邮件在网上遇到的大坑:笔者写邮件的背景是,利用公司邮箱公共账号(比如公共账号名字是public),将一封邮件发送抄送给一些人,但是要求发件人不能是公共账号,因为一些员工设置的邮件过滤,可能会导致用公共账号名字发送的邮件被直接扔垃圾箱,导致员工看不到邮件,但是利用公共账号发送的邮件,对方接收的时候显示的就是公共账号的名字,即public(PS:修改邮件发件人昵称,并不能修改接收方看到的发件人名字,昵称只提供在邮件正文里面,实际上邮箱显示的发件人还是公共账号的名字,比如你修改发件人昵称为test,其实对方收到的提醒还是public发送的邮件,并不是test发送的邮件,只有对方点开这封邮件,才会在邮件里面看到test这个昵称。),而且,可能是笔者自己的原因,网上那些利用javax.mail包,设置昵称的办法(就是这种:InternetAddress senderEmailAddress = new InternetAddress(nick + "")),笔者这里根本不管用,最后看了很多源码之后,终于把昵称设置好了(这种方法message.setHeader("Sender", "我是昵称")),结果却发现,设置的昵称根本不能伪造发件人,当时笔者心里是非常崩溃的(尼玛,搞了半天,好不容易搞定了昵称,居然发现没有起到想要的效果,最后笔者只有了解smtp协议,然后用Java进行底层实现),所以,笔者要告诉大家的是,使用java封装好的第三方jar包发送邮件,不能伪造发件人,不能伪造,不能伪造,重要的事说三遍,详细的情况在后面会贴一部分源码讲解。

二、基于smtp协议发送邮件(该方法能够伪造任意发件人)

package cn.su.core.util; import java.io.*; import java.net.Socket; import java.util.Base64; /** * @Author: su rui * @Date: 2021/6/15 10:36 * @Description: 伪造邮件发件人工具类 */ public class ForgeEmailSenderUtil { private static final String defaultHost = "smtp.exmail.qq.com"; private static final int defaultPort = 25; private String host = ""; private Integer port = null; private String userName = "[email protected]"; private String password = "xxxxx"; private Socket socket; private BufferedReader bufferedReader; private PrintWriter printWriter; public ForgeEmailSenderUtil setSenderAccount(String userName, String passwordOrAuthCode) { this.userName = userName; this.password = passwordOrAuthCode; return this; } public ForgeEmailSenderUtil setEmailHostAndPort(String host, int port) { this.host = host; this.port = port; return this; } private Socket createSocket() { try { return null == host || host.trim().length() == 0 ? new Socket(defaultHost, defaultPort) : new Socket(host, port); } catch (Exception e) { throw new IllegalArgumentException("创建会话失败,请稍后重试"); } } public void sendForgeSenderEmail(String sender, String recipient, String ccs) { sender = null == sender || sender.trim().length() == 0 ? userName : sender; try { String baseUserName = Base64.getEncoder().encodeToString(userName.getBytes("UTF-8")); String basePassword = Base64.getEncoder().encodeToString(password.getBytes("UTF-8")); this.socket = createSocket(); this.bufferedReader = getReader(socket); this.printWriter = getWriter(socket); writeCommandStream(null); //按照命令行发送邮件的顺序与smtp服务器进行交流 writeCommandStream("helo hello");//与smtp服务器进行对话 writeCommandStream("auth login");//登录命令 //用户名和密码都是用base64进行编码了的,不是普通的字符串 writeCommandStream(baseUserName);//登录用户用户名 writeCommandStream(basePassword);//密码 //登录成功之后,设置发件人 writeCommandStream("mail from:");//设置发件人,xxxxxx为真实的邮件发送源地址,如[email protected]这种邮箱地址 //设置收件人,可以设置多个,所以采用遍历方式进行设置 //参数reciver里面装了所有收件人的邮箱地址,多个邮箱用","号分隔,所以我用逗号拆分 for (String oneReciver : recipient.split(",")) { writeCommandStream("rcpt to:" + oneReciver); } //开始输入邮件内容 writeCommandStream("data");//邮件内容,在输入命令data之后开始 //这个地方就是伪造邮件发件人的时候,from之后的字符串任意填, //填了之后,收到邮件的人,会看到以这个名字发送的邮件,但是他不能回复,因为这个是伪造的地址,无效的。 printWriter.println("from:" + sender); //收件人,格式和抄送者一样 printWriter.println("to:" + recipient); //这是抄送者,同收件人一样,可以设置多个,中间用,号分隔 //比如:[email protected],[email protected],[email protected] printWriter.println("Cc:" + ccs); //设置邮件主题 printWriter.println("subject:" + "这是邮件主题"); //设置邮件正文 //注意下面这个设置类型的,这一句代码是必须的,不然你发的邮件的正文内容是不会存在的 //笔者最开始没有设置邮件正文类型,发了很多封,但是每一封邮件的正文内容都为空,后来才发现必须加上这个 printWriter.println("Content-Type:text/html;");//这个是HTML格式的邮件正文,如果是纯文本,用text/plain //注意这个空行是必须的,设置好了类型,需要空一行再起一行输入正文内容 printWriter.println(); printWriter.println("这是邮件的内容,该邮件是一封HTML格式的邮件,如果要切换邮件格式," + "设置conten-type的值就可以改变,当然还可以加上超链接这是超链接"); printWriter.println(); //结束邮件发送"."命令 writeCommandStream("."); //关闭 writeCommandStream("quit"); } catch (Exception e) { e.printStackTrace(); } finally { try { printWriter.close(); bufferedReader.close(); socket.close(); } catch (Exception e2) { e2.printStackTrace(); } } } private PrintWriter getWriter(Socket socket) throws IOException { OutputStream socketOut = socket.getOutputStream(); return new PrintWriter(socketOut, true); //注意设置为true } private BufferedReader getReader(Socket socket) throws IOException { InputStream socketIn = socket.getInputStream(); return new BufferedReader(new InputStreamReader(socketIn)); } private void writeCommandStream(String command) throws IOException { if (command != null) { printWriter.println(command); printWriter.flush(); System.out.println("客户端命令行信息→" + command); } char[] serviceResponse = new char[1024]; bufferedReader.read(serviceResponse); System.out.println("服务器响应→" + new String(serviceResponse)); } }

三、基于javax.mail包进行邮件发送

就笔者而言,利用该jar包进行邮件发送,没有真正实现伪造发件人,只能设置邮件发件人昵称,之前看网上很多伪造都是设置邮件服务器属性smtp.auth为false,意思就是不对邮件进行用户验证等操作。笔者在设置之后,发送邮件只会提示,作为该发送者没有权限,或者xxxxx权限验证失败等提示。

另外关于设置昵称,网上这种方法其实是不能设置昵称的(也可能是笔者太垃圾,这里只是代表我个人看法,说不定以后我自己也会发现是错,现在就讲讲当时我看源码的理解,因为资源原因,源码以后会陆续贴上)

public void sendEmailByJar(String sender, String recvier, String cc) { //设置邮件服务器参数 Properties props = new Properties(); props.put("mail.smtp.host", host); props.put("mail.smtp.auth", "true"); props.put("mail.transport.protocol", "smtp"); //设置邮件Session对象,同时配置验证方法 //注意这里的Session是javax.mail.session包的Session,利用该Jar包,这个Session是必须的, //关于邮件的一切信息,都是通过这个session进行创建的 Session session = Session.getInstance(props, new javax.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(userName, password); } }); //网上大多数设置昵称的方法,至少笔者使用该方法不管用 String nick = null; try { nick = javax.mail.internet.MimeUtility.encodeText("我是昵称"); } catch (Exception e) { e.printStackTrace(); } try { //创建Message对象,并设置相关参数 InternetAddress senderEmailAddress = new InternetAddress(nick + ""); //设置抄送者,cc参数里面是多个邮箱,用,号分隔 @SuppressWarnings("static-access") InternetAddress[] ccsAddress = new InternetAddress().parse(cc); @SuppressWarnings("static-access") InternetAddress[] reciverAddress = new InternetAddress().parse(recvier); Message message = new MimeMessage(session); //笔者亲测设置邮件发件人昵称的方法,至少笔者设置成功 //顺便讲一下Message对象里面的header属性,笔者调试的时候,发现Message对象header属性保存了我们写的邮件的所有信息 //里面有from,sender,to,cc,subject,content-type(包括resent-to,resent-from等,好像是重发邮件的属性)等属性,目测就是对应邮件的各个信息 //所以,其实邮件的所有信息,我们都可以通过messaget.setHeader("键", "值")来设置 //比如我们调用的设置邮件发件地址的方法setFrom(xxxxx),其实等同于setHeader("From", "xxxxx"), //如果你同时使用了俩个方法setFrom,setHeader("From", "xxx"),那么后一个会覆盖前一个的值 //这里讲一下我理解的为什么网上设置昵称的方法不起作用的原因:网上设置的昵称都是在setFrom()方法里面设置的 //而阅读源码,我们会发现,setFrom里面的值,会被拆分到俩个字段里面保存:personal字段和address字段 //其中,你设置的nick昵称就会被保存在personnal字段,而邮箱地址会被保存在address字段 //同时,你在源码里面也能找到smtp协议的命令行语句mail from这些命令 //源码里面,我只看到了这些必要的命令行:发件人mail from ,接收者rcpt to,正文data,结束. //其中,data源码是用一个流写入的,所以具体写的,怎么解析的我们设置的参数我也没看懂,但是实验证明就是不能伪造发件人 //而mail from,设置的参数的值,是从address字段取的,并没有取你设置的昵称personnal,所以直接设置昵称在from这个header的值是无效的 //rcpt to是从你的收件人里面取的值。 //而笔者成功的昵称设置,是通过设置setHeader("Sender", "xxx")成功的,所以可以猜测,源码解析的时候,取昵称是从这个字段sender里面取的 //那么其实最后jar包源码里面,设置smtp mail from还是设置的邮箱,并没有带上你设置的昵称 //所以笔者认为这个就是使用网上方法设置昵称不管用的原因(笔者的观点,可能会有错,毕竟笔者源码也没有完全看懂) //另外,setFrom()设置的值,必须和登录验证用的用户名和密码的账号匹配,不然就会报权限验证错误,所以这也是笔者认为不能伪造的根本原因 message.setHeader("Sender", "nick"); message.setFrom(senderEmailAddress);//该方法等同于message.setHeader("From","xxx"); message.setRecipients(Message.RecipientType.CC, ccsAddress); message.setRecipients(Message.RecipientType.TO, reciverAddress); message.setSubject("主题"); message.setText("简单文本邮件"); //不管是调用Transport静态方法send,还是通过session获取transport,在链接,在发送,其实都一样,源码已经帮我们处理好了 //如果调用静态方法,源码会获取session对象并用session创建一个transport,如果获取到session对象为null,会创建一个默认的session对象 Transport.send(message); } catch (Exception e) { e.printStackTrace(); } }

这就是笔者总结的两种java实现发邮件的方法了,希望对大家有所帮助,如有错误,望提醒!!!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有