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

背景

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

具體是這樣,我們安卓版本的app是禁止截屏的(應該是app裏做了攔截),但部分頁面,支持配置成可以截屏。這個配置是通過後端接口獲取的,意思就是,如果調用這個接口失敗,就整個app默認不能截屏;如果調用成功,就可以在配置的指定頁面截屏。

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

排查過程

前期排查

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

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

image-20231202110550337

丟出這個後,就沒有進一步的動作了,認爲不是安卓端的問題,因爲用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掉,這類方案對於非安卓開發人員還是困難了一點,要一整套工具鏈,以後換個遙遙領先的話,可以好好折騰下。

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

image-20231202115202773

檢測網站

https://myssl.com/

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

檢查安卓端配置

可能有如下這個配置文件,看看裏面的內容,這裏面也涉及一些trust-anchor的內容:

圖片

負載均衡設備抓包

排除後端嫌疑

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

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

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

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

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

都還不錯。

1701490193819

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

image-20231202121151609

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

後面和那個能穩定復現的安卓設備比較,發現是同事手機的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沒校驗證書,所以才成功的,然後找了找,發現確實有這麼個選項:

image-20231202132420296

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

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

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

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

image-20231202133132969

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

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

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

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

image-20231202133649730

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

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

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

補齊證書鏈解決問題

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

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

image-20231202143535618

其實更好的辦法是用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的證書的,沒必要:

image-20231202152459708

遺留問題

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

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

補充問題

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

image-20231202153427056

所以,我現在也有點疑問了,到底他麼該返回幾層呢,只能說,如果大家遇到這類問題,可以往這個方面試一下,這個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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章