1. 程式人生 > 其它 >一封偽造郵件引發的“探索”(涉及釣魚郵件、SPF和DKIM等)

一封偽造郵件引發的“探索”(涉及釣魚郵件、SPF和DKIM等)

0x00. 引言

我用swaks 傳送一封以我們公司CTO為顯示發件人(騰訊企業郵箱)的偽造郵件給我的一個同事,郵件的內容就是讓這位同事去CTO的辦公司一趟,沒想到這位同事真的去了,然後一臉懵逼的回來了。

惡作劇算是完了,但是這讓我開始研究偽造郵件是為什麼產生的,騰訊企業郵為什麼沒有攔截。

0x01. 關於偽造郵件的一些總結

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 是[email protected] from是[email protected]

mail_from 和from 的字尾中就cn 和com 不同,也就是說只有頂級域名不同,其他相同

這樣騰訊企業有竟然沒有代發提示、安全提示,正常的出現在了我的收件箱中, 不管mail_from 中字尾xxx.com 的SPF是不是OK,

也不管xxx.com是不是存在

騰訊企業郵支援將郵件原始內容匯出成eml檔案(可用文字編輯器編輯、檢視)

而另一封我偽造的一封郵件實際發件人是 [email protected], 顯示發件人是[email protected] ,收件人是 [email protected]

顯然mail_from 和from不一致,這裡騰訊企業郵是會提示你代發

比對兩個偽造郵件,我據此反饋給了騰訊企業郵開發組,我覺得是騰訊企業郵的BUG,截止到本篇文章發表1周前,騰訊企業郵給我的回覆是:郵件相關策略有問題,還在優化中

6)reply-to: 信件回覆的收件人, 使用者直接回復郵件時,reply-to就是預設收件人。 如果使用者不指定它, from就是預設收件人

7) mail_to 和 to的區別

   mail_to 是實際收件人(信封上的收件人), 而 to 是顯示收件人(即信封內容中的收件人)

  to 也是可以偽造的(to 支援別名顯示,別名也是可以偽造的),類似於from

  這是一封偽造郵件,to 也被偽造了

0x02. 關於防止垃圾郵件的兩種技術

1、SPF

關於SPF的概念

檢視SPF維基百科%20%20%20%20%20%20%20%20%20%20%20%20%E6%88%96%E8%80%85%20%20%20%20%20%20%20%20%20%20%20%20%5B%E6%88%91%E7%9A%84%E5%89%8D%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%5D(https%3A//forrestx386.github.io/2017/04/01/%E5%85%B3%E4%BA%8E%20SPF/))

SPF的配置

SPF 其實就是一條DNS的TXT的記錄,其記錄值就是 SPF的內容 比如:v=spf1 include:spf.mail.qq.com -all”

SPF 需要在域名解析伺服器上配置,比如說是我們國內常用的DNSPOD配置如下

比如說[email protected] 這封郵件的SPF 記錄怎麼設定,那麼需要在二級域名freebuf.com下增加一個主機記錄為@, 記錄型別為TXT, 記錄值為v=spf1 include:spf.mail.qq.com ~all (記錄值格式是這樣,具體值可能有所不同)

如果收到的郵件格式是這樣的: [email protected] ,那麼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記錄

[email protected]為例

在主機記錄中寫入 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設定中配置的,可以和郵件域(比如[email protected] @後面的即是郵件域)不一樣(一般都保持一樣) 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. 郵件代發之偽裝成他人發件

1. foxmail 可以配置顯示其他賬戶(由本郵件代發顯示郵件賬號)

2. 用上圖的配置給自己(上圖的實際賬號)發封郵件

這裡會顯示代發

3. 如果是微信收到郵件呢(騰訊企業郵箱繫結微信後,微信可收信)

不注意看,還真以為是顯示的發件人發的郵件呢

4. 給Gmail 也發一封

Gmail 也沒提示代發

但是我們檢視Gmail的原始郵件,可以看到此郵件不是顯示發件人發的

5. 我們來看回復此郵件能不能看到貓膩

Gmail的回覆, 回覆給了顯示發件人

foxmail的回覆,也是回覆給了顯示收件人

foxmail的快速回復, 回覆給了實際發件人

注: 如果是回覆全部,則包含實際發件人

0x05. 一些識別偽造郵件的小技巧

1、實際發件人與顯示發件人不一致

這時候就需要小心了,確認郵件真的是由合法的第三方代發的,比如有名的郵件代發服務商sendCloud,如果不是,一般都是偽造郵件

如何知道郵件的實際發件人?

一般是檢視郵件的原始內容,不過還有一個小技巧,就是在收到郵件的時候,郵箱提示資訊中顯示的就是實際發件人

2、一般正常的發件伺服器都會配置SPF,有的還會配置DKIM,如果收到的郵件的發件人的郵件域沒有配置SPF,則有可能是偽造郵件

3、一般郵件服務商都會有相應的反垃圾郵件的機制,對於有安全提示的郵件要小心,不要輕易相信,不要輕易點選其中圖片、連結、附件

如上圖,都是偽造郵件,而且顯示是收件人也是偽造的

0x06. 補充

騰訊企業郵傳送的郵件預設是加密的

一般郵件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([('xe8xaexbfxe9x97xaexe4xb8x8bxe9x82xaexe4xbbxb6xe4xb8xadxe7x9ax84xe9x93xbexe6x8exa5xefxbcx8cxe7x9cx8bxe7x9cx8bxe4xb8x8dxe8x83xbdxe8xaexbfxe9x97xae', 'utf-8')]).encode()

比如說自己構造郵件原始內容(不是呼叫某某庫哦)的時候想把subject 內容修改一下,則需要先用repr 將中文的16進位制編碼內容傳入make_header的引數中,這種得到的結果就是郵件subject(中文)原始內容

這裡要注意一下,不能直接將content傳入make_header中,否則會出錯,而是先列印repr(‘subject中文內容’)值,然後將其拷貝至make_header中