跨站腳本(Cross-Site Scripting,XSS)發生在網站把使用者輸入的特殊字元未經 sanitize 直接渲染,導致瀏覽器執行攻擊者注入的 JavaScript。最經典案例是 2005 年 10 月由 Samy Kamkar(薩米·卡姆卡)在 Myspace 上釋放的 Samy Worm:他在自己的 profile 上注入 JavaScript,任何登入的訪客一打開他的頁面,瀏覽器就會自動把訪客變成他的好友、改寫訪客 profile 寫上「but most of all, samy is my hero」並繼續複製到其他頁面,引發跨使用者擴散。

這條蠕蟲讓 Kamkar 被搜索住處並認罪——XSS 看似只是「彈出一個 alert」,但放大效應可以是全站性災難。

危險字元與 sanitize#

幾個會打開攻擊面的字元:

  • 雙引號("
  • 單引號('
  • 角括號(<>

正確 sanitize 後應該以 HTML 實體呈現:

原字元HTML 實體
"&quot;&#34;
'&apos;&#39;
<&lt;&#60;
>&gt;&#62;

最直白的測試 payload:

<script>
  alert(document.domain);
</script>

alert 跳出視窗顯示當前 DOM 的 domain——驗證 XSS 在哪個 origin 上執行很關鍵。

同源政策(SOP)與 XSS 的影響#

瀏覽器以同源政策(Same-Origin Policy,SOP) 限制不同來源(origin)之間的互動。Origin 由 協定 + 主機 + 連接埠 三者構成(IE 例外不看 port):

URL同源於 http://www.example.com/原因
http://www.example.com/countries
https://www.example.com/countries協定不同
http://store.example.com/countries主機不同
http://www.example.com:8080/countries連接埠不同

兩個特殊情況:about:blankjavascript: URI 繼承呼叫者的 origin。所以 javascript:alert(document.domain)www.example.com 上開啟時,alert 會顯示 www.example.com

評估 XSS 影響時的判斷點:

  • 受害網站是否在敏感 Cookie 上設了 httponly?沒設 → 可竊取 session
  • 即便 Cookie 拿不到,是否能透過 DOM 取使用者資料、代為操作?
  • XSS 執行時的 document.domain 是不是受害網站本身?若在 sandboxed iframe 裡的 alert,可能無傷大雅

注入點不只 <script>#

並非每個地方都能直接塞 <script>。以下是常見的「在屬性內或變數內」注入:

屬性內注入#

<input type="text" name="username" value="hacker" width="50px" />

value 改成 hacker" onfocus=alert(document.cookie) autofocus "

<input type="text" name="username" value="hacker" onfocus=alert(document.cookie)
autofocus "" width=50px>

autofocus 讓欄位載入時自動取得焦點,onfocus 立刻觸發 alert。

autofocus 在隱藏欄位失效;多個 autofocus 元素時依瀏覽器取第一個或最後一個。

<script> 內變數注入#

<script>
  var name = "hacker";
</script>

如果 hacker 可控,把它換成 hacker';alert(document.cookie);'

<script>
  var name = "hacker";
  alert(document.cookie);
  ("");
</script>

' 關閉變數、; 結束句子、注入 alert,最後 ' 確保語法仍然合法。

Cure53 維護的 https://html5sec.org/ 是 XSS payload 的權威參考。

XSS 的分類#

Reflected XSS(反射型)#

惡意 payload 透過單次 HTTP 請求送出後立即被回應渲染、執行——payload 不被儲存。Chrome、IE、Safari 早期內建 XSS Auditor 試圖阻擋(Microsoft 已於 2018 年宣布在 Edge 退役)。Auditor 不時被繞過,相關技巧可參考:

Figure 7-1: 被 Google Chrome XSS Auditor 阻擋的頁面

Stored XSS(儲存型)#

payload 被儲存到伺服器,渲染時才執行。風險最高——可能在多個頁面、不同時間爆發。

子分類#

  • DOM-based XSS:JavaScript 操作既有 DOM 時引入了惡意輸入,例如:
    <script>
      document.getElementById("name").innerHTML = location.hash.split("#")[1];
    </script>
    造訪 www.example.com/hi#<img src=x onerror=alert(document.domain)> 即可觸發
  • Blind XSS:payload 在駭客看不到的位置執行(例如管理員後台),需用工具驗證。Matthew Bryant 的 XSSHunter 是經典工具
  • Self XSS:只能影響自己的 XSS,多數計畫不付錢;要把它變武器需結合 login/logout CSRF 等手法(Jack Whitton 在 Uber 的案例:https://whitton.io/articles/uber-turning-self-xss-into-good-xss/

案例 1:Shopify Wholesale#

漏洞描述#

Shopify wholesale 首頁的搜尋框,輸入會被原樣插入既有的 <script> 標籤內。HTML 字元被編碼,但放在 JavaScript 字串字面值內,依舊可破。

研究者送入:

test';alert('XSS');'

伺服器產生:

var search_term = 'test';alert('XSS');'';

Takeaways#

永遠檢視原始碼view-source:URL)確認 payload 落點。落在 HTML 還是落在 <script> 內字串,所需的 payload 完全不同。

Figure 7-2: https://nostarch.com/ 的頁面原始碼

案例 2:Shopify Currency Formatting#

漏洞描述#

商店設定的「貨幣格式」欄位輸入未經 sanitize 就出現在「社群媒體銷售通路」頁面。Shopify 用 Liquid 範本引擎,正常值是 ${{amount}};攻擊者改成:

Figure 7-3: 回報當下的 Shopify 貨幣設定頁

${{amount}}"><img src=x onerror=alert(document.domain)>

"> 關閉既有 HTML 標籤,<img onerror> 觸發 XSS。XSS 不是在貨幣設定頁觸發,而是在另一位管理員打開銷售通路頁時觸發

Takeaways#

XSS payload 不一定立即執行。要把所有「該值可能被渲染」的位置都走過一遍,才知道真正的觸發場景。

案例 3:Yahoo! Mail Stored XSS#

漏洞描述#

Yahoo! Mail 編輯器允許嵌入 <img> 標籤,且把 onloadonerror 等 JS 屬性過濾掉。但 Jouko Pynnonen 利用了 Boolean 屬性的解析瑕疵

<input type="checkbox" checked="hello" name="check box" />

Yahoo 把 "hello" 拿掉但保留等號

<input type="checkbox" checked="NAME" ="check box" />

HTML 規範允許等號旁邊有空白,於是瀏覽器解析成 CHECKED 的值是 NAME="check、第三個屬性叫 box——整個結構錯位。Pynnonen 利用此邏輯:

<img
  ismap="xxx"
  itemtype="yyy style=width:100%;height:100%;position:fixed;left:0px;top:0px; onmouseover=alert(/XSS/)//"
/>

Yahoo! 過濾後變成:

<img ismap= itemtype='yyy'
style=width:100%;height:100%;position:fixed;left:0px;top:0px;
onmouseover=alert(/XSS/)//>

<img> 鋪滿整個瀏覽器視窗,使用者一移動滑鼠 onmouseover 就觸發 XSS。

Takeaways#

當網站「修改」使用者輸入做 sanitize(而非編碼或轉義),思考開發者隱含的假設:兩個 src 屬性會怎樣?空白被換成 / 會怎樣?Boolean 屬性帶值會怎樣?這些角落很容易藏漏洞。

漏洞描述#

Mahmoud Jamal 注意到圖片搜尋結果的 URL:

http://www.google.com/imgres?imgurl=https://lh3.googleuser.com/...

imgurl 直接被當作 <a href>。他改成 javascript:alert(1)——href 也跟著變:

<a href="javascript:alert(1)">...</a>

javascript: URI 不需特殊字元繞過,且因為繼承父頁 origin,可在 google.com 的上下文中執行。

但點擊時 Google 的 onmousedown 又會把 URL 過濾。Jamal 換了招——按 Tab 把焦點移到 View Image 後按 Enter,不經過滑鼠點擊,過濾沒觸發,XSS 成功。

Takeaways#

只要 URL 參數會反映到頁面,就值得測;如果結果落在 href 中,可以用 javascript: URI 繞過字元過濾。連 Google 這種大公司都會有這種漏洞,別因為對方有名氣就放棄嘗試。

案例 5:Google Tag Manager Stored XSS#

漏洞描述#

Patrik Fehrenbach 嘗試在 Google Tag Manager 的 Web 表單注入 #"><img src=/ onerror=alert(3)> 都被 sanitize。但 Tag Manager 還支援上傳 JSON 檔批次匯入:

"data": {
  "name": "#\"><img src=/ onerror=alert(3)>",
  "type": "AUTO_EVENT_VAR",
  "autoEventVarMacro": {
    "varType": "HISTORY_NEW_URL_FRAGMENT"
  }
}

Google 在「送入時 sanitize」而非「渲染時 sanitize」——上傳路徑被忘記了。

Takeaways#

  • 找替代輸入路徑:Web 表單、JSON 上傳、API、行動 App 等
  • 最佳實務是「在渲染時 sanitize」而非「在送入時 sanitize」——前者較不易因新增輸入入口而遺漏

案例 6:United Airlines XSS#

漏洞描述#

Mustafa Hasan 發現 checkin.united.comSID 參數直接寫入 HTML,但他注入 "><svg onload=confirm(1)> 並未觸發。原因是站上載入了一段「XSS 防禦」JavaScript,把 alertconfirmpromptdocument.write 等函式都用 proxy 重寫成 no-op:

XSSObject.proxy(window, "alert", "window.alert", false);
XSSObject.proxy(window, "confirm", "window.confirm", false);
XSSObject.proxy(window, "prompt", "window.prompt", false);
XSSObject.proxy(document, "write", "document.write", false);

Hasan 注意到名單漏了 writeln。他與 Rodolfo Assis 合作後產出:

";}{document.writeln(decodeURI(location.hash))-"#<iframe src=javascript:alert(document.domain)><iframe>

關鍵設計:

  • ";} 關閉既有 JS 區塊
  • {document.writeln(decodeURI(location.hash)) 把 URL 的 fragment 解碼後寫入 DOM
  • -" 修補語法
  • #<iframe src=javascript:alert(document.domain)> 在 fragment 內注入 <iframe>,src 用 javascript: URI

<iframe> 是「全新 HTML 文件」,不會載入父頁的 XSS Filter;又因為 javascript: 繼承父 origin,alert 顯示 www.united.com——XSS 確認成立。

Takeaways#

  1. 持續挖掘:payload 沒觸發時,去看為什麼
  2. 看到黑名單就要警覺:黑名單是漏洞的明燈,因為總有一個函式被忘記列入
  3. JavaScript 知識是必備:複雜 XSS 的驗證需要靈活操作 DOM 與 prototype

章末總結#

  • XSS 是長期高發的漏洞,最簡單的測試 payload 是 <img src=x onerror=alert(document.domain)>
  • 影響取決於:reflected vs stored、是否能讀 Cookie、執行時的 origin、能否操作 DOM
  • 常被忽略的測試點:
    • 修改式」sanitize 的邊界(Boolean 屬性帶值、雙重屬性、空白被替換等)
    • 替代輸入路徑(JSON、API、檔案上傳、行動 App)
    • URL 參數被反映到 href → 試 javascript: URI
    • 黑名單防禦——找漏掉的函式或屬性
  • payload 不一定立刻執行,要把該值可能出現的所有頁面走過一遍