NodeJS命令列注入:示例及預防

Jaxu發表於2024-04-26

  在本文中,我們將學習如何在NodeJS中使用命令列函式進行注入漏洞攻擊。

  現代網站可以是一個複雜的軟體,它由許多分佈在不同環境中的部分組成。如果你的應用程式沒有得到有效的保護,那麼分佈在這些環境中的每一個組成部分都有可能受到命令列注入漏洞的攻擊。

  本文將介紹如何在NodeJS中使用shell命令列函式時進行注入漏洞攻擊。同時我們還將探討一些有關如何防範這些攻擊的技術。

  下面讓我們開始吧!

什麼是命令列注入漏洞?

  簡單來講,當你的應用程式接受不安全的使用者輸入並將輸入的內容作為執行作業系統命令的引數時,就有可能產生這樣的漏洞。命令列注入攻擊的目的是透過合法的命令,以便攻擊者在目標系統中執行任意命令。這些輸入可以來自任意使用者可修改的源,例如網頁表單、cookies、HTTP頭等。

  注意命令列注入攻擊與程式碼注入攻擊不同。後者主要破壞應用程式本身,例如前端JavaScript程式碼注入攻擊可能會在使用者的瀏覽器上執行一些惡意程式碼。但攻擊通常會被限制在瀏覽器範圍內。而命令列注入攻擊的目標則是底層作業系統,因此更具破壞性。

攻擊過程解析

  讓我們看一下上面這張圖。系統接受使用者的輸入並組成一個完整的命令。假設該輸入是有效的,並且使用者沒有輸入惡意的命令,那麼系統將執行命令並返回正確的結果。

  但是,如果惡意使用者想要嘗試系統的漏洞,那麼他們可以在命令的後面附加自己的命令來過載應用程式的系統命令。例如,在Windows中的DOS命令列,你可以使用&符號附加額外的命令。在Linux系統中,你可以使用;符號達到相同的效果。此時應用程式將執行多個命令並將結果返回給使用者。

  攻擊者甚至都不用關心命令的返回結果。如果由於某種原因應用程式沒有返回結果,那麼攻擊者可以傳送curl之類的命令來ping被攻擊的伺服器,以確認命令列注入攻擊是否成功。這使得透過自動化工具來查詢和攻擊具有這種型別漏洞的應用程式和網站變得更加容易。

  下面讓我們透過一個實際的例子來了解更多有關命令列注入漏洞的內容。

一個存在漏洞的網站示例

  我們將建立一個小的有風險的網站來演示這個漏洞。我們的網站是眾多介紹shell命令的網站中的一個,它教你學習shell命令並幫助你如何透過命令列出系統目錄中的內容。它接受使用者輸入目錄的完整路徑,然後返回目錄中的內容。

  讓我們開始setup。

設定

  首先確保你已經安裝了需要的NodeJSnpm。如果沒有的話,需要先安裝。

  接下來,執行以下命令來初始化專案。該示例假設你在Mac或Linux環境中執行命令,或者在Windows WSL2上執行。

mkdir nodejs-command-injection
cd nodejs-command-injection
npm init -y
npm install express
npm install pug

  這些命令將建立專案資料夾並安裝ExpressPug。我們將使用Express的web伺服器功能來載入網站,然後使用Pug來實現一些快速模板功能。

新增功能

  現在讓我們建立一個非常簡單的頁面,該頁面接受使用者輸入目錄的路徑。建立檔案nodejs-command-injection/server.js,並複製下面的程式碼:

const express = require('express');
const {exec} = require('child_process');
const app = express();
const port = 3000;
const pug = require('pug');

