範本引擎(Template Engine)負責動態產生網站、Email 等媒體內容——它在渲染時把佔位符替換成實際資料,方便開發者把應用邏輯與呈現邏輯分離。例如使用者個資頁可能只用一個範本,由佔位符填入名稱、Email、年齡等欄位。範本引擎通常還提供輸入 sanitize 與 HTML 產生簡化等好處,但不代表它們本身免於漏洞。
兩種範本注入#
範本注入(Template Injection)發生在引擎渲染使用者輸入時未正確 sanitize,嚴重時可導致遠端程式碼執行(RCE,第 12 章)。
伺服器端範本注入(Server-Side Template Injection,SSTI)#
注入發生在伺服器端邏輯,可能藉由範本引擎所綁定的程式語言執行任意程式碼。能達到何種程度視引擎與站點防護而定:
- Python Jinja2:曾允許任意檔案存取與 RCE
- Ruby ERB(Rails 預設):同樣可被串成 RCE
- Shopify Liquid:Ruby 引擎,刻意限制可呼叫的方法以防 RCE
- 其他常見引擎:PHP 的 Smarty、Twig;Ruby 的 Haml、Mustache
各引擎語法不同,必須先確認站點使用什麼技術——可用 Wappalyzer ↗ 或 BuiltWith ↗。常見的測試 payload:
- Smarty:
{{7*7}}→ 期望輸出49- ERB:
<%= 7*7 %>→ 期望輸出49
客戶端範本注入(Client-Side Template Injection,CSTI)#
注入發生在客戶端 JavaScript 範本引擎。常見:Google AngularJS、Facebook ReactJS。
- 無法直接 RCE,但可達到 XSS
- ReactJS:預設保護完善;要找漏洞,看 JS 中是否有可控輸入餵給
dangerouslySetInnerHTML——這個函式會故意繞過 XSS 防護 - AngularJS < 1.6:內建 Sandbox,但歷年陸續被研究者繞過。在開發者工具輸入
Angular.version確認版本
AngularJS 1.3.0 ~ 1.5.7 的經典 Sandbox bypass:
{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(1),a')}}更多版本對應的 bypass 可在 https://pastebin.com/xMXwsm0N ↗、https://jsfiddle.net/89aj1n7m/ ↗ 找到。
即便
{{4+4}}被計算成8,站點可能還有額外防禦:例如過濾*、()、[],或限制最大字元數——這些都會讓 CSTI 看起來成立但難以實際利用。
案例 1:Uber AngularJS Template Injection#
- 難度:高
- URL:
https://developer.uber.com/- 回報來源:https://hackerone.com/reports/125027/ ↗
- 回報日期:2016-03-22
- 獎金:$3,000
漏洞描述#
PortSwigger(Burp Suite 開發商)首席研究員 James Kettle 在 Uber 開發者站找到 CSTI:
https://developer.uber.com/docs/deep-linking?q=wrtz{{7*7}}頁面渲染後輸出 wrtz49,證實 AngularJS 解析了 7*7。Uber 用的版本含有可繞過的 Sandbox。Kettle 提交完整 Sandbox bypass:
?q=wrtz{{(_="".sub).call.call({}[$="constructor"].getOwnPropertyDescriptor(_.__proto__,$).value,0,"alert(1)")()}}zzzz最終彈出 alert(1),證明 CSTI 可升級為 XSS,能威脅 Uber 開發者帳號與綁定的 App。
Takeaways#
找到 CSTI 後流程:
- 用
Angular.version確認版本- 1.6+ → 直接送 payload
- < 1.6 → 找對應版本的 Sandbox bypass
案例 2:Uber Flask Jinja2 Template Injection#
- 難度:中
- URL:
https://riders.uber.com/- 回報來源:https://hackerone.com/reports/125980/ ↗
- 回報日期:2016-03-25
- 獎金:$10,000
背景#
Uber 公開了「treasure map」標示各子網域所用技術。riders.uber.com 用 Node.js + Express + Backbone.js,但 vault.uber.com 與 partners.uber.com 用 Flask + Jinja2——後者是有 SSTI 風險的伺服器端範本引擎。如果一處改名觸發了這些子網域的 Email 寄送,就有跨站注入機會。
漏洞描述#
研究者 Orange Tsai 在 riders.uber.com 改名為 {{1+1}},收到的通知 Email 把名字顯示為 2——說明 Email 那端用 Jinja2 並評估了表達式。

Figure 8-1: Orange 收到的 Email——名字欄位執行了他注入的程式碼
進一步驗證:
{% for c in [1,2,3]%} {{c,c,c}} {% endfor %}收到的 Email 名稱被展開成九個數字。

Figure 8-2: Orange 注入更複雜程式碼後收到的 Email
為避免越權操作,Orange 只示範執行能力,並未進一步利用 Jinja2 的 introspection(運行時查詢物件屬性)來組成 RCE。要繼續探索影響時,先和計畫方確認——這既保護自己也讓對方能正確評估風險。
Takeaways#
即使主站不用某技術,子網域之間的互動也可能讓注入跨站發酵。本案的 payload 不是在
riders主站觸發,而是在 Email 寄出時由另一個子網域處理。
案例 3:Rails Dynamic Render(CVE-2016-0752)#
- 難度:中
- URL:N/A
- 回報來源:https://nvisium.com/blog/2016/01/26/rails-dynamic-render-to-rce-cve-2016-0752/ ↗
- 回報日期:2015-02-01
- 獎金:N/A
漏洞描述#
Rails 採 MVC 架構。Controller 中常以 render 渲染指定範本:
render params[:template]如果 template 參數是使用者可控,Rails 會遞迴搜尋:先找 /app/views,再 application root,再 server root。在 CVE-2016-0752 修補前,可送:
template=%2fetc%2fpasswd伺服器最終會找到 /etc/passwd 並印出。同樣的搜尋鏈可被用來達成任意程式碼執行:
<%25%3d`ls`%25>解碼後是 ERB 的 <%= \ls` %>`——直接執行 shell 指令。
即使官方 Rails 修補,仍要留意開發者把使用者輸入直接餵給
render inline:的情況——這條路依舊把字串當 ERB 解析。
Takeaways#
讀懂框架的「慣例優於設定(convention over configuration)」設計細節,能找出依賴慣例放大的攻擊面。
案例 4:Unikrn Smarty Template Injection#
- 難度:中
- URL:N/A
- 回報來源:https://hackerone.com/reports/164224/ ↗
- 回報日期:2016-08-29
- 獎金:$400
漏洞描述#
作者本人在 Unikrn(電競投注網站)的初步偵測時,Wappalyzer 標出 AngularJS。試 {{7*7}} 在 profile 沒中,改在「邀請朋友」功能;送出後收到的邀請信夾帶了 Smarty 錯誤堆疊——表示 Email 端是 Smarty。

Figure 8-3: 作者收到 Unikrn 的 Email,內含 Smarty 錯誤堆疊
進一步驗證:
- 把名字改成
{$smarty.version}→ 收到的邀請信顯示2.6.18(Smarty 版本) - 名字改成
{php}print "Hello"{/php}→ 邀請者顯示為Hello
最後構造完整 PoC:
{php}$s=file_get_contents('/etc/passwd',NULL,NULL,0,100);var_dump($s);{/php}收到的邀請信中出現 /etc/passwd 前 100 個字元(用 0, 100 分段是因為 Unikrn 對名稱長度有限制)。回報後一小時內修復。

Figure 8-4: Unikrn 邀請信顯示 /etc/passwd 檔案內容
Takeaways#
看到錯誤堆疊就追下去——「Where there’s smoke, there’s fire」。發現潛在 SSTI 後,讀引擎的官方文件找保留變數與標籤是高 CP 值步驟(如 Smarty 的
{$smarty.version}、{php}{/php})。
章末總結#
- 注入測試先確認技術棧(Wappalyzer / BuiltWith / 頁面源碼觀察
ng-等屬性) - SSTI 可達 RCE,CSTI 一般只能達 XSS
- 各引擎語法不同,先用簡單算式(
{{7*7}})測是否有評估 - 範本引擎的 Sandbox 可能被繞過,遇到舊版(如 AngularJS < 1.6、Jinja2)尤其值得試
- 漏洞不一定在送出時觸發;Email、PDF 匯出、後台頁面都是常被忽略的渲染點
- 找到 SSTI 後優先做能力證明(如 introspection),不要直接組 RCE,讓計畫方評估或主動詢問