安卓端出現https請求失敗的一次問題排查

2023-12-02 18:00:36

背景

某天早上,正在一個會議時,突然好幾個同事被叫出去了;後面才知道,是有業務同事反饋到領導那裡,我們app裡面某個功能異常。

具體是這樣,我們安卓版本的app是禁止截圖的(應該是app裡做了攔截),但部分頁面,支援設定成可以截圖。這個設定是通過後端介面獲取的,意思就是,如果呼叫這個介面失敗,就整個app預設不能截圖;如果呼叫成功,就可以在設定的指定頁面截圖。

業務反饋就是說,之前可以截圖的幾個頁面,現在突然不能截圖了,不知道是不是我們搞了啥變更;後面產品去業務那深入瞭解了下,發現:連線公司wifi後就不能截圖,用4g/5g是可以的。

排查過程

前期排查

安卓開發首先介入,具體方式就是,因為可以復現,找了個安卓裝置,連線電腦就可以debug app(沒搞過安卓,具體不清楚),後面說是獲取截圖設定的介面(https)報錯了:

ret:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found

丟出這個後,就沒有進一步的動作了,認為不是安卓端的問題,因為用5g就可以,只是wifi不行。然後問題就卡在那了。有人又丟出之前的一個變更通知,那次變更是這樣,之前我們https證書解除安裝都是在業務伺服器的nginx做的,這樣的話,每個業務都會有自己的nginx,每個nginx都要負責https加解密,後來就提出來,要把這個https加解密前置,後面就前置到了負載均衡裝置(比如典型的硬體負載均衡裝置:F5)。

有人就說是不是動了這個導致的,雖然這個極有可能,但是,沒有人去查,去確認。

後端開始介入

因為安卓側認為自己沒問題,產品後面來找我,我才開始介入這個問題。

下午先了解了下整個事情,比較重要的事情是,拿到了復現問題的手機,然後試著連線電腦charles進行抓包,才想起來安卓目前抓包非常困難,在電腦端用charles、fiddler這類代理是沒有用的;那就只能找安卓開發看這個,我本來預期的是,在他那裡,通過debug,要知道這個錯誤到底是什麼導致的,比如是https的哪個階段,是不是https證書的哪個欄位有啥問題,結果,最終和我說的是,這個是底層okhttp的,沒法debug到那一層;我其實是對這塊持懷疑態度,肯定是有辦法的,但可能他不會,從沒深入過https這層,所以就說他沒辦法繼續定位到更多資訊了。

他麼當時火也大,但問題還是得解決(後面我看到貨拉拉那個文章裡,其實是可以debug那部分程式碼,不過確實是不在android.jar原始碼裡,在單獨的模組中)。

安卓端沒法看,電腦端沒法用簡單的方式抓包,我瞭解到的一些抓包的辦法都是很複雜,不搞安卓開發的話,光是搭環境都要搭半天那種;要麼就是在手機上裝抓包軟體,但有些需要root,且能不能抓https這層檢查證書,我也持懷疑態度,我個人又是垃圾iphone,對安卓確實不熟悉。

唯一的辦法,就只有:wifi路由器上抓包,或者是找到目前負責https加解密的負載均衡裝置的同事,來進行抓包。

搜尋引擎查詢可能原因

證書鎖定

拿那個錯誤,查了下原因,查到一篇貨拉拉的文章,感覺比較靠譜。

https://mp.weixin.qq.com/s/Je1Kf0UX9pkwedaL7pTe3A 貨拉拉SSL證書踩坑之旅

裡面提到,app內部可能內建了伺服器端的證書,而app在存取https後端建立https連線的過程中,伺服器端會把自己的證書(一般設定在nginx,我們這邊就是負載均衡裝置,F5)返回給app,app檢查到返回的證書如果和本地內建的不一致,就可能報那個錯;

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found

