API 安全測試概述
API(Application Programming Interface)已成為現代應用程式架構的核心組件。隨著微服務架構和雲端服務的普及,API 的數量急劇增加,這也使得 API 安全測試變得更加重要。
為什麼 API 安全測試很重要?
- 資料外洩風險:API 直接暴露後端資料,若未妥善保護,可能導致敏感資料外洩
- 業務邏輯漏洞:API 端點可能存在業務邏輯缺陷,允許未授權的操作
- 攻擊面擴大:每個 API 端點都是潛在的攻擊入口
- 合規要求:GDPR、PCI-DSS 等法規要求對 API 進行安全評估
API 安全測試類型
| 測試類型 | 說明 | 測試重點 |
|---|
| 功能測試 | 驗證 API 是否按預期工作 | 請求/回應格式、錯誤處理 |
| 安全測試 | 識別安全漏洞 | 認證、授權、輸入驗證 |
| 滲透測試 | 模擬攻擊者行為 | 漏洞利用、權限提升 |
| 模糊測試 | 發送異常輸入 | 邊界條件、異常處理 |
常見 API 漏洞
根據 OWASP API Security Top 10,以下是最常見的 API 安全漏洞:
1. Broken Object Level Authorization (BOLA)
物件層級授權失效是最常見的 API 漏洞。攻擊者透過修改請求中的物件 ID 來存取其他使用者的資料。
1
2
3
4
5
6
7
| # 原始請求 - 存取自己的訂單
GET /api/orders/1001 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# 攻擊請求 - 嘗試存取其他用戶的訂單
GET /api/orders/1002 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
2. Broken Authentication
認證機制缺陷可能導致帳號接管或未授權存取。
常見問題:
- 弱密碼政策
- 缺少帳號鎖定機制
- Token 未正確失效
- 憑證在 URL 中傳輸
3. Excessive Data Exposure
API 回傳過多資料,依賴前端進行過濾。
1
2
3
4
5
6
7
8
9
| // 不安全的回應 - 包含敏感資訊
{
"user_id": 123,
"username": "john",
"email": "john@example.com",
"password_hash": "$2b$12$...",
"credit_card": "4111-1111-1111-1111",
"ssn": "123-45-6789"
}
|
4. Lack of Resources & Rate Limiting
缺少速率限制可能導致 DoS 攻擊或暴力破解。
5. Broken Function Level Authorization
功能層級授權失效允許攻擊者存取管理功能。
1
2
3
| # 一般使用者嘗試存取管理 API
DELETE /api/admin/users/456 HTTP/1.1
Authorization: Bearer <user_token>
|
認證與授權測試
JWT Token 測試
JWT(JSON Web Token)是 API 認證的常見方式。測試重點包括:
1. 簽名驗證測試
1
2
3
4
5
6
7
8
9
| # 原始 JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjN9.signature
# 測試 1:移除簽名(alg: none 攻擊)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxMjN9.
# 測試 2:修改 payload 中的角色
# Header: {"alg":"HS256","typ":"JWT"}
# Payload: {"user_id":123,"role":"admin"}
|
2. Token 過期測試
1
2
3
4
5
6
7
8
9
10
11
| import jwt
import time
# 產生過期的 Token
expired_token = jwt.encode(
{"user_id": 123, "exp": int(time.time()) - 3600},
"secret_key",
algorithm="HS256"
)
# 測試 API 是否接受過期 Token
|
OAuth 2.0 測試
1
2
3
4
5
6
7
8
9
10
11
12
| # 測試重定向 URI 驗證
GET /oauth/authorize?
client_id=legit_client&
redirect_uri=https://evil.com/callback&
response_type=code&
scope=read HTTP/1.1
# 測試 CSRF 保護(缺少 state 參數)
GET /oauth/authorize?
client_id=legit_client&
redirect_uri=https://app.com/callback&
response_type=code HTTP/1.1
|
授權測試清單
輸入驗證測試
SQL Injection 測試
1
2
3
4
5
6
7
8
9
10
| # 測試 SQL 注入
GET /api/users?id=1' OR '1'='1 HTTP/1.1
# JSON 格式的 SQL 注入
POST /api/search HTTP/1.1
Content-Type: application/json
{
"query": "'; DROP TABLE users; --"
}
|
NoSQL Injection 測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # MongoDB 注入測試
POST /api/login HTTP/1.1
Content-Type: application/json
{
"username": {"$gt": ""},
"password": {"$gt": ""}
}
# 運算子注入
{
"username": "admin",
"password": {"$regex": ".*"}
}
|
XML External Entity (XXE) 測試
1
2
3
4
5
6
7
8
9
10
| POST /api/upload HTTP/1.1
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<data>&xxe;</data>
</root>
|
Server-Side Request Forgery (SSRF) 測試
1
2
3
4
5
6
| POST /api/fetch-url HTTP/1.1
Content-Type: application/json
{
"url": "http://169.254.169.254/latest/meta-data/"
}
|
速率限制測試
測試方法
1
2
3
4
5
6
7
| # 使用 curl 進行速率限制測試
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://api.example.com/login \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"test123"}'
done | sort | uniq -c
|
繞過技巧測試
1
2
3
4
5
6
7
8
9
10
11
12
| # 測試 X-Forwarded-For 繞過
POST /api/login HTTP/1.1
X-Forwarded-For: 1.2.3.4
X-Real-IP: 5.6.7.8
# 測試大小寫變化
POST /api/Login HTTP/1.1
POST /API/login HTTP/1.1
# 測試路徑變化
POST /api/login/ HTTP/1.1
POST /api/./login HTTP/1.1
|
Postman 安全測試
環境設定
1
2
3
4
5
6
7
8
9
10
11
12
| // 在 Pre-request Script 中設定認證
pm.environment.set("auth_token", pm.environment.get("jwt_token"));
// 動態產生 timestamp 和簽名
const timestamp = Date.now();
const signature = CryptoJS.HmacSHA256(
timestamp + pm.environment.get("api_key"),
pm.environment.get("secret")
).toString();
pm.environment.set("timestamp", timestamp);
pm.environment.set("signature", signature);
|
自動化測試腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Tests 標籤頁的測試腳本
pm.test("狀態碼應為 200", function() {
pm.response.to.have.status(200);
});
pm.test("回應時間應小於 500ms", function() {
pm.expect(pm.response.responseTime).to.be.below(500);
});
pm.test("不應包含敏感資訊", function() {
const response = pm.response.json();
pm.expect(response).to.not.have.property("password");
pm.expect(response).to.not.have.property("password_hash");
pm.expect(response).to.not.have.property("credit_card");
});
pm.test("應有適當的安全標頭", function() {
pm.response.to.have.header("X-Content-Type-Options");
pm.response.to.have.header("X-Frame-Options");
});
|
Collection Runner 批次測試
- 建立包含各種測試案例的 Collection
- 使用 Data File(CSV/JSON)提供測試資料
- 執行 Collection Runner 進行批次測試
Burp Suite API 測試
設定 API 測試
匯入 OpenAPI/Swagger 規格
- 前往
Target > Site map - 右鍵選擇
Import OpenAPI definition - 選擇 swagger.json 或 openapi.yaml 檔案
設定認證
- 在
Project options > Sessions 中設定 Session handling rules - 新增 Macro 自動取得和更新 Token
使用 Repeater 測試 API
1
2
3
4
5
6
7
| # 測試 BOLA 漏洞
GET /api/v1/users/{{user_id}}/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer {{token}}
# 逐一測試不同的 user_id 值
# user_id: 1, 2, 3, ..., 100
|
Intruder 自動化測試
設定 Payload 位置:
1
2
3
| GET /api/orders/§1001§ HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
Payload 類型設定:
- Numbers: 1000-2000, step 1
- Simple list: 常見的 ID 值
- Brute forcer: 字元組合
自動化工具(OWASP ZAP)
安裝與設定
1
2
3
4
5
6
| # Docker 安裝
docker pull ghcr.io/zaproxy/zaproxy:stable
# 執行 ZAP
docker run -u zap -p 8080:8080 -p 8090:8090 \
ghcr.io/zaproxy/zaproxy:stable zap-webswing.sh
|
API 掃描
1
2
3
4
5
6
| # 使用 ZAP API 掃描
docker run --rm -v $(pwd):/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py \
-t https://api.example.com/openapi.json \
-f openapi \
-r api-scan-report.html
|
ZAP 自動化腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| from zapv2 import ZAPv2
# 連接 ZAP
zap = ZAPv2(apikey='your-api-key', proxies={
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
})
# 匯入 OpenAPI 定義
zap.openapi.import_url('https://api.example.com/openapi.json')
# 執行主動掃描
scan_id = zap.ascan.scan('https://api.example.com')
# 等待掃描完成
while int(zap.ascan.status(scan_id)) < 100:
print(f'掃描進度: {zap.ascan.status(scan_id)}%')
time.sleep(5)
# 取得掃描結果
alerts = zap.core.alerts()
for alert in alerts:
print(f"漏洞: {alert['name']} - 風險: {alert['risk']}")
|
測試清單
認證測試清單
授權測試清單
輸入驗證測試清單
資料保護測試清單
速率限制測試清單
參考資料