NPM項目已正式承認其存在一個長期的安全漏洞,該漏洞可能導致惡意的代碼包可以在開發者系統上隨意的運行任意代碼,導致了第一個NPM創建的蠕蟲。在一篇名為“npm無法限制惡意代碼包的行為”的漏洞說明VU319816中,Sam Saccone描述了創建一個蠕蟲需要的步驟以及怎么讓它自動傳播。盡管這是在2016年一月被報道出來的,然而從NPM倉庫管理初始版本發布開始,這個問題就已經存在而且被廣泛知曉。
這個問題的根源在于,NPM模塊有相關的腳本文件,它們可以在安裝的時候被NPM運行。當模塊從NPM上被下載后,它有足夠多的機會可以運行其代碼:
preinstall - 在包被安裝之前運行 postinstall - 在包被安裝之后運行 preinstall - 在包被卸載之前運行 uninstall - 在包被卸載時運行 postuninstall - 在包被卸載之后運行這些腳本使得NPM模塊的發布更加簡單并可以在使用之前進行內容的后期處理。舉例來說,一些模塊原本是由CoffeeScript或者TypeScript編寫的,它們需要一個轉化的步驟,或者是用ES6編寫的,需要Babel轉化后才能運行在目前的瀏覽器上。另外,JavaScript庫一般都會進行壓縮(使其更小),這往往也是需要自動化運行的一個步驟。因為JavaScript是解釋性的,像make這樣的工具不會被使用,所以npm腳本就用來干這些苦力活。
在模塊被客戶端下載和使用的時候,NPM錯誤地使用了這些代碼,不光用來完成編譯時間驗證,還進行客戶端運行。舉例來說,left-pad慘狀(InfoQ昨日報道)在重新發布模塊后得到解決;然而,如果像這樣被廣泛使用的模塊(例如true,其功能只是打印true)有被影響的代碼塊,那么這樣的代碼運行在成千上萬的機器上也有了可能。
這些代碼滿足了蠕蟲的創建條件,然后這些蠕蟲持續地影響其他的代碼包并依次傳播。NPM包開發者使用標準的證書把他們的代碼包(當然是通過npm)發布到NPM目錄。為了加快發布,他們有可能會登錄到該目錄然后一直保持登錄狀態,此后任何的代碼包都可以發布到該目錄。正因如此,一旦一個npm包開發者的機器被攻占,蠕蟲可以掃描其他的代碼包,然后重新發布它們(通過開發者現有的證書),與此同時,蠕蟲已經被注入到這些剛被感染的包的腳本代碼塊中。
甚至,包管理其自己也執行JavaScript,這意味著僅僅是解決包的依賴就會導致任意代碼的執行。這里有個例子可以最作為該概念的認證,一個代碼包使用變量作為其包名,被發現可以偽裝成任何的包。參考真實的開源精神,該概念認證是MIT證書認證:
A="$1" echo '{ "name": "'"$A"'", "version": "2.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC"}' > package.jsonnpm publish如果一個包開發者下載了此代碼包然后運行了其腳本,那么之后該包會運行index.js,之后便可以在終端機器上運行任意的代碼。該例子就展示了怎樣運行sudo rm -rf /,顯然這會在沒有任何備份的情況下造成嚴重后果。
NPM目錄允許任何人重新規劃一個存在的代碼包,只需要在他們的空間里用相同的命名發布一個新的版本,這使得該問題變得更加復雜。除非開發者特意的寫死他們所依賴的代碼包版本,否則很有可能自動更新到最近的版本并在之后遭受攻擊。
到目前為止NPM的回復都相當的軟弱,否認了其對掃描惡意軟件的責任,并指出開發者使用那些模塊受到注入攻擊也是咎由自取。然而整個基礎都被設置成允許所有人擁有自己的包并可以用任何JavaScript替換這些包;盡管這些腳本可以被排除在外(通過使用using npm install --ignore-scripts 或者npm config set ignore-scripts true),然而還是有機會使用 require('shelljs').exec('rm -rf /')替代JavaScript文件的內容,其運行時還是有著破壞性的影響。并且,讓一個npm會話保持登錄意味著任何應用,無論是基于NodeJS還是其他語言,都可以很容易的在用戶不知曉的情況下以當前用戶的名義發布腳本。
最近的這些事件表明,隨著JavaScript的以及服務器端NPM使用的快速增長,其被賦予了和JavaScript語言本身一樣的安全性的考慮。唯一讓人感到驚訝的是,整個事件花費了這么長的時間才水落石出。
查看英文原文:NPM Worm Vulnerability Disclosed