// Listen in on root
app.get('/', (req, res) => {
  const folder = req.query.folder;
  if (folder) {
    // Run the command with the parameter the user gives us
    exec(`ls -l ${folder}`, (error, stdout, stderr) => {
      let output = stdout;
      if (error) {
        // If there are any errors, show that
        output = error; 
      }
      res.send(
        pug.renderFile('./pages/index.pug', {output: output, folder: folder})
      );
    });
  } else {
    res.send(pug.renderFile('./pages/index.pug', {}));
  }
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

  接下來,新增模板檔案以顯示錶單。建立檔案nodejs-command-injection/pages/index,並複製下面的程式碼:

html 
  head 
    title="NodeJS Command Injection Example"

  body 
    h1="List Folders"  

    form(action="/" method="GET") 
      input(type="text" name="folder" value=folder) 
      button(type="submit") Submit

    code 
      pre #{output}

  很好。我們使用Pug簡潔的標記風格建立了一個簡單的HTML頁面。

  現在我們可以透過執行這個命令來啟動應用程式:node server.js。如果執行正常,在瀏覽器中輸入http://localhost:3000/,你應該可以看到以下頁面。

  如果一切進展順利,接下來讓我們詳細檢視漏洞。

探查漏洞

  提醒一下,我們假設你是在基於Mac、Linux或Windows WSL2的環境中執行該網站。這一點很重要,因為我們將在接下來的示例中使用相應的命令在資料夾中查詢內容。

  現在,讓我們來測試一下我們的網站。輸入/usr並點選Submit按鈕。你應該看到資料夾中的內容輸出到列表中。

  很好,看起來我們的網站實現了預期的功能。如果你在輸入框中輸入不同的命令,例如ps,則無法正常工作,它只列出了資料夾。但真的是這樣嗎?試試在輸入框中輸入/usr;ps

  嗯,出問題了!我們列出了託管我們網站的伺服器上的所有使用者。這完全有可能,因為我們使用分號作為命令的分隔符,這將告訴shell執行兩個單獨的命令。

  如果你檢視下面的程式碼,可以看到ls命令首先執行,然後命令與使用者的輸入連線起來。程式碼原本期望只執行一個命令,但是如果使用者破壞了這個過程,那麼就可以在伺服器上執行他想要的任何命令了。

...
exec(`ls -l ${folder}`, (error, stdout, stderr) => {
....

  那如何改進我們的程式碼呢?接下來我們將介紹幾個可用的選項。

防止命令列注入

  如上所述,這個漏洞存在的原因是我們為惡意使用者開啟了一扇可以輸入任意內容的大門。理想情況下,不應該將使用者輸入的任意內容傳遞到shell命令。這種做法是非常糟糕的,應該絕對避免。

  說到這裡,假設在極少數的情況你恰好需要以上示例中所演示的這個功能,那如何才能避免命令列注入漏洞呢?

驗證輸入

  不僅是對於我們的示例網站而言,在多數情況下我們都應該遵循:永遠不要相信使用者輸入的內容。應該始終對使用者輸入的內容進行過濾處理,從而避免惡意使用者將其用作破壞性命令的傳遞方法。

  至少,你應該做到以下幾點:

  1. 使用白名單以確保只有允許的命令和引數進入系統執行。
  2. 對輸入的內容進行驗證,並確保不允許某些特殊字元進入系統,而僅允許你認為有效的字元可以被輸入。

使用execFile()函式代替exec()函式

  NodeJS有一個非常方便的函式execFile,它允許你直接呼叫一個可執行的檔案,而不是使用原始的shell訪問。使用這個函式會將整個執行過程更加安全,因為使用者不能執行任何額外的命令以及以引數的方式傳遞給命令列來執行。

  如果我們更新程式碼,它看起來像這樣:

const {exec, execFile} = require('child_process');
    ...
    app.get('/', (req, res) => {
      const folder = req.query.folder;
      ...
      execFile(`/usr/bin/ls`, ['-l', folder], (error, stdout, stderr) => {
         ...
      }
    }

使用API級別的函式代替Shell命令

  避免命令列注入漏洞最安全的方法之一是透過使用程式設計環境自帶的函式來替換Shell命令——本例中我們使用NodeJS程式設計環境。NodeJS已經內建了用來列出目錄內容的函式,我們只需要在程式碼中匯入檔案系統模組就可以使用這些函式。

  下面的程式碼顯示了一個使用fs模組的示例。

const fs = require('fs');
  const folder = req.query.folder;
  if (folder) {
    // Read the files in the folder using the fs module
    fs.readdir(folder, function (err, files) {
      //handling error
      if (err) {
        return console.log('Unable to scan directory: ' + err);
      }
      let fileOutput = '';
      files.forEach(function (file) {
        fileOutput += `${file}\n`;
      });
      res.send(
        pug.renderFile('./pages/index.pug', {
          output: fileOutput,
          folder: folder,
        })
      );
    });
  }

  這種方法比我們一開始使用的方法要安全得多。在完成核心功能之前,探索程式設計環境本身提供了哪些功能總是一個不錯的主意!

總結

  在本文中,我們建立了一個帶有命令列注入漏洞的示例網站。我們還研究了一些可選項,以便更好地保護自己的網站免受這些型別的攻擊。

  接下來,如果有興趣我們可以繼續瞭解命令列注入漏洞如何影響其它的程式設計環境,比如RustJava。另外一個有趣的點是NodeJS中的SQL隱碼攻擊指南。

  Shell訪問是一種強大的工具,同時它也是一把雙刃劍。在使用Shell命令之前,首先要考慮我們是否一定需要它。畢竟,當你只需要一個鑿子時,為什麼要用電鑽呢?

原文地址:NodeJS Command Injection: Examples and Prevention

相關文章