本系列第 13 篇處理「PHP、Node.js、Python 的安全請求封裝」。重點不是背誦繞過字串,而是把外部輸入、URL 解析、DNS、實際連線、回應處理和網路權限逐層分開。只要能說清楚每層由誰決定、使用哪個解析結果,就能在設計與 Code Review 階段找出風險。
本篇會學到什麼
- 用資料流說明本篇 SSRF 風險,而不是依賴攻擊字串。
- 辨識安全檢查與實際連線可能不一致的位置。
- 以應用、網路、監控與事件回應建立可驗證的修補。
必備名詞與生活化心智模型
安全不是『使用某個語言函式』,而是確保解析、DNS、Redirect 與 socket 都遵守同一政策。三種語言預設值和擴充點不同。
Source 是使用者可影響的網址或目的地識別;Parser 把文字拆成 URL 組件;Resolver 把 Host 轉成位址;Sink 是真正建立連線的 Client、代理或背景工作。驗證若只存在於其中一層,其他層仍可能採用不同資料。
SSRF 資料流技術圖

常見發生情境
PHP cURL、Node fetch/undici、Python urllib/requests 都能方便發請求,但 Redirect、代理、DNS 綁定與回應上限需額外設計。
審查時應由功能入口一路追到同步或非同步的網路 Sink。不要因欄位藏在 JSON、資料庫、Cookie、佇列或後台設定就假設可信;如果低信任角色能直接或間接改變目的地,它仍是外部控制資料。
不安全原始碼與逐步解讀
function preview($url) { return ['client_default' => true, 'url' => $url]; }
包裝函式只驗證字串後呼叫預設 Client;升級語言或函式庫時,預設 Redirect/代理行為改變卻沒有回歸測試。
- 找出輸入最初由誰控制,以及是否跨越資料庫或佇列。
- 確認安全檢查使用的解析器、DNS 答案和 Client 是否一致。
- 確認重試、Redirect、代理與背景工作會不會重新決定目的地。
- 盤點程式執行身分能接觸的網段、服務和資料。
修補後原始碼與設計理由
$ticket = $policy->authorize($url, $resolverFixture);
$result = $restrictedTransport->execute($ticket, timeoutMs: 2000, maxBytes: 1048576);
統一 `authorize()` 與 `execute(ticket)` 介面,各語言 adapter 只接受已授權票據;以 fake transport 測試目的地、跳轉及資源上限。
安全版本刻意把「授權目的地」與「執行請求」拆成兩個介面。政策回傳的不是可以任意修改的 URL 字串,而是含政策版本、正規化目的地、已驗證位址與資源限制的票據。執行層拒絕沒有票據的呼叫,才能避免另一段程式繞過共同控制。
如何驗證修補
使用表格驅動的離線測試,注入固定 DNS 答案與假 transport。至少驗證正常 Allowlist、錯誤 Scheme、userinfo、非預期 Port、解析失敗、私有/Loopback/Link-local、混合 A 與 AAAA、Redirect、逾時和超過回應大小。測試應斷言「沒有建立非預期連線」,而不是只比較錯誤文字。
部署測試再核對實際 Client profile、代理、環境變數、IPv6 路由與防火牆規則。單元測試證明政策邏輯,網路控制則證明即使應用層出錯,最壞影響仍受限制。
更多應用案例與常見誤判
最常見的誤判是把『沒有把回應顯示在頁面』當成沒有 SSRF,或把 WAF 命中當成根因已修補。另一個誤判是相信來自自家資料庫的 URL;資料可能先由低信任使用者寫入,之後才由高權限背景工作取出。判斷依據必須是資料控制權與最終 Sink,而不是欄位所在位置。
正常的特殊字元、國際化網域、暫時 DNS 錯誤或第三方服務改址也可能觸發規則。防守端應保留可解釋的拒絕原因與政策版本,讓開發者能修正業務設定,但不能因為出現誤判就把預設政策改成全部允許。
開發與 SOC 觀察點
把語言 runtime、Client 版本、profile 與政策版本寫入部署清冊;升級後比較實際出站行為而非只看單元測試。
建議日誌欄位包含時間、功能、使用者/工作 ID、trace ID、政策結果、正規化 Host、Port、位址類別、Redirect 跳數、耗時、回應大小與結果類別。查詢字串、Header、憑證與完整回應不可直接進入一般日誌。SOC 應把應用事件、DNS、出口代理、網路流量和雲端稽核串成同一時間線。
防禦檢查清單
- 能否不用完整 URL,而改用已登錄的目的地 ID?
- Scheme、userinfo、Host、Port、DNS 全部答案與每一個 Redirect 是否受控?
- 驗證結果是否與實際連線綁定,重試時是否重新套用政策?
- 是否設定連線/總逾時、回應大小、內容型別與方法限制?
- 工作負載身分、網段與出口代理是否遵循最小權限?
- 能否用 trace ID 還原來源、政策、DNS、連線與結果?
重點整理與下一篇
PHP、Node.js、Python 的安全請求封裝的核心是控制『伺服器最後實際連到哪裡』。字串驗證只是入口;真正可靠的方案還要涵蓋解析一致性、位址分類、Client 行為、出口限制與可觀測性。下一篇會沿著系列順序,把這些控制套到另一個常見 SSRF 邊界。
參考資料與更新日期
- OWASP SSRF Prevention Cheat Sheet
- RFC 3986 URI Generic Syntax
- PHP parse_url documentation
- Node.js URL documentation
- Python urllib.parse documentation
更新日期:2026-07-20














