Day 12,我測試伺服器把 q 放進行內 JavaScript 字串時,探針會不會跳出字串。Day 13 再把原始回應與瀏覽器執行證據交給 AI,檢查報告能否保留差異。這兩天的輸入都隨 HTTP 請求到達伺服器,原始回應也是重要證據。
今天換成瀏覽器端的資料流:我把探針放在網址 # 後的片段,讓前端 JavaScript 讀取,再寫進頁面。
這次我只測 3 個本機頁面:同一段片段輸入進入 innerHTML、進入 textContent,以及完全不被頁面使用。我要分開回答「伺服器看見什麼」「DOM 建立了什麼」和「探針是否執行」。
我在 3 個頁面都放了 <p id="result"></p>。前兩頁使用同一行程式,從 #q=... 讀取 q:
const value = new URLSearchParams(location.hash.slice(1)).get('q') ?? '';
兩頁只在寫入目標元素的方式不同;第三頁則完全不讀取片段:
| 頁面 | 寫入方式 | 本次要觀察的差異 |
|---|---|---|
/dom-vulnerable |
output.innerHTML = value |
輸入會不會被解析成 HTML 元素 |
/dom-safe |
output.textContent = value |
完整輸入能否只作為文字顯示 |
/dom-ignored |
output.textContent = '固定內容' |
片段存在時,目標元素是否仍不顯示它 |
innerHTML 會解析收到的字串並建立 DOM 元素;textContent 用於純文字內容,不把其中的標籤當成 HTML。
我給每組測試一個獨立識別碼,並把以下固定形狀的探針放進 fragment 的 q:
<img src="/day14-missing.png" alt="" onerror="document.documentElement.setAttribute('data-day14-xss','本次識別碼');alert(1)">
圖片路徑是本機刻意不存在的檔案。如果這段文字真的被解析成 <img>,載入失敗就會觸發 onerror。我要求 Chrome 同時捕捉到訊息為 1 的 alert,並在 <html> 讀回與本次識別碼相同的 data-day14-xss,才把本次探針標為已執行。只有圖片元素、只有對話框,或只有旗標,都不足以通過這個判定。
這裡沒有用 <script> 當探針。MDN 指出,經 innerHTML 插入的 <script> 元素不會因此執行,但事件處理屬性仍可能執行。若只拿 <script> 測試,沒有跳出對話框可能導致錯誤結論。
程式暫時在本地的 127.0.0.1 啟動網站,先保存每頁的原始 HTML 回應,再讓 Chrome 開啟含有 #q=... 的網址。3 頁都回傳 200 與 text/html; charset=utf-8;本次環境是 Python 3.14.4、Google Chrome 153.0.8010.36。
三份原始回應都沒有各自的探針識別碼。本機伺服器記下的 HTTP request target 也只有 /dom-vulnerable、/dom-safe、/dom-ignored 等路徑,沒有 #q=...。弱點頁另有 /day14-missing.png 的 404 請求,因為瀏覽器後來建立了探針圖片;這是圖片請求,不是原始頁面請求帶入了 fragment。Chrome 也可能請求 /favicon.ico。
因此,「原始回應沒有探針」在這次只能證明伺服器沒有把本次識別碼放進 HTML。它不能回答前端腳本是否把片段輸入放進 DOM。
弱點頁的瀏覽器從 fragment 讀回完整探針,#result 中形成了真正的 <img>,其獨立 onerror 屬性包含本次識別碼。Chrome 捕捉到 alert(1),而 <html> 的 data-day14-xss 是 day14-5f8c23553468a3c0,與送出的識別碼一致。依前面的雙重條件,本次固定探針在這個本機頁面上執行,分類為 confirmed_dom_xss。
文字寫入頁也從 fragment 讀回完整探針,但 #result 的 textContent 就是整段字面文字;讀取其 innerHTML 時,開頭的 <img 呈現為 <img。DOM 沒有建立圖片元素,也沒有 alert 或相符旗標。本次分類是 fragment_rendered_as_text,不能把「畫面上看得到 onerror 文字」寫成「事件處理屬性已形成」。
忽略片段頁的網址同樣帶著探針;檢查程式可以從瀏覽器位置讀到它,但頁面腳本只寫入「固定內容」,沒有讀取 fragment。#result 沒有本次探針,也沒有執行訊號,因此分類為 fragment_not_rendered。這與文字寫入頁的「有顯示,但只作為文字」是兩種不同結果。
| 組別 | 原始回應含探針 | 目標 DOM | alert(1)/相符旗標 |
分類 |
|---|---|---|---|---|
innerHTML |
否 | 建立帶 onerror 的 <img> |
兩者都有 | confirmed_dom_xss |
textContent |
否 | 完整探針文字,沒有 <img> |
兩者都沒有 | fragment_rendered_as_text |
| 忽略片段 | 否 | 固定內容 | 兩者都沒有 | fragment_not_rendered |
完整原始回應、每組 JSON、SHA-256、伺服器收到的請求路徑和摘要都保存在 day-14/results/。我也執行了 4 個本機瀏覽器與輸入限制測試,全部通過;測試涵蓋探針未進入 HTTP 回應、DOM 結構、雙重執行訊號,以及拒絕遠端目標與無效識別碼。
這次的關鍵不在回應裡搜尋 alert(1)。3 頁的原始回應都沒有探針,只有弱點頁的前端程式把 fragment 送進 innerHTML,讓它變成帶事件處理屬性的 DOM 元素。
本次直接支持的結論只限於本機 URL fragment 的 q、這 3 個 DOM 寫入情境和固定的 img onerror 探針。沒有測試其他 DOM 來源、其他寫入點、CSP、真實使用者或正式風險等級;textContent 頁與忽略片段頁的未執行結果,也不能外推為整站安全。今天沒有把結果交給 AI 產生風險報告,先把瀏覽器端的證據留清楚。
那就…
明天見!