作者 | 李世奇
責編 | 郭 芮
從一個安全漏洞說起
Lodash是一款非常流行的 npm庫,每月的下載量超過 8000萬次,GitHub上使用它的項目有超過 400萬。前段時間 Lodash的一個安全漏洞刷爆了朋友圈,我們先來回憶下這個安全漏洞:
攻擊者可以通過 Lodash的一些函數覆蓋或污染應用程序。例如:通過 Lodash庫中的函數 defaultsDeep可以修改 Object.prototype的屬性。
我們都知道,Java在讀取對象中的某個屬性時,如果查找不到就會去其原型鏈上查找。試想一下,如果被修改的屬性是 toString方法:
constpayload = '{"constructor": {"prototype": {"toString": true}}}'
_.defaultsDeep({}, JSON.parse(payload))
每個對象都有一個 toString方法,當該對象被表示為一個文本值時,或者一個對象以預期的字符串方式引用時自動調用。默認情況下,toString方法被每個 Object對象繼承。如果此方法在自定義對象中未被覆蓋,toString返回 [object type],其中 type是對象的類型。如果覆蓋了 toString方法,那么給應用帶來的影響就是非常大的。
其實上述的問題就屬于一個很常見的安全漏洞 ——原型污染。
原型污染:攻擊者通過某種手段修改 Java 對象的原型(prototype)
原型污染:攻擊者通過某種手段修改 Java 對象的原型(prototype)
然而這并不是 Lodash第一次爆出安全漏洞了。事實上,像這樣的安全漏洞還可能存在于我們使用的千千萬萬個不同的開源依賴中,如果我們平時不重視他們,一旦出現問題對我們的項目造成的損失是不可估計的。這相當于你的項目中埋著很多不知道什么時候就會爆炸的炸彈。
安全調查
其實開發人員對開源代碼的安全性的信任程度要大于對自己編寫的代碼的安全性的信任程度,但是在確保代碼安全性和質量的工具還有很多不足之處。在 npm還沒有一個完善的安全檢測機制之前,npm和 NodeJs團隊曾經對數萬名 Java開發者發起過一個調查,第一個問題就是安全問題,具體就是開發人員如何看待他們編寫的代碼和所使用的開源項目的安全性。
調查結果顯示:全球 97%的 Java開發人員在自己開發的項目中都依賴開源代碼,77%的開發人員對他們使用的開源代碼是否安全表示擔憂。更有趣的是,有 87%的人表示擔心自己的代碼的安全性。
另外,超過一半的 Java開發人員認為,他們用來評估開源代碼的安全性和質量的工具還不夠好。
npm audit
npm官方專門維護了一個漏洞列表,當開發者或者專業的安全團隊發現某個依賴包存在安全問題后就會上報給 npm官方,然后官方會通知該項目開發者進行修復,修復完成后 npm會把漏洞詳細的描述信息、解決方案發布出來:
npm aduit主要做的就是把需要檢查的依賴信息發送給一個官方檢查接口, 該結構會在歷史上報的漏洞數據庫中判斷當前依賴信息是否含有漏洞,然后生成一個包含包名稱、漏洞嚴重性、簡介、路徑等的漏洞報告反饋給開發者。
我們現在直接安裝一個具有安全漏洞的 lodash@4.17.4版本,可見安裝完成后會提醒你你剛剛增加的依賴中含有3個漏洞。
執行 npm audit我們可以看到漏洞詳情,這個版本的 lodash存在3個安全漏洞,我們來具體看一個:
- High :表安全漏洞等級
- Package :存在漏洞的包名稱
- Dependency of :當前工程直接依賴的包名稱
- Path :漏洞完整依賴路徑
- More info :漏洞詳情
這里注意,并不只是直接依賴的包具有漏洞才會收到提醒,而是只要是你的依賴樹中某一個節點依賴依賴了具有漏洞的包你就會收到提醒,來看看下面的例子:
項目中并非直接依賴了 lodash,而是 @commitlint/cli依賴的 @commitlint/load中依賴了 lodash就會算作一個漏洞,所以一些龐大的迭代周期很長的項目含有幾萬個安全漏洞也是很正常的。
點開漏洞詳情的鏈接:https://npmjs/advisories/1065
我們可以看到漏洞具體說明,以及解決方法,右側是該漏洞的具體上報時間,漏洞公開時間。
GitHub 安全板塊
平時我們可能經常會收到類似如下的 GitHub安全漏洞提醒的郵件。
打開鏈接,我們可以看到漏洞具體的詳情頁面:
GitHub單獨為它開辟了一個,Security板塊來展示 GitHub檢測到的依賴安全漏洞,可見這些漏洞是足夠引起大家重視并且需要快速修復的:
另外,GitHub還為每個可修復的漏洞提供了一鍵修復的功能,點擊 Automated security updates按鈕,GitHub會自動將這些依賴漏洞修復,并提交一個 Pull Request給你的倉庫:
安全漏洞修復策略
npm也提供了 npm audit fix命令來幫助我們自動修復漏洞,還繼續使用上面的例子, Lodash在 4.17.12版本之前都具有原型污染漏洞,下面我們來看看具體的修復策略:
直接依賴漏洞
當前我們直接依賴了一個具有安全漏洞的 lodash@4.17.4版本:
"dependencies": {
"lodash": "^4.17.4"
}
由于 ^4.17.4的依賴范圍是 >=3.0.3 < 4.0.0,已修復的版本 4.17.12在這個范圍內,那么實際上 npm audit fix執行的邏輯就是 npm update lodash@4.17.12。
lodash@^ 4. 17. 4-> lodash@^ 4.17. 12
間接依賴漏洞
假設我們現在的依賴路徑非常深:@commitlint/cli^7.1.2>@commitlint/load^1.0.1>lodash^3.0.0
因為 @commitlint/load對 lodash的依賴是^3.0.0(>=3.0.0 <4.0.0),4.17.12不在這個范圍,所以我們不能直接通過升級 Lodash來修復漏洞,這時我們就要向上層依賴進行分析。
假設此時 @commitlint/load有一個更新版本 @commitlint/load^1.0.2對 lodash的依賴是^4.0.0(>=4.0.0 <5.0.0),lodash@4.17.12在這個依賴范圍內,那么修復策略為 npm update @commitlint/load@1.0.2 --depth=2。
npm update 只會檢查更新頂層的依賴,更新更深層次的依賴版本需要使用 --depth 指定更新的深度。
npm update 只會檢查更新頂層的依賴,更新更深層次的依賴版本需要使用 --depth 指定更新的深度。
按照這個邏輯,如果 @commitlint/load也沒有找到可以升級的包,那么再到上層依賴查找,直到找到可以修復漏洞的那個層級的依賴。
強制修復漏洞
按照上面的策略,從底層依賴一直向上層查找,如果一直到最上層依賴才有符合要求的修復版本,那么就直接 npm update更新最頂層依賴。
繼續使用上面的例子,如果 @commitlint/cli^7.1.2( >=7.1.2 <8.0.0 )還不能找到一個可修復的版本,那么 npm audit fix這個命令就無能為力了。
這時我們可以嘗試 npm audit fix --force(強制執行 audit fix 安裝最新的依賴項(toplevel))來進行修復,這個邏輯就是:npm install @commitlint/cli@patchedVersion --save
這個命令會直接跨越當前指定的 semver版本范圍,強制將依賴更新到最新版本,一定要謹慎使用。
這個命令會直接跨越當前指定的 semver版本范圍,強制將依賴更新到最新版本,一定要謹慎使用。
npm 還提供了一些其他的修復命令命令:
- npm audit fix --package-lock-only :在不修改 node_modules 的情況下執行 audit fix ,仍然會更改 pkglock
- npm audit fix --only=prod :跳過更新 devDependencies
不可修復漏洞
當然,以上的修復策略都不能解決這個安全漏洞,那說明此漏洞是無法自動修復的,需要人工判定處理。
關閉安全檢查
如果你對這些安全漏洞不 care,你也可以手動指定一些配置來關閉這些安全檢查:
- 安裝單個包關閉安全審查: npm install example-package-name --no-audit
- 安裝所有包關閉安全審查 - 運行 npm set audit false
- 手動將 ~/.npmrc 配置文件中的 audit 修改為 false
當然,強烈不推薦這么做,一定要對自己開發的項目負責到底~
當然,強烈不推薦這么做,一定要對自己開發的項目負責到底~
解讀依賴漏洞報告
執行 npm audit --json將會打印出一個詳細的 json格式的安全報告,在這個報告里可以看到這些漏洞的詳情,以及具體的漏洞修復策略。
由于這個 JSON比較大,我就不直接貼在這里了,大家可以選擇一個項目到本地執行 npm audit --json查看。
漏洞數據總覽
在 metadata屬性中:我們可以看到漏洞檢查的數據總覽:
vulnerabilities中展示了每種等級漏洞的數量,info、low、moderate、high、critical從左到右對應的安全漏洞等級從低到高。
下面是每種依賴的檢測數量,就是我們熟悉的 dependencies、devDependencies等等。
修復策略
在 actions屬性中,會列出所有可漏洞的修復策略,例如下面的,對 @commitlint/load執行更新,深度為 2,以修復 @commitlint/cli>@commitlint/load>lodash這條路徑上的漏洞:
{
"action": "update",
"module": "@commitlint/load",
"target": "1.0.2",
"resolves": [
{
"id": 1184,
"path": "@commitlint/cli>@commitlint/load>lodash",
"dev": false,
"optional": false,
"bundled": false
}
],
"depth": 2
}
另外,action還有我們上面提到的其他幾種操作:
- install (修復直接依賴)
- install major (強制升級依賴,跨越主版本)
- review (不可自動修復,需要人工review)
漏洞詳情
advisories屬性存放了每個漏洞的詳情:
由于屬性比較多,我們挑幾個重點的來看看:
- cves :CVE漏洞編號
- severity :漏洞等級
- found_by_user_name :發現該漏洞的用戶(這里 Snyk Security Team 是一個知名的安全團隊)
- vulnerable_versions :受影響的版本
- patched_versions :已修復的版本
- findings :所有依賴此路徑的漏洞
- overview :漏洞的簡要說明,這里就提到了 Lodash 的 defaultsDeep 容易收到原型污染
- references :漏洞參考,一般是由某些專業安全平臺發布的報告
安全平臺
上面的報告中提到了幾個專業的安全平臺,容易讓人產生迷惑,我們下面來具體看一下:
1、HackerOne
HackerOne(hackerone)成立于 2012年,是一個安全漏洞聚合和披露平臺,黑客可以在網站上披露自己發現的安全漏洞、并報告給相關的網站或公司,這些網站或公司在確認后可以給黑客提供獎金等各類感謝。HackerOne平臺的注冊黑客人數已突破 30萬人,提交的有效漏洞總計超過 10萬個,支付的漏洞獎勵金超過 4200萬美元。
我們可以在安全報告中看到,某些漏洞還會有一份 HackerOne的漏洞報告做為參考。
2、Snyk
Snyk是用于多個開發堆棧的依賴關系分析平臺,涵蓋 Java,Java,.Net,Ruby,Python,PHP,Golang和 Scala。Snyk維護著一個全面的,開放源代碼漏洞數據庫,其中包括 Snyk自己的專門研究團隊發現的漏洞,以及從公共數據源跟蹤的漏洞。
Snyk的漏洞數據庫通過其威脅情報系統提供有關漏洞的全面數據,提供更好的覆蓋范圍,并能夠顯示和報告尚未收到 CVE的漏洞。例如,npm提示的漏洞中有 72%是先被添加到 Snyk數據庫里的。
Snyk同樣也提供了掃描安全漏洞的機制,相比 npm audit,它的優勢是可以很容易和 GitHub或 GitLab的 CI流程集成在一起,更多掃描機制上的對比,可以看看這篇文章:https://nearform/blog/comparing-npm-audit-with-snyk/。
我們可以在上面的安全報告中看到,某些漏洞還會有一份 Snyk的漏洞報告做為參考,并且 Lodash這個安全漏洞也是由 Snyk安全團隊發現并上報的。
3、CVE
用 CVE ID標識特定漏洞或暴露, 組織可以快速準確地從各種 CVE兼容的信息源中獲取信息。通過在不同安全工具和服務之間的進行比對, CVE可以幫助組織選擇最適合其需要的內容。
我們可以看到,不論是上面的 npm audit報告還是 Snyk、HackerOne等其他的安全平臺的報告都會附上一個漏洞的 CVE編號。
小結
希望看完本篇文章能對你有如下幫助:
- 了解依賴安全漏洞的背景;
- 了解 npm 自動修復安全漏洞的機制;
- 了解一些常規的安全知識,提高對前端安全問題的關注度。
作者:李世奇,字節跳動IES前端工程師。


