💡 今日學習目標:突破「Web 資安就等於 SQL Injection 和 XSS」的認知盲點,擴展全方位的資安視野。
如果我們去問一位剛入行的 Web 開發者:「你覺得做 Web 資安要防範什麼漏洞?」
九成的回答往往是:
「嗯... 防範 SQL 注入(SQL Injection)跟跨站腳本攻擊(XSS)吧!有裝框架、ORM 好像就差不多了?」
但如果去問一位資安專家或滲透測試員,他們腦海中的防守地圖卻遠遠不止於此。
這就像是一座冰山:開發者看到的只有浮出水面的 SQLi 與 XSS;但在水面之下,隱藏著龐大且複雜的進階攻擊手法!
讓我們把這座冰山畫出來,看看開發者以為的資安,跟資安專家看到的攻擊面到底差多少:

為什麼會產生這麼巨大的認知落差?
因為開發者習慣專注於 「功能邏輯」 ,而駭客專注於 「解析器差異(Parser Discrepancies)、架構縫隙與非預期行為」 。
我們挑選三個開發者極易忽視、卻常被駭客拿來攻破系統的「盲點漏洞」來進行深度解析:
開發者常需要寫一些功能,例如「輸入圖片 URL,由伺服器幫忙抓取圖片」或「Webhook 通知」。
// ❌ 盲點範例:未限制伺服器向外請求的目標 (SSRF)
Function FetchWebImage(imageUrl):
// 伺服器直接幫前端發送 HTTP 請求去下載 imageUrl
Response = HTTPClient.Get(imageUrl)
Return Response.Body
⚠️ 駭客怎麼玩:攻擊者輸入
imageUrl = "http://169.254.169.254/latest/meta-data/"(AWS 雲端 Instance Metadata API) 或http://127.0.0.1:6379(內網 Redis)。伺服器會替攻擊者穿透外網防火牆,讀取雲端金鑰或存取內網敏感服務!
// ✅ 通用防禦通則:解析出真實 IP,封鎖內網與雲端 Metadata 區段
Function FetchWebImage(imageUrl):
// 1. 先把 URL 拆解出主機名稱,不要直接把整串 URL 丟給 DNS 解析
ParsedUrl = ParseUrl(imageUrl)
TargetIP = DNS.Resolve(ParsedUrl.Host)
// 2. 拒絕私有/環回/雲端 Metadata IP 區段
// 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
If IsPrivateOrInternalIP(TargetIP):
Return Error("Blocked: 目標網址解析到內部 IP")
Return HTTPClient.Get(imageUrl)
📌 但這還不是完整解:上面這段在「檢查 IP」與「實際發出連線」之間存在一個時間差,攻擊者可以利用 DNS Rebinding,趁這個空檔把網域重新指向內網位址。完整的防禦(直接連線到已驗證過的 IP、關閉自動轉址追蹤)我們留到 Day 21 一次講透。
現代 Web 框架(如 ORM、DTO 自動綁定)讓開發者可以輕鬆地把 HTTP 傳進來的 JSON 自動轉換為資料庫物件。
// ❌ 盲點範例:直接將前端 JSON 自動綁定至資料庫模型
Function UpdateUserProfile(userId, jsonPayload):
User = Database.Find(userId)
// 框架自動把 jsonPayload 的所有欄位覆蓋到 User 物件上!
User.UpdateAttributes(jsonPayload)
User.Save()
⚠️ 駭客怎麼玩:一般使用者修改個人資料只傳
{"name": "Bob"}。但攻擊者在 POST Body 中偷加{"is_admin": true, "role": "superuser"}。如果 User 資料表剛好有is_admin欄位,系統就自動幫他提權成管理員!
// ✅ 通用防禦通則:採用嚴格的 Data Transfer Object (DTO) / 允許白名單
Function UpdateUserProfile(userId, jsonPayload):
User = Database.Find(userId)
// 只顯式萃取允許修改的公開欄位 (Explicit Whitelisting)
User.Name = jsonPayload.GetString("name")
User.Bio = jsonPayload.GetString("bio")
// 絕不自動綁定權限、身分或內部狀態欄位
User.Save()
許多系統為了方便快取或傳輸,會把物件序列化(Serialize)成字串或 Base64 儲存,使用時再反序列化(Deserialize)還原成物件。
// ❌ 盲點範例:信任並直接反序列化來自客戶端的資料
Function ProcessSession(cookieData):
// 直接將 Cookie 解碼並還原成物件
UserSession = Deserialize(Base64Decode(cookieData))
Return UserSession
⚠️ 駭客怎麼玩:在 Java/Python/Node.js/PHP 等語言中,反序列化過程會自動觸發魔術方法(如
__wakeup或可執行的 Gadget Chain),攻擊者可構造惡意 Payload 直接在伺服器上執行任意系統指令(RCE)!
// ✅ 通用防禦通則:純資料格式 + 完整性簽章,絕不還原成任意物件
Function ProcessSession(cookieData, signature):
// 1. 先驗證簽章,確認資料沒有被竄改過(此時連解析都還沒開始)
If NOT HMAC.Verify(cookieData, signature, ServerSecretKey):
Return Error("Invalid session")
// 2. 使用純資料格式解析,不使用會「自動實例化任意類別」的原生序列化
Data = JSON.Parse(Base64Decode(cookieData))
// 3. 顯式取出需要的欄位,物件一律由自己的程式碼建立
Session = new UserSession()
Session.UserId = Data.GetInt("uid")
Session.ExpiresAt = Data.GetTimestamp("exp")
Return Session
🛡️ 防禦原理:反序列化漏洞的根源,是讓外部輸入決定了伺服器上要建立什麼類別的物件。改用 JSON 這類純資料格式,再由自己的程式碼把欄位一個一個明確搬進物件裡,攻擊者就失去了觸發 Gadget Chain 的入口。
而最根本的解法其實是:根本不要反序列化使用者傳來的資料。把 Session 狀態留在伺服器端,Cookie 裡只放一個無意義的 Session ID。這也是 Day 20 我們會再回來細談的做法。
💬 明日預告:【Day 05】SDLC 軟體開發流程:資安防守該從哪一個環節開始切入?
明天我們將探討如何把這些防守通則,完整無縫地嵌入專案的開發生命週期(SDLC)中!