這個專業術語叫做:證書鎖定. (https://zhuanlan.zhihu.com/p/58204817)

這種就是可以防止中間人攻擊的,如fiddler、charles這類基於代理的,基本就屬於中間人攻擊,因為charles他們會把自己的證書給我們,我們內建了證書的話,就會發現charles證書和內建證書不一致,就可以主動終止連線。

好些安卓的專業抓包方案,就是基於hook,把證書校驗的那些程式碼都給hook掉,這類方案對於非安卓開發人員還是困難了一點,要一整套工具鏈,以後換個遙遙領先的話,可以好好折騰下。

另外,如果真用了證書鎖定,那麼根據貨拉拉文章內容,新證書可能少了某個欄位,導致這個問題:

檢測網站

https://myssl.com/

可以輸入自己的網址,檢查下,不一定準,我們的問題當時就沒查出來。

檢查安卓端設定

可能有如下這個組態檔,看看裡面的內容,這裡面也涉及一些trust-anchor的內容:

負載均衡裝置抓包

排除後端嫌疑

次日,我直接找了app端的leader,結果leader反饋說,app沒搞證書鎖定那些高階玩意,其他設定也檢查了,好像沒啥問題,所以無疾而終。

然後去找了負載均衡裝置的同事,同事還是非常支援,所以,那天下午,我們就在一塊,在負載均衡裝置上,抓了一下午的包。

他首先懷疑的是,後端服務返回的內容是不是有問題,因為,用他手機嘗試時,一會可以截圖,一會不可以,就是沒能穩定復現。

於是就抓取負載裝置和後端nginx之前的報文,這塊我們面臨一個問題,負載上流量很大,怎麼區分出他手機的流量呢?尤其是現在好多手機都是優先用Ipv6,而目前在百度這種查ip,基本只顯示了ipv4

那天我看同事用的ip138.com,我今天又搜了一個:https://ipw.cn/

都還不錯。

所以我們就抓負載和nginx之間的包,包裡會有欄位帶了我們的手機的出口ip:

就用這個欄位篩選出我們的流量後,檢查發現,後端返回的內容沒啥問題。

後面和那個能穩定復現的安卓裝置比較,發現是同事手機的app版本低了,艹,升到最新版,就能穩定復現了。

各種場景對比

後面就開始對比,從公網過來,和從wifi過來的包;再就是,安卓裝置端公網出口ip為ipv4和ipv6的,這麼一組合,就有4種組合。

後面發現,公網過來的,不管是ipv4還是ipv6,都沒問題;從wifi過來的,我們這邊測試,好像都是有問題的,但我們也抓包發現了其他人的請求,看著好像是從wifi來的,又沒問題的。

這期間其實探索了很多可能性,比如也檢查了waf裝置(waf裝置比負載均衡裝置還要靠前,且waf工作在7層,也會涉及https的加解密,我是有懷疑過waf,但當時看了waf的紀錄檔啥的,沒發現異常)

另外,這期間,我也在自己的雲伺服器上,嘗試瞭如下方式:

 openssl s_client -debug -connect xxx.com.cn:443
 
 tcpdump -i any host xxx.com.cn and tcp port 443 -w 443.pcap

和負載均衡端側的抓包進行交叉對比。

對比的場景太多,都記不清了,但最終確定的是,wifi網路下,出口ip是ipv4還是ipv6來著的時候,就有問題。

其實我一開始就是懷疑證書那塊可能有問題,但是,也不能在沒找到確切原因的時候,貿然對證書進行操作,所以就和負載均衡裝置的同事搞了一下午。

雖然當時沒確定出根因,但收穫包括:

流量情況下,存取xxx.com.cn:443是直接到xxx.com.cn:443的防火牆裝置;

wifi下,存取xxx.com.cn:443也是繞到了公司的網際網路出口,再去存取xxx.com.cn:443的防火牆裝置;

但是,可以肯定的是,這兩種情況下,xxx.com.cn:443的防火牆那邊,肯定是設定了不同的路由策略,兩者的網路路徑應該是不一樣的,這塊就還得找具體負責防火牆的同事來一起看。

本機模擬發現新端倪

我們不是在負載均衡和nginx那層抓了包嗎,那層是明文的,我們就照著那個明文,錄入到本機的postman裡,呼叫,發現是成功的。

後來,我想是不是postman沒校驗證書,所以才成功的,然後找了找,發現確實有這麼個選項:

預設是false,不校驗,我打卡後,再一請求,果然報錯了,不過報的是伺服器端返回的證書缺少了中間證書。

所謂的中間證書,可以這麼理解,目前世界上,有一批權威機構(ROOT CA),他們負責給大家頒發https證書,頒發的證書會給到我們,然後我們就放到伺服器上。

瀏覽器、手機等使用者端存取我們時,我們就把證書返回給瀏覽器等,此時,他們怎麼知道我們的證書是真的假的呢,就是靠證書裡的頒發者欄位,他們找到頒發者,再和自己瀏覽器內建的或者作業系統中內建的ROOT CA白名單做一個匹配,如果在本機內建的ROOT CA白名單中,就可以認為證書確實是這些權威機構頒發的,值得信賴。(當然,這只是其中的一個檢查項,不是全部,比如還要檢查證書是否在有效期內,是否已經被吊銷了)

但是哈,一般我們的證書,不會是這些ROOT CA直接頒發的,而是ROOT CA下屬的某個中間證書頒發的,以下面百度的為例:

此時,百度伺服器端就必須返回baidu.com這個證書,但是它是由中間證書籤發的,而一般作業系統或者瀏覽器沒內建中間證書那些機構,所以,伺服器端一般要把baidu.com以及中間證書機構的證書,一併返回,這樣,才能一層層找到中間證書的簽發者,然後發現簽發者是root ca的話,就和本機的白名單做對比。

另外,我也在本機對了對照組,postman在兩種網路下發請求:

  • 本機pc在公司wifi下,此時,走的是公司wifi
  • 本機pc連線手機的熱點,此時,走的是流量網路

對比了下,發現真的有問題:

在這兩種情況下,使用者端首先發請求(client hello)和伺服器端協商後續用哪個版本的tls協定。使用者端發出去的請求我對比了,除了亂數部分,基本一致,但是,伺服器端最終協商出來的結果卻不一樣,一個是tls v1.2 ,一個是tls v.1.3

從這裡也驗證了,這個xxx.com.cn:443的接入這塊(一般接入那裡應該是路由器,但一般好像也具有防火牆的功能),會根據使用者端的網路來源於wifi和流量,走了不同的路線。

這塊也得具體諮詢接入這塊的同事了。

補齊證書鏈解決問題

結果我們後續還沒來得及去找接入的同事,負責負載均衡裝置的同事跟我說,他把證書鏈補充完整了,讓我再試試。

所謂證書鏈補齊了的意思是,他之前就是負責將nginx層的證書挪到了負載均衡裝置,在他完成這次變更後,https建立連線時,每次伺服器端就只返回兩層證書了:

其實更好的辦法是用openssl工具,因為上面這個方法我發現也不一定準確,我之前確實是發現有返回3層證書(含root ca)的時候,但我寫文章這會,測試了下,發現又只有兩層了。

但是,用openssl進行如下測試,都是能看到三層證書的:

 openssl s_client -debug -connect xxx.com.cn:443
 或
 [root@VM-0-6-centos ~]# openssl s_client -showcerts -verify 5 -verify_return_error -connect xxx.com.cn:443
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert Secure Site CN CA G3
verify return:1
depth=0 C = CN, ST = xxxx, CN = *.xxx.com.cn
verify return:1

對我對上述openssl命令,同時抓包的話,包內顯示依然只有兩層證書,我之前看一本書裡也說,一般也是不推薦返回ROOT CA的證書的,沒必要:

遺留問題

因為問題解決了,也就沒有再去找負責xxx.com.cn網路接入的同事查問題了,大家事情也多,就這樣吧,事情搞定就行了。

但這也算隱形的坑,我猜測的話,可能是一個鏈路走了waf,一個鏈路沒走waf;所以最終一個協商出用tls v1.2,一個協商出用tls v1.3.

補充問題

我翻到一個8月份的抓包檔案:跟隨追蹤.pcap,裡面的話,伺服器端確實是返回了3層證書的,包括了ROOT CA的,如下:

所以,我現在也有點疑問了,到底他麼該返回幾層呢,只能說,如果大家遇到這類問題,可以往這個方面試一下,這個https水還是比較深的。

curl知識補充

平時經常用curl,但遇到https這種時,一般會失敗;此時,習慣性加個-k,跳過https證書校驗.

-k, --insecure
              (SSL)  This  option  explicitly  allows  curl  to  perform  "insecure"  SSL connections and transfers. All SSL connections are
              attempted to be made secure by using the CA certificate bundle installed by default. This  makes  all  connections  considered
              "insecure" fail unless -k, --insecure is used.

              See this online resource for further details: http://curl.haxx.se/docs/sslcerts.html
[root@VM-0-6-centos ~]# curl https://www.baidu.com
curl: (77) error setting certificate verify locations:  CAfile: /etc/ssl/certs/ca-certificates.crt CApath: none

[root@VM-0-6-centos ~]# curl https://www.baidu.com -k
<!DOCTYPE html>
...

但是,這次是要解決https的問題,肯定不能跳過了,所以研究了下怎麼把root ca裝到機器上,我是centos機器,我發現這樣就可以了:

root ca檔案參考:https://curl.se/docs/caextract.html 
wget https://curl.se/ca/cacert.pem -k  下載到cacert.pem

然後指定下ca檔案: 
[root@VM-0-6-centos ~]# curl --cacert cacert.pem   https://www.baidu.com

參考檔案

https://mp.weixin.qq.com/s/Je1Kf0UX9pkwedaL7pTe3A 貨拉拉SSL證書踩坑之旅

https://mp.weixin.qq.com/s/bGc-GScIEn_1cqZ64A7E3Q

https://mp.weixin.qq.com/s/5Cfwli0aC-ueaXTi0Pwfyw

https://mp.weixin.qq.com/s/zAFkcDBTNjfDLAnzbi5j6Q

https://cloud.tencent.com/developer/article/1973401

https://mp.weixin.qq.com/s/eKLNLj7ZqD80kZbsjQzS6Q

https://mp.weixin.qq.com/s/xFj9fjQ7crc2RnR5ckTfpw

https://mp.weixin.qq.com/s/XD8cvqb1ScWMxEwhnwaVtg

https://mp.weixin.qq.com/s/7-iQtXifIvwyXcleO2rpzw

https://mp.weixin.qq.com/s/_bVnCAheO5e71iniSzTLcg

https://mp.weixin.qq.com/s/faExv_-y0MxTFBoum7csHQ

openssl: man openssl

openssl s_client : man s_client