Valine 是一款樣式精美,部署簡單的評論系統, 第一次接觸便被它精美的樣式,無服務端的特性給吸引了。它最大的特色是基于 LeanCloud 直接在前端進行數據庫操作而無需服務端,極大的縮減了部署流程,僅需要在靜態頁引入 Valine SDK 即可。
?????? 初識 Valine
以下是 Valine 官網提供的快速部署腳本,其中 appId
和 appKey
是你在 LeanCloud 上創建應用后對應的應用密鑰。也正是基于這對密鑰,Valine 在內部調用了 LeanCloud SDK 進行數據的獲取,最終將數據渲染在 #vcomments
這個 DOM 上。這便是 Valine 的大概原理。
<head>
..
<script data-original='//unpkg.com/valine/dist/Valine.min.js'></script>
...
</head>
<body>
...
<div id="vcomments"></div>
<script>
new Valine({
el: '#vcomments',
appId: 'Your appId',
appKey: 'Your appKey'
})
</script>
</body>
有同學可能會有疑問了,appId
和 appKey
都直接寫在前端了,那豈不是誰都可以修改數據了?這就需要牽扯到 LeanCloud 的數據安全問題了,官方專門寫了篇文檔《數據和安全》 來說明這個問題。簡單的理解就是針對數據設置用戶的讀寫權限,確保正確的人對數據有且僅有正確的權限來保證數據的安全。
乍聽一下,保證用戶數據只讀的話,感覺還是挺安全的??墒聦嵳娴娜绱嗣?,讓我們繼續來看看。
???♂? Valine 的問題
?? 閱讀統計篡改
Valien 1.2.0 增加了文章閱讀統計的功能,用戶訪問頁面就會在后臺 Counter 表中根據 url 記錄訪問次數。由于每次訪問頁面都需要更新數據,所以在權限上必須設置成可寫,才能進行后續的字段更新。這樣就造成了一個問題,實際上該條數據是可以被更新成任意值的。感興趣的同學可以打開 https://valine.js.org/visitor... 官網頁面后進入控制臺輸入以下代碼試試。試完了記得把數改回去哈~
const counter = new AV.Query('Counter');
const resp = await counter.equalTo('url', '/visitor.html').find();
resp[0].set('time', -100001).save();
location.reload();
可以看到該頁面的訪問統計被設置成了 -100000
了。這個問題唯一值得慶幸的是 time
字段的值是 Number 類型的,其它的值都無法插入。如果是字符串類型的話就是一個 XSS 漏洞了。
該問題有一個解決辦法,就是不使用次數累加的存儲方式。更改為每次訪問都存儲一條只讀的訪問記錄,讀取的時候使用 count()
方法進行統計。這樣所有數據都是只讀的,就不存在篡改的問題了。這種解決方案唯一的問題就是數據量會比較大,對查詢會造成一定壓力。當然如果是在基于原數據不變的情況下,只能是增加一層服務端來做修改權限的隔離了。
?? XSS 安全
從很早的版本開始就有用戶報告了 Valine 的 XSS 問題,社區也在使用各種方法在修復這些問題。包括增加驗證碼,前端XSS過濾等方式。不過后來作者才明白,前端的一切驗證都只能防君子,所以把驗證碼之類的限制去除了。
現有的邏輯里,前端發布評論的時候會將 Markdown 轉換成 HTML 然后走一下前端的一個 XSS 過濾方法最后提交到 LeanCloud 中。從 LeanCloud 中拿到數據之后因為是 HTML 直接插入進行顯示即可。很明顯,這個流程是存在問題的。只要直接提交的是 HTML 而且拿到 HTML 之后直接進行展示的話,XSS 從根本上是無法根除的。
那有沒有根本的解決辦法?其實是有的。針對存儲型的 XSS 攻擊,我們可以使用轉義編碼進行解決。只要效仿早前 BBCode 的做法,提交到數據庫的是 Markdown 內容。前端讀取到內容對所有 HTML 進行編碼后再進行 Markdown 轉換后展示。
function encodeForHTML(str){
return ('' + str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
};
由于 Serverless 攻擊者是可以直達存儲階段,所以數據存儲之前的一切防范是無效的,只能在讀取展示過程處理。由于所有的 HTML 轉義后無法解析,Markdown 相當于我們根據自定義的語法解析成 HTML,保證轉換后的 HTML 沒有被插入的機會。
不過這個方法存在一個問題,那就是對老數據存在不兼容。因為這相當于修改了存儲和展示的規則,而之前一直存儲的都是 HTML 內容,修復后之前的數據將無法展示 HTML 樣式。而為了能在存儲的還是 HTML 情況下規避 XSS 安全問題,唯一的辦法就是增加服務端中間層。存儲階段增加一道閥門,將轉義階段提前至存儲階段,保證新老數據的通用。
?? 隱私泄露
說完了存儲的問題,我們再來看看讀取的問題。攻擊者除了可以直達存儲,也可以直達讀取,當一個數據庫的字段開放了讀取權限后,相當于該字段的內容對攻擊者是透明的。
在評論數據中,有兩個字段是用戶比較敏感的數據,分別是 IP 和郵箱。燈大甚至專門寫了一篇文章來批判該問題 《請馬上停止使用Valine.js評論系統,除非它修復了用戶隱私泄露問題》。甚至掘金社區在早期使用 LeanCloud 的時候也暴出過泄露用戶手機號的安全問題。
為了規避這個問題,Valine 作者增加了 recordIP
配置用來設置是否允許記錄用戶 IP。由于是 Serverless,目前能想到的也只是不存儲的方式解決了。不過該配置項會存在一個問題,就是該配置項的配置權在網站,隱私的問題是評論者遇到的,也就是說評論者是無權管理自己的隱私的。
除了這個矛盾點之外,還有就是郵箱的問題。郵箱本質上只需要返回 md5 用來獲取 Gravatar 頭像即可。但是由于無服務端的限制,只能返回原始內容由前端計算。而郵箱我們又需要獲取到原始值,方便做評論回復郵件通知功能。所以我們也不能不存儲,或者存儲 md5 后的值。
該問題的解決方案只能是增加一層服務端,通過服務端過濾敏感信息解決這個問題。
?? Waline!
基于以上原因,我們發現只有增加一層服務端中間層才能很好的解決 Valine 的安全問題,所以 Waline 橫空出世了!Waline 與 Valine 最大的不同就是增加了服務端中間層,解決 Valine 暴露出來的安全問題。同時基于服務端的特性,提供了郵件通知、微信通知、評論后臺管理、LeanCloud, MySQL, MongoDB, SQLite, PostgreSQL 多存儲服務支持等諸多特性。不僅如此,Waline 默認使用 Vercel 部署,實現完全免費部署!
Waline 最初的目標僅僅是為 Valine 增加上服務端中間層。但是由于作者不知為何從 1.4.0
版本開始只推送編譯后的文件到 Github 倉庫中,源文件停止更新。導致我只能連帶前端也實現一遍。當然前端的很多代碼和邏輯為了和 Valine 的配置保持一致都有參考 Valine,甚至在名字上,我也是從 Valine 上衍生的,讓大家能明白這個項目是 Valine 的衍生版。
?? 后記
Serverless 的概念火了非常多年,但技術沒有銀彈,我們在看到它的優點的同時,也要正視它所帶來的問題。而 Serverless 自己可能也意識到了這個問題,從早期的無服務端慢慢轉向了無服務器,更偏向 BaaS 了。不過由于 Valine 沒有開放源代碼,所以上面說的一些問題和解決方法只能等待作者自己發現這件事了。
查看原文