預(yù)防NodeJS命令注入的方法詳解
前言
Node.js和npm為前端生態(tài)中提供了統(tǒng)一的開發(fā)語言、強(qiáng)大的包管理和模塊生態(tài)系統(tǒng)、靈活的構(gòu)建工具和任務(wù)自動(dòng)化、以及豐富的前端框架和庫等等。
可以說,正是因?yàn)閚odejs帶來的這些工具和資源使前端開發(fā)更加高效、便捷,并推動(dòng)了前端技術(shù)的快速發(fā)展。
但是近年來,Node.js 生態(tài)系統(tǒng)中的 npm 軟件包中出現(xiàn)了許多 CVE("Common Vulnerabilities and Exposures" 常見漏洞和公開漏洞),譬如lodash庫的CVE漏洞——CVE-2018-16487[2]、express庫的CVE漏洞——CVE-2018-17346[3]以及jsonwebtoken庫的CVE漏洞——CVE-2018-12424[4]等等,在這其中有一個(gè)特別危險(xiǎn)且屢禁不止的漏洞就是命令注入(Command Injection)。
作為前端工程師而言,在我們?nèi)粘9ぷ髦?,不僅需要快速交付、優(yōu)化性能相關(guān),還要時(shí)刻對(duì)項(xiàng)目中所采用的nodejs技術(shù)棧及其安全相關(guān)的因素考慮在內(nèi)。
簡而言之,關(guān)于安全這根弦兒得時(shí)刻緊繃著!
命令注入[5]是一種攻擊,其目的是通過有漏洞的應(yīng)用程序在主機(jī)操作系統(tǒng)上執(zhí)行任意命令。當(dāng)應(yīng)用程序?qū)⒂脩籼峁┑牟话踩珨?shù)據(jù)(表單、cookie、HTTP 標(biāo)頭等)傳遞給系統(tǒng)shell時(shí),就有可能發(fā)生命令注入攻擊。在這種攻擊中,攻擊者提供的操作系統(tǒng)命令通常是以受攻擊應(yīng)用程序的權(quán)限執(zhí)行的。命令注入攻擊之所以可能,主要是因?yàn)檩斎胄r?yàn)不足。
這種攻擊與代碼注入不同,代碼注入允許攻擊者添加自己的代碼,然后由應(yīng)用程序執(zhí)行。在命令注入攻擊中,攻擊者擴(kuò)展應(yīng)用程序的默認(rèn)功能,執(zhí)行系統(tǒng)命令,而無需注入代碼。
場景分析
假設(shè)有某程序員a同學(xué)在某個(gè)nodejs項(xiàng)目中寫出了類似的代碼:
const { exec } = require('child_process'); function runCommand(userInput) { const command = `ls ${userInput}`; // 將用戶所輸入的內(nèi)容拼接到命令中 exec(command, (error, stdout, stderr) => { if (error) { console.error(`執(zhí)行命令時(shí)出錯(cuò):${error}`); return; } console.log(`命令執(zhí)行結(jié)果:${stdout}`); }); } const userInput = '; rm -rf /'; // 惡意用戶所輸入的內(nèi)容 runCommand(userInput);
我們簡單分析下以上代碼,這段程序可以將用戶所輸入的內(nèi)容直接拼接到命令行字符串中。如果因?yàn)轫?xiàng)目工期緊張,沒經(jīng)過code review匆忙上線,恰好碰到個(gè)別用戶所輸入的惡意的命令,例如'; rm -rf /',那么最終執(zhí)行的命令將變?yōu)閘s ; rm -rf /,導(dǎo)致“刪庫跑路”的危險(xiǎn)操作。
當(dāng)然這只是為了舉例的簡單例子,實(shí)際項(xiàng)目中,發(fā)生命令注入的原因大多是沒有對(duì)用戶所輸入的內(nèi)容進(jìn)行嚴(yán)謹(jǐn)?shù)男r?yàn)。
命令注入 - 常見威脅
命令注入是 Node.js 生態(tài)系統(tǒng)中真實(shí)而普遍的威脅。
看似顯而易見的安全風(fēng)險(xiǎn),如以下代碼所示:
var exec = require('child_process').execSync var platform = require('os').platform() module.exports = function(){ var commands = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.apply(arguments) var command = null commands.some(function(c){ if (isExec(findCommand(c))){ command = c return true } }) return command } function isExec(command){ try{ exec(command, { stdio: 'ignore' }) return true } catch (_e){ return false } } function findCommand(command){ if (/^win/.test(platform)){ return "where " + command } else { return "command -v " + command } }
上述命令注入漏洞是在 find-exec[6] npm 軟件包中發(fā)現(xiàn)的,該軟件包每周的下載量多達(dá) 2000 多次。雖然數(shù)量不多,但足以讓一些用戶面臨風(fēng)險(xiǎn)。命令注入漏洞的后果可能是毀滅性的,從數(shù)據(jù)泄露到系統(tǒng)完全崩潰不等。
現(xiàn)在我們再回過頭來看,到底什么是命令注入[7]?簡而言之,命令注入的核心是應(yīng)用程序允許未經(jīng)審核的用戶所輸入的內(nèi)容作為系統(tǒng)命令執(zhí)行。這些命令可操縱底層系統(tǒng),可能導(dǎo)致未經(jīng)授權(quán)的訪問、數(shù)據(jù)泄露,甚至完全破壞系統(tǒng)。
fs-git
在另外一個(gè)案例中,我們可以看下fs-git npm 軟件包(版本 1.0.1)這個(gè)看似無害的模塊是如何成為一個(gè)嚴(yán)重的安全隱患的:
fs-git 是 Node.js 的一個(gè) npm 包,能夠?yàn)?Git 倉庫提供類似于文件系統(tǒng)的 API,進(jìn)而可以讓開發(fā)人員更直觀、更容易地與 Git 倉庫交互。它擁有相當(dāng)數(shù)量的用戶群體,所以該安全隱患所造成的影響可見一斑。
在1.0.1 版本的 fs-git 模塊中,被發(fā)現(xiàn)了編號(hào)為 CVE-2017-1000451[8]漏洞。該模塊依賴 child_process.exec 函數(shù)來執(zhí)行系統(tǒng)命令。然而,用于構(gòu)建執(zhí)行字符串的 buildCommand函數(shù)缺少嚴(yán)謹(jǐn)?shù)男r?yàn)邏輯,使其容易受到命令注入的攻擊。
以下是fs-git 中存在漏洞的代碼片段:
showRef(): Promise<RefInfo[]> { let command = this._buildCommand("show-ref"); return new Promise((resolve: (value: RefInfo[]) => void, reject: (error: any) => void) => { child_process.exec(command, { maxBuffer: maxBuffer }, (error, stdout, stderr) => { if (error) { reject(error); } else { let list = stdout.toString("utf8").split("\n").filter(line => !!line); let resultList:RefInfo[] = list.map(str=> { let columns = str.split(" ", 2); 返回 { gitDir: this.path、 ref: columns[0]、 name: columns[1] }; }); resolve(resultList);
最終,代碼還將調(diào)用 _buildCommand 函數(shù),其中包含字符串連接和用戶提供的數(shù)據(jù):
_buildCommand(...args: string[]): string { return `git --git-dir=${this.path} ${args.join(" ") }`;
當(dāng)攻擊者篡改傳遞給 fs-git 模塊的數(shù)據(jù)以制作利用命令注入漏洞的惡意代碼時(shí),攻擊就展開了。通過提供精心制作的輸入,攻擊者能夠向系統(tǒng)注入任意命令。這樣,攻擊者就可以利用運(yùn)行進(jìn)程的權(quán)限執(zhí)行未經(jīng)授權(quán)的命令,從而可能危及主機(jī)系統(tǒng)。
該漏洞影響深遠(yuǎn)。攻擊者可以執(zhí)行任意命令,其中可能包括外泄敏感數(shù)據(jù)、修改文件甚至破壞系統(tǒng)正常運(yùn)行等操作。對(duì)于依賴fs-git的項(xiàng)目和應(yīng)用程序來說,這個(gè)漏洞構(gòu)成了重大的安全風(fēng)險(xiǎn)。
這個(gè)案例充分說明了校驗(yàn)用戶所輸入的內(nèi)容和必要的數(shù)據(jù)清除在防止命令注入漏洞方面的重要性。
所以即使是看似無害的模塊,如果不遵循安全編碼實(shí)踐,也會(huì)帶來嚴(yán)重的安全風(fēng)險(xiǎn)。開發(fā)者在處理用戶所輸入的內(nèi)容的數(shù)據(jù)時(shí)必須十分謹(jǐn)慎。
安全建議
對(duì)于NodeJs項(xiàng)目,我們可以大致從以下幾點(diǎn)入手,從而減少命令注入的風(fēng)險(xiǎn):
- 使用ORM(對(duì)象關(guān)系映射)庫:使用ORM庫可以幫助處理數(shù)據(jù)庫查詢,避免手動(dòng)拼接SQL語句,從而減少SQL注入的風(fēng)險(xiǎn)。
譬如,筆者就在曾經(jīng)的Egg.js項(xiàng)目中使用過的Sequelize[9] ORM庫來執(zhí)行安全的數(shù)據(jù)庫操作。
- 校驗(yàn)嚴(yán)謹(jǐn)
對(duì)用戶所輸入的內(nèi)容進(jìn)行校驗(yàn)和過濾,以防止惡意輸入
- 遵循安全編碼規(guī)范
避免直接拼接用戶所輸入的內(nèi)容到命令字符串、使用安全的文件路徑拼接方法等。確保在代碼中進(jìn)行輸入校驗(yàn)和輸出轉(zhuǎn)義,并注意處理用戶所輸入的內(nèi)容時(shí)的邊界情況。
- NPM Audit & NSP
使用經(jīng)過安全審計(jì)和更新頻繁的第三方庫,以減少潛在的安全漏洞。另外還可以使用工具如npm Audit[10]或NSP(Node Security Platform)[11]來檢查項(xiàng)目依賴的安全性。
回過頭看
假設(shè)項(xiàng)目中需要使用到exec[12]和spawn[13]方法時(shí),如果沒有適當(dāng)?shù)臄?shù)據(jù)清理和校驗(yàn),用戶所輸入的內(nèi)容可能被惡意利用,導(dǎo)致命令注入攻擊。
以下是一個(gè)簡單Demo說明這些類似的場景:
const { exec, spawn } = require('child_process'); // 示例:使用exec執(zhí)行命令 function executeCommandWithExec(userInput) { const command = `ls ${userInput}`; // 拼接用戶所輸入的內(nèi)容的命令 exec(command, (error, stdout, stderr) => { if (error) { console.error(`執(zhí)行命令出錯(cuò):${error}`); return; } console.log(`命令執(zhí)行結(jié)果:${stdout}`); }); } // 示例:使用spawn執(zhí)行命令 function executeCommandWithSpawn(userInput) { const command = 'ls'; const args = [userInput]; // 將用戶所輸入的內(nèi)容作為命令行參數(shù) const child = spawn(command, args); child.stdout.on('data', (data) => { console.log(`命令執(zhí)行結(jié)果:${data}`); }); child.stderr.on('data', (data) => { console.error(`執(zhí)行命令出錯(cuò):${data}`); }); } // 測試示例 const userInput = '; rm -rf /'; // 惡意的用戶所輸入的內(nèi)容,嘗試刪除整個(gè)系統(tǒng) executeCommandWithExec(userInput); executeCommandWithSpawn(userInput);
在上面的示例中,executeCommandWithExec和executeCommandWithSpawn函數(shù)接受用戶所輸入的內(nèi)容,并將其用于執(zhí)行l(wèi)s命令。
然而,如果惡意用戶所輸入的內(nèi)容像; rm -rf /這樣的內(nèi)容,它會(huì)將rm -rf /命令添加到ls命令后面,進(jìn)而導(dǎo)致"刪庫跑路"的悲劇發(fā)生。
為了防止這種攻擊,應(yīng)該對(duì)用戶所輸入的內(nèi)容進(jìn)行適當(dāng)?shù)臄?shù)據(jù)清理和校驗(yàn)。
所以對(duì)于以上代碼,可以使用安全的執(zhí)行方法execFile和spawn,并將用戶所輸入的內(nèi)容作為命令行參數(shù)而不是直接拼接到命令中:
const { execFile, spawn } = require('child_process'); // 示例:使用execFile執(zhí)行命令 function executeCommandWithExecFile(userInput) { const command = 'ls'; const args = [userInput]; // 將用戶所輸入的內(nèi)容作為命令行參數(shù) execFile(command, args, (error, stdout, stderr) => { if (error) { console.error(`執(zhí)行命令出錯(cuò):${error}`); return; } console.log(`命令執(zhí)行結(jié)果:${stdout}`); }); } // 示例:使用spawn執(zhí)行命令 function executeCommandWithSpawn(userInput) { const command = 'ls'; const args = [userInput]; // 將用戶所輸入的內(nèi)容作為命令行參數(shù) const child = spawn(command, args); child.stdout.on('data', (data) => { console.log(`命令執(zhí)行結(jié)果:${data}`); }); child.stderr.on('data', (data) => { console.error(`執(zhí)行命令出錯(cuò):${data}`); }); } // 測試示例 const userInput = '; rm -rf /'; // 惡意的用戶所輸入的內(nèi)容,嘗試刪除整個(gè)系統(tǒng) executeCommandWithExecFile(userInput); executeCommandWithSpawn(userInput);
在上面的代碼實(shí)現(xiàn)中,executeCommandWithExecFile函數(shù)使用了execFile[14]方法來執(zhí)行命令,而executeCommandWithSpawn函數(shù)保持不變,仍然使用spawn方法執(zhí)行命令。
使用execFile方法可以避免將用戶所輸入的內(nèi)容直接拼接到命令中,這樣可以在一定程度上減少命令注入攻擊的風(fēng)險(xiǎn)。
總結(jié)
記住,無論采用哪種方案,主體思想都應(yīng)該是謹(jǐn)慎處理用戶所輸入的內(nèi)容,并進(jìn)行嚴(yán)謹(jǐn)?shù)男r?yàn),以確保代碼的安全性。
以上就是預(yù)防NodeJS命令注入的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于預(yù)防NodeJS命令注入的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Node.js實(shí)現(xiàn)鏈?zhǔn)交卣{(diào)
這篇文章介紹了Node.js實(shí)現(xiàn)鏈?zhǔn)交卣{(diào)的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07利用yarn代替npm管理前端項(xiàng)目模塊依賴的方法詳解
這篇文章主要給大家介紹了關(guān)于利用yarn代替npm管理前端項(xiàng)目模塊依賴的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09Nodejs回調(diào)加超時(shí)限制兩種實(shí)現(xiàn)方法
這篇文章主要介紹了Nodejs回調(diào)加超時(shí)限制兩種實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2017-06-06基于html5和nodejs相結(jié)合實(shí)現(xiàn)websocket即使通訊
HTML5 擁有許多引人注目的新特性,如 Canvas、本地存儲(chǔ)、多媒體編程接口、WebSocket 等等。雖然現(xiàn)在大家把它捧的很火的樣子,但是個(gè)人認(rèn)為它還需要其他平臺(tái)的支持才能真正的"火起來"2015-11-11