严正声明:本文仅限于技术讨论与学术学习研究之用,严禁用于其他用途(特别是非法用途,比如非授权攻击之类),否则自行承担后果,一切与作者和平台无关,如有发现不妥之处,请及时联系作者和平台
作者:ForrestX386
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
0x00. 前言
我用swaks 发送一封以我们公司CTO为显示发件人(腾讯企业邮箱)的伪造邮件给我的一个同事,邮件的内容就是让这位同事去CTO的办公司一趟,没想到这位同事真的去了,然后一脸懵逼的回来了
恶作剧算是完了,但是这让我开始研究伪造邮件是为什么产生的,腾讯企业邮为什么没有拦截
0x01. 关于伪造邮件的一些概念
1) 邮件服务商之间转发邮件是不需要认证的,也就是说MTA 到MTA之间转发邮件是不需要认证的,这是SMTP协议本身定义的。 所以协议钓鱼邮件就可以伪称来自某某MTA发送钓鱼邮件
2) 互联网上有一些邮件域名没有配置SPF记录 或者SPF记录值设置不当,就会被用作伪造邮件的mail_from 后缀域名
比如xxx.com
3) 我们平常必须登录才能发送邮件(一般用的发邮件工具称作MUA,比如foxmail等),这是因为邮件服务商人为要求的,这不是SMTP协议本身要求的,SMTP协议本身是不需要身份认证的
4) mail_from 和from 的区别
mail_from: 是信封上的发件人,由[前缀@域名]组成,是实际发件人
from: 信封内容里的发件人。 也就是我们平时
如果mail_from (实际发件人) 和 from (宣称的发件人) 不一致,则收到的邮件会显示 本邮件由<实际发件人>代发,以提醒收件人两者的不同
有的ESP(邮件服务商)并不会要求mail_from 和from完全一致,而只是要求两者的域名相同(比如QQ 邮箱 和Gmail邮箱)
下面是Gmail邮箱收到的一封<码农周刊>发送的邮件,mail_from 和from 不完全一致, 但没有提示代发
是调用sendCloud 的API 进行发件的,由于SendCloud 对mail_from 的前缀(@前面的)用的是随机字符串,所以遇到严苛的ESP(mail_from 和from 必须完全一致才不显示代发,比如网易邮箱), 那就爱莫能助了
5) 一个腾讯企业邮特殊的例子
这是一封腾讯企业邮的收到的伪造邮件(mail_from 和from不一致), mail_from 是xxx@xxx.com from是xxx@xxx.cn
mail_from 和from 的后缀中就cn 和com 不同,也就是说只有顶级域名不同,其他相同
这样腾讯企业有竟然没有代发提示、安全提示,正常的出现在了我的收件箱中, 不管mail_from 中后缀xxx.com 的SPF是不是OK,
也不管xxx.com是不是存在
腾讯企业邮支持将邮件原始内容导出成eml文件(可用文本编辑器编辑、查看)
而另一封我伪造的一封邮件实际发件人是 service@htouhui.com, 显示发件人是xxx@xxx.cn ,收件人是 xxxx@xxx.cn
显然mail_from 和from不一致,这里腾讯企业邮是会提示你代发
比对两个伪造邮件,我据此反馈给了腾讯企业邮开发组,我觉得是腾讯企业邮的BUG,截止到本篇文章发表时一周前,腾讯企业邮给我的回复是:邮件相关策略有问题,还在优化中
6)reply-to: 信件回复的收件人, 用户直接回复邮件时,reply-to就是默认收件人。 如果用户不指定它, from就是默认收件人
7) mail_to 和 to的区别
mail_to 是实际收件人(信封上的收件人), 而 to 是显示收件人(即信封内容中的收件人)
to 也是可以伪造的(to 支持别名显示,别名也是可以伪造的),类似于from
这是一封伪造邮件,to 也被伪造了
0x02. 关于防止垃圾邮件的两种技术
1、SPF
关于SPF的概念:
1) SPF的配置
SPF 其实就是一条DNS的TXT的记录,其记录值就是 SPF的内容 比如:v=spf1 include:spf.mail.qq.com -all”
SPF 需要在域名解析服务器上配置,比如说是国内常用的DNSPOD配置如下:
比如说service@xxx.com 这封邮件的SPF 记录怎么设置,那么需要在二级域名xxx.com下增加一个主机记录为@, 记录类型为TXT, 记录值为v=spf1 include:spf.mail.qq.com ~all (记录值格式是这样,具体值可能有所不同)
如果收到的邮件格式是这样的: service@mail.vpgame.net ,那么SPF 记录需要这样设置
在二级域名vpgame.net配置如下:
主机记录为mail ,记录类型为TXT,记录值为:v=spf1 include:spf.sendcloud.org -all
2)查询邮件域的SPF:
windows :
nslookup -qt=txt xxx.com
Linux:
dig -t txt xxx.com
2、DKIM
国外用的比较多,国内不多,比如腾讯邮箱默认就不支持这个
下图是一封腾讯企业邮发送到Gmail邮箱的邮件部分原始邮件信息:
可以看到并没有DKIM签名
而Gmail默认是有DKIM签名的
下图是一封Gmail邮箱发送到腾讯企业的邮件部分原始邮件信息:
可以看到是有DKIM签名的。
1)关于DKIM的概念
DKIM全称叫”Domain Key Identified Mail”,是yahoo的domainkey技术跟cisco的identified mail合起来的产物,有标准rfc4871、
rfc5762,它的目的主要用来保证邮件的完整性,避免钓鱼。与SPF一样也做Sender authentication,但DKIM做的比SPF更复杂,DKIM会对邮件头
及正文进行签名,没有私钥下,邮件被假冒或篡改后,就会与邮件头签名不一致,从而防止这样的情况。
DKIM签名是先对内容(BODY)部分HASH,然后把这个BODY HASH放到HEADER里面,再对头部做签名。头部也不是所有字段都要签名,只有一些常用的字段,或者比较有意义的。像Received、Return-path、Bcc、Resent-bcc、DKIM-Signature、Comments、Keywords这样的字段一般不签名,FROM则是必须被签名(rfc4871 5.5 Recommended Signature Content), 最后在邮件头中增加一个DKIM-Signature头用于记录签名信息。
接收方则通过DNS查询得到公开密钥后进行验证, 验证不通过,则认为是垃圾邮件,所以DKIM不仅仅可以防止垃圾邮件,还可以防止邮件内容被篡改
简单来说,DKIM(DomainKeys Identified Mail)是一种电子邮件的验证技术,使用密码学的基础提供了签名与验证的功能。
一般来说,发送方会在电子邮件的标头插入DKIM-Signature及电子签名信息。而接收方则通过DNS查询得到公开密钥后进行验证。
2)邮件域的DKIM配置和查询
邮件接收方通过DNS查询得到公开密钥后进行验证所以说需要在DNS域名解析上中加上一个TXT的记录,用来记录DKIM的公钥信息, 以DNSPOD为例 ,类似SPF记录
以service@mail.vpgame.net为例
在主机记录中写入 mail._domainkey.mail (这里的第一个mail为DKIM中域名的selector,可以修改为不同的值,一个域名可以有多个selector,这样不同的Email server可以有不同的key), 记录类型为TXT, 记录值为:
v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmMPX+sFtBSSBaQENMXIY0kMoU xwpjsktTkjlsrdErh8WKSdRqNEZCE7e5/i9qT/rot5WikkyLoO9nWactl5u5rXli Nqy4eGq3aSQAo0J1/prrL9ZP/NWVo2j6lcSgkMgVCdw7gSIxObfvmp6PIb4edNzP nRBnpjey8xWFTDBzvQIDAQAB
格式类似这样,可能具体的公钥信息不一致, 其中v表示DKIM的版本; k表示非对称的加密算法; p表示base64之后的公钥信息
如何查询邮件域的DKIM 公钥:
windows:
nslookup -qt=txt mail._domainkey.mail.vpgame.net
第一个mail 是上面所说的邮件域的selector,_domainkey 是固定格式(DKIM就是基于domainkeys的技术发展而来), mail.vpgame.net 是邮件域
Linux:
dig -t txt mail._domainkey.mail.vpgame.net
补充一个gmail的:
3)DKIM签名信息分析
这是一封Gmail发给我的腾讯企业邮箱的邮件:
我们看一下DKIM-Signature的内容:
其中,v表示DKIM的版本,
a=rsa-sha1,表示算法(algorithm)。有rsa-sha1和rsa-sha256两种,
c=relaxed/relaxed,表示标准化方法(Canonicalization),头部和内容都用的relaxed方法。还可以用simple,表示不能有任何改动,包括空格.
d=gmail.com,发送者的域名, 也就是Gmail收到邮件信息中的所谓的”署名域”, 这个”署名域”需要在邮件服务器的DKIM设置中配置的,可以和邮件域(比如service@mail.vpgame.net @后面的即是邮件域)不一样(一般都保持一样)
s=20161025,表示域名的selector,通过这个selector,可以允许一个域名有多个public key,这样不同的server可以有不同的key。
h=…,是header list,表示对HEADER中有哪些字段签名。
bh=…,是body hash。也就是内容的hash。
b=…,是header的签名。也就是把h=那个里面所有的字段及其值都取出来,外加DKIM-signature这个头(除了b=这个值,因为还不存在),一起hash一下,然后用rsa加密。
0x03. 关于国内有名的sendCloud配置注意事项
1、发件域和显示发件人(from)的邮件域(@后面的部分) 不一致导致的代发提示
ESP(邮件服务商)在收到邮件的时候都会检查mail_from 和from 的邮件域(@后面的部分)是否一致,不一致则提示邮件代发
gmail也是这样处理
如果你在sendCloud上配置的发件域和邮件显示的发件人的邮件域不一致,则会在gmail邮箱中显示邮件代发
实际发件域是mail.vpgame.net,而显示的发件人的邮件域是mail.vpgame.cn ,两者不一致,Gmail提示代发
下图是一封码农周刊发送到我Gmail邮箱中的一封邮件, 没有提示代发,因为实际发件人的邮件域是和显示发件人的邮件域是一致的
2、使用非加密端口发送代发邮件
比如上面的mail.vpgame.net 代发的一封邮件就是被显示没有加密,可能是直接调用sendCloud的未加密端口发送的
这里显示sendCloud.org未加密这封邮件, 因为gmail是从sendCloud 收到这封邮件的
0x04. 关于使用foxmail代发邮件
1. foxmail 可以配置显示其他账户(由本邮件代发显示邮件账号)
2. 用上图的配置给自己(上图的实际账号)发封邮件
这里会显示代发
3. 如果是微信收到邮件呢(腾讯企业邮箱绑定微信后,微信可收信)
不注意看,还真以为是显示的发件人发的邮件呢
4. 给Gmail 也发一封
Gmail 也没提示代发
但是我们查看Gmail的原始邮件,可以看到此邮件不是显示发件人发的
5. 我们来看回复此邮件能不能看到猫腻
Gmail的回复, 回复给了显示发件人
fomail的回复,也是回复给了显示收件人
foxmail的快速回复, 回复给了实际发件人
注: 如果是回复全部,则包含实际发件人
0x05. 一些识别伪造邮件的小技巧
1、实际发件人与显示发件人不一致
这时候就需要小心了,确认邮件真的是由合法的第三方代发的,比如有名的邮件代发服务商sendCloud,如果不是,一般都是伪造邮件
如何知道邮件的实际发件人?
一般是查看邮件的原始内容,不过还有一个小技巧,就是在收到邮件的时候,邮箱提示信息中显示的就是实际发件人
当然也可以尝试回复一下邮件,这样真实发件人就知道了,对比一下和显示的发件人是否一致,不一致就要小心了
2、一般正常的发件服务器都会配置SPF,有的还会配置DKIM,如果收到的邮件的发件人的邮件域没有配置SPF,则有可能是伪造邮件
3、一般邮件服务商都会有相应的反垃圾邮件的机制,对于有安全提示的邮件要小心,不要轻易相信,不要轻易点击其中图片、链接、附件
如上图,都是伪造邮件,而且显示是收件人也是伪造的
0x06. 补充
腾讯企业邮发送的邮件默认是加密的
一般邮件body 内容是base64-utf8 编码后的结果,可以使用k8-web 编码转换工具解码或者编码
邮件中的邮件头的from 或者 to 部分都支持中文别名显示(subject也支持中文),这些就需要写代码将中文内容编码一下, 以下是实现代码(python)
#!/usr/bin/env python
# -*- coding:utf8 -*-
import sys
from email.header import make_header
if __name__ == '__main__':
reload(sys)
sys.setdefaultencoding('utf8')
content = repr('访问下邮件中的链接,看看不能访问')
print make_header([('xe8xaexbfxe9x97xaexe4xb8x8bxe9x82xaexe4xbbxb6xe4xb8xadxe7x9ax84xe9x93xbexe6x8exa5xefxbcx8cxe7x9cx8bxe7x9cx8bxe4xb8x8dxe8x83xbdxe8xaexbfxe9x97xae', 'utf-8')]).encode()
比如说自己构造邮件原始内容(不是调用某某库哦)的时候想把subject 内容修改一下,则需要先用repr 将中文的16进制编码内容传入make_header的参数中,这种得到的结果就是邮件subject(中文)原始内容
这里要注意一下,不能直接将content传入make_header中,否则会出错,而是先打印repr(‘subject中文内容’)值,然后将其拷贝至make_header中