一封伪造邮件引发的“探索”(涉及钓鱼邮件、SPF和DKIM等)
作者:admin | 时间:2017-7-7 02:22:11 | 分类:黑客技术 隐藏侧边栏展开侧边栏
0×00. 引言
我用swaks 发送一封以我们公司CTO为显示发件人(腾讯企业邮箱)的伪造邮件给我的一个同事,邮件的内容就是让这位同事去CTO的办公司一趟,没想到这位同事真的去了,然后一脸懵逼的回来了。
恶作剧算是完了,但是这让我开始研究伪造邮件是为什么产生的,腾讯企业邮为什么没有拦截。
0×01. 关于伪造邮件的一些总结
1) 邮件服务商之间转发邮件是不需要认证的,也就是说MTA 到MTA之间转发邮件是不需要认证的,这是SMTP协议本身定义的。 所以协议钓鱼邮件就可以伪称来自某某MTA发送钓鱼邮件;
2) 互联网上有一些邮件域名没有配置SPF记录 或者SPF记录值设置不当,就会被用作伪造邮件的mail_from 后缀域名;
比如88mmmoney.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,截止到本篇文章发表1周前,腾讯企业邮给我的回复是:邮件相关策略有问题,还在优化中
6)reply-to: 信件回复的收件人, 用户直接回复邮件时,reply-to就是默认收件人。 如果用户不指定它, from就是默认收件人
7) mail_to 和 to的区别
mail_to 是实际收件人(信封上的收件人), 而 to 是显示收件人(即信封内容中的收件人)
to 也是可以伪造的(to 支持别名显示,别名也是可以伪造的),类似于from
这是一封伪造邮件,to 也被伪造了
0×02. 关于防止垃圾邮件的两种技术
1、SPF
关于SPF的概念
查看SPF维基百科
SPF的配置
SPF 其实就是一条DNS的TXT的记录,其记录值就是 SPF的内容 比如:v=spf1 include:spf.mail.qq.com -all”
SPF 需要在域名解析服务器上配置,比如说是我们国内常用的DNSPOD配置如下
比如说service@freebuf.com 这封邮件的SPF 记录怎么设置,那么需要在二级域名freebuf.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
查询邮件域的SPF记录也很简单:
windows :
nslookup -qt=txt freebuf.com
Linux:
dig -t txt freebuf.com
2、DKIM
国外用的比较多,国内不多,比如腾讯邮箱默认就不支持这个
下图是一封腾讯企业邮发送到Gmail邮箱的邮件部分原始邮件信息:
可以看到并没有DKIM签名
而Gmail默认是有DKIM签名的
下图是一封Gmail邮箱发送到腾讯企业的邮件部分原始邮件信息:
可以看到是有DKIM签名的
关于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查询得到公开密钥后进行验证。
邮件域的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的:
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加密。
0×03. 关于国内有名的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 收到这封邮件的
0×04. 邮件代发之伪装成他人发件
1. foxmail 可以配置显示其他账户(由本邮件代发显示邮件账号)
2. 用上图的配置给自己(上图的实际账号)发封邮件
这里会显示代发
3. 如果是微信收到邮件呢(腾讯企业邮箱绑定微信后,微信可收信)
不注意看,还真以为是显示的发件人发的邮件呢
4. 给Gmail 也发一封
Gmail 也没提示代发
但是我们查看Gmail的原始邮件,可以看到此邮件不是显示发件人发的
5. 我们来看回复此邮件能不能看到猫腻
Gmail的回复, 回复给了显示发件人
foxmail的回复,也是回复给了显示收件人
foxmail的快速回复, 回复给了实际发件人
注: 如果是回复全部,则包含实际发件人
0×05. 一些识别伪造邮件的小技巧
1、实际发件人与显示发件人不一致
这时候就需要小心了,确认邮件真的是由合法的第三方代发的,比如有名的邮件代发服务商sendCloud,如果不是,一般都是伪造邮件
如何知道邮件的实际发件人?
一般是查看邮件的原始内容,不过还有一个小技巧,就是在收到邮件的时候,邮箱提示信息中显示的就是实际发件人
2、一般正常的发件服务器都会配置SPF,有的还会配置DKIM,如果收到的邮件的发件人的邮件域没有配置SPF,则有可能是伪造邮件
3、一般邮件服务商都会有相应的反垃圾邮件的机制,对于有安全提示的邮件要小心,不要轻易相信,不要轻易点击其中图片、链接、附件
如上图,都是伪造邮件,而且显示是收件人也是伪造的
0×06. 补充
腾讯企业邮发送的邮件默认是加密的
一般邮件body 内容是base64-utf8 编码后的结果,可以使用k8-web 编码转换工具解码或者编码
邮件中的邮件头的from 或者 to 部分都支持中文别名显示(subject也支持中文),这些就需要写代码将中文内容编码一下
#!/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([('\xe8\xae\xbf\xe9\x97\xae\xe4\xb8\x8b\xe9\x82\xae\xe4\xbb\xb6\xe4\xb8\xad\xe7\x9a\x84\xe9\x93\xbe\xe6\x8e\xa5\xef\xbc\x8c\xe7\x9c\x8b\xe7\x9c\x8b\xe4\xb8\x8d\xe8\x83\xbd\xe8\xae\xbf\xe9\x97\xae', 'utf-8')]).encode()
比如说自己构造邮件原始内容(不是调用某某库哦)的时候想把subject 内容修改一下,则需要先用repr 将中文的16进制编码内容传入make_header的参数中,这种得到的结果就是邮件subject(中文)原始内容
这里要注意一下,不能直接将content传入make_header中,否则会出错,而是先打印repr(‘subject中文内容’)值,然后将其拷贝至make_header中
*本文作者:knpewg85942