資安小常識 - 為什麼不建議在網址用 QueryString (?a=...) 傳資料?
| | 9 | |
幫忙 Review 前端網頁程式,發現某網頁用了以下寫法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Query String Risk Demo</title>
<style>
table {
border-collapse: collapse;
width: 300px;
font-family: 微軟正黑體, Arial, sans-serif;
td {
padding: 6px;
border: 1px solid #ddd;
}
}
.hdr {
background-color: #eee;
text-align: right;
width: 100px;
}
</style>
</head>
<body>
<div>
<h2>申請資料已送出</h2>
<div>
<p>
親愛的客戶,我們已收到你的資料,我們將會盡快與您聯絡。
</p>
<table>
<tr>
<td class="hdr">姓名</td>
<td id="name"></td>
</tr>
<tr>
<td class="hdr">車號</td>
<td id="carNo"></td>
</tr>
</table>
</div>
</div>
<script>
const urlParams = new URLSearchParams(window.location.search);
const name = urlParams.get('name');
const carNo = urlParams.get('carNo');
document.getElementById('name').innerText = name;
document.getElementById('carNo').innerText = carNo;
</script>
</body>
</html>
身為老鳥,腦中頓時警鈴大作,母湯啊母湯~~ 年輕同學不知當中兇險,於是我便分享了不建議這麼做的理由,以及其可能帶來的問題。
想到可能有其他同學也不知道這種做法的問題所在,索性整理成一篇,方便未來再遇到時,能快速提供完整資訊。
要串接前端多個網頁,使用 Query String (?a=...&b=...) 是十分簡單直覺的做法,但用來傳送產品代號、文章序號、檢視模式... 控制顯示內容或狀態還行,若傳送的內容包含個人資料或是會直接顯示在網頁的資訊,就必須留意個資外流、網頁被偽造操控,甚至被 XSS (Cross Site Scripting) 的風險。
以下是我想到的一些風險:
- Query String 被視為網址的一部分,會在許多地方留下記錄(有些你可能想都沒想過),例如瀏覽器的歷程記錄:(所以借用別人的電腦一律開無痕是好習慣 😄)
防火牆、網站 Log 檔:
一旦你在 Query String 加上姓名、Email、電話等隱私,甚至身分證號或信用卡資料,等同一次把個資灑得到處都是,簡直在幫駭客助攻。 - URL 參數直接轉成網頁內容,永遠存在被竄改冒用的風險。
當惡意攻擊者發現你的網頁會將 URL 參數轉成網頁內容,便可以精心設計 URL 引誘不知情使用者點擊,好在你的頁面塞入詐騙內容,例如搞個縮網址https://reurl.cc/04yb06
,點選後在你的網頁塞入任意內容:
- 最糟糕的一種情況是 - 你還同時犯了低級錯誤 - 將 Query String 參數當成 HTML 內容,形成可插入惡意 Script 攻擊的大漏洞。延伸閱讀:什麼是 XSS 攻擊?如何防範?
例如,若網頁寫成document.getElementById('carNo').innerHTML = carNo;
(警告:這是不安全的寫法,勿使用),駭客便能參數塞入 JavaScript 在你的網頁執行:
如此一來,能玩的花樣就多了,偷 Cookie、偷 localStorage、導向詐騙網頁... 能搞出什麼招式端看駭客的想像力與創意。
透過以上說明與展示,大家應該已經知道用 Query String 傳遞資料的風險,那該怎麼修改呢?跨網頁傳遞參數的方法不少,URL 由於完全外露且易被修改濫用,能免則免,但有一些較安全的做法,以下列舉幾種:
- 將資料本體存入伺服器端(記憶體快取、資料庫、Redis...),隨機產生對映資料的 Token 字串(例如 GUID),用 Query String 傳送 Token,新網頁再用 Token 向伺服器提取資料顯示。
- 若來源與目的網頁位於同一網站(同一來源 Same Origin),則彼此可以透過 JavaScript 呼叫對方的函式、存取 DOM 物件,或者共用 Cookie、Local Storage (若屬機敏資料或個資,則也不建議存在客戶端),都比用 URL 傳送安全。
- 若來源與目的網頁分屬不同網站,可考慮使用 postMessage API。(延伸閱讀:前端單兵基本教練 - 使用 postMessage 與 IFrame 跨網站網頁互動)
以上大概是我這些年累積的經驗分享,如有缺漏謬誤,歡迎大家指正補充。
Using Query Strings to pass sensitive data in URLs can expose personal information to various risks, including URL logging and potential XSS attacks. This blog highlights these dangers and suggests safer alternatives such as server-side storage with tokens, same-origin JavaScript interactions, and cross-origin messaging with postMessage API.
Comments
# by 傻B韭菜
用 query string, 但是走的是 https 加密協定,中間人攻擊還能看得到 query string 傳遞的內容嗎? 印象中用 https 應該是看不到才對
# by Terry
所以要把query string加密 取得的時候在解密
# by Jeffrey
to 傻B韭菜,走 HTTPS 的話,直接攔截傳輸封包的確會因無法解密看不到內容(除非用中間人攻擊偷換憑證),但如果有用 HTTP Proxy,Proxy 端看得到。另外,Query String 在客戶端跟網站端都是明碼,這部分風險仍需考慮。
# by Jeffrey
to Terry,加密機制要審慎設計,若解密邏輯寫在客戶端(JavaScript)並使用固定金鑰,基本上是擋不住有心人的。
# by yoyo
這個case關鍵在前端網頁直接抓Query String,沒有做任何驗證處理 理論上資料應由後端驗證查詢再回傳前端顯示 永遠不要相信前端傳過來的資料! 當然Query String應避免直接傳輸敏感資料 HTTP POST body也相同,但大多數人認為放在body會比較安全,其實沒有!
# by Jeffrey
to yoyo,但大多數人認為放在body會比較安全 <= 硬要解釋的話,是因為 URL 他 X 的超級危險,改成 POST Body 後危險度下降,但照樣有被偽造攻擊的風險,還是不安全。:D
# by 菜鳥
Query的時候 至少那串要加密就還堪用
# by 野比小熊
所以最安全的方法是 https+參數加密+HMAC金鑰+後台在解密驗證
# by Jeffrey
to 野比小熊,我個人偏好把資料存在伺服器端,回傳一次性的隨機碼(如:GUID)當 Token,用 Token 當 QueryString 參數呼叫另一頁取回資料。 這種做法不需要管理金鑰,不用搞加解密,架構相對簡單。若要更安全,Token 可設計成只用一次或有時效性,就算被竊聽偷走也不能用來攻擊。 我覺得這樣就夠安全了。