RESTful API 安全测试实战——基于 PortSwigger 靶场的攻防解析

RESTful API 安全测试实战——基于 PortSwigger 靶场的攻防解析

==RESTful 风格:以资源为中心,使用 HTTP 方法表达操作语义,如 GET /api/users/1 获取用户,PATCH /api/users/1 部分修改用户。==

1. 与 API 交互的基础

1.1 HTTP 请求方法

HTTP/1.0 定义了三种基础方法:

  • GET —— 请求指定资源,仅用于获取数据。
  • POST —— 向指定资源提交数据,通常会引起服务端状态变化。
  • HEAD —— 几乎与 GET 相同,但服务器在响应中只返回响应头,不返回响应体。

HTTP/1.1 新增了更多方法,在 API 测试中尤其需要关注以下四种:

  • OPTIONS —— 查询目标资源支持的通信选项,常用于探测服务器允许的 HTTP 方法。
  • PUT —— 用请求体完整替换目标资源。
  • DELETE —— 删除指定资源。
  • PATCH —— 对资源进行部分修改。

测试技巧:对一个 API 端点发送 OPTIONS 请求,可以从 Allow 头中发现那些前端未使用、但后端允许的“隐藏方法”。

1.2 Content-Type(媒体类型)

Content-Type 头告诉服务器请求体的数据类型,API 测试中经常需要在不同格式间切换以绕过限制或触发解析差异。常用类型包括:

  • application/json —— JSON 格式,RESTful API 使用最广。
  • application/x-www-form-urlencoded —— 表单提交默认编码。
  • multipart/form-data —— 包含文件上传的表单。
  • application/xml —— XML 格式。
  • text/plain —— 纯文本。
  • application/octet-stream —— 二进制流,不指定具体格式。
  • image/jpegimage/png —— 图像资源。

测试技巧:将 application/json 改为 application/xmltext/plain 发送,观察服务器响应是否出现解析错误、信息泄露或不同的执行路径。

1.3 API 端点的构成

一个完整的 API 请求由以下要素组成:

  • 基础路径:如 /api,通常代表 API 的统一前缀。
  • 资源路径:如 /users/1,采用层级结构定位资源。
  • 查询参数?fields=id,name 用于过滤或投影。
  • 请求体:JSON、XML 等格式的实际载荷,包含需要传递的数据。
  • 请求头:Content-Type、Authorization、自定义头等。

测试时,需要分别探测路径参数、查询参数和请求体参数,这三个位置都可能存在注入或越权。

1.4 HTTP 状态码速查

API 测试中重要的状态码:

  • 200 OK —— 请求成功。
  • 201 Created —— 资源创建成功。
  • 204 No Content —— 操作成功但无返回体。
  • 301/302 —— 重定向。
  • 400 Bad Request —— 请求格式错误,常泄漏缺失参数。
  • 401 Unauthorized —— 未认证。
  • 403 Forbidden —— 无权限(授权失败)。
  • 404 Not Found —— 端点不存在或资源不可见。
  • 405 Method Not Allowed —— 方法不被允许,响应头可能包含 Allow 字段。
  • 429 Too Many Requests —— 触发速率限制。
  • 500 Internal Server Error —— 可能意味着参数造成了服务端异常,存在注入可能。

2. 靶场测试

靶场地址:https://portswigger.net/web-security/all-labs#api-testing

2.1 Exploiting an API endpoint using documentation

地址:https://portswigger.net/web-security/api-testing/lab-exploiting-api-endpoint-using-documentation

方法一:

1、打开靶场:先用给出的账号登录

2、来到这个页面,有个更新邮箱的功能![image-20260423233759202](./images/API攻防二之RESTful API 安全测试实战/image-20260423233759202.png)

3、任意写一个邮箱,点击更新,在BP中查看数据包

4、发现这么一个数据包

![image-20260423233920178](./images/API攻防二之RESTful API 安全测试实战/image-20260423233920178.png)

5、通过基础的了解,知道PATCH 是对资源进行部分修改,所以这里就是修改了邮箱

6、然后前面介绍了OPTIONS的HTTP请求方法,可以探测服务器允许的 HTTP 方法,所以在重放中修改请求方法为OPTIONS 请求,可以从 Allow 头中发现那些前端未使用、但后端允许的“隐藏方法”。

![image-20260423234200440](./images/API攻防二之RESTful API 安全测试实战/image-20260423234200440.png)

7、可以看到允许DELETE, PATCH, GET请求方法

8、修改请求方法为DELETE,发送请求,可以看到当前用户被删除了

![image-20260423234255704](./images/API攻防二之RESTful API 安全测试实战/image-20260423234255704.png)

9、这个通关的要求是删除carlos用户,所以我们把wiener修改为carlos,发送就可以通关。(因为前面删除了我们给的账户,所以要重启靶场,然后重新发送这个数据包到重放中再修改)

![image-20260423234758953](./images/API攻防二之RESTful API 安全测试实战/image-20260423234758953.png)

方法二:

当然也可以在登录给出的账号后的页面,点击FindSomething插件,可以看到/api/user接口

![image-20260423235041457](./images/API攻防二之RESTful API 安全测试实战/image-20260423235041457.png)

访问这个接口:https://0afb007604363b2383c64c43005200ff.web-security-academy.net/api/user/

页面响应

{
  "error": "Malformed URL: expecting an identifier"
}
路由规则:/api/user/ 必须携带后续标识符(用户名参数),空后缀非法

路由规则:/api/user/ 必须携带后续标识符(用户名参数),空后缀非法

根据这个api,我们可以猜测user/后是用户名,所以在后面添加一个用户名

https://0afb007604363b2383c64c43005200ff.web-security-academy.net/api/user/amdin

{
  "type": "ClientError",
  "code": 404,
  "error": "User not found"
}
路由正常解析、参数接收成功
逻辑:接口存在按用户名查询用户信息的功能

返回用户未发现,说明思路是对的,修改用户名为我们当前的用户,收到正常响应

{
  "username": "wiener",
  "email": "wiener@normal-user.net"
}
正常返回对应用户的账号、邮箱敏感数据

修改用户名为:carlos

{
  "username": "carlos",
  "email": "carlos@carlos-montoya.net"
}

接口存在 用户枚举 / 越权查询漏洞

  • 未做身份鉴权、未做访问控制
  • 任意未授权访客,只要拼接正确用户名,就能直接查询任意用户的个人信息(账号、邮箱)

尝试访问 /api/ 根目录,发现服务器未对 API 根路由做访问限制与目录隐藏,直接完整泄露全站 REST 接口定义;

![image-20260424002750741](./images/API攻防二之RESTful API 安全测试实战/image-20260424002750741.png)

直接点击DELETE,输入用户名carlos,就能直接删除用户carlos

![image-20260424002842787](./images/API攻防二之RESTful API 安全测试实战/image-20260424002842787.png)

2.2 Exploiting server-side parameter pollution in a query string

地址:https://portswigger.net/web-security/api-testing/server-side-parameter-pollution/lab-exploiting-server-side-parameter-pollution-in-query-string

REST URL 中的服务器端参数污染详解

在做这个靶场前先了解什么是API参数污染,下面两篇文详细分析了 REST URL 路径中参数污染的利用方式,与本关查询字符串场景的原理相通,可作为补充阅读

https://cloud.tencent.com/developer/article/1516333

https://cn-sec.com/archives/4921029.html

1. 基础认识

在 RESTful 架构里,后端服务之间经常通过 URL 路径或查询字符串传递参数。如果中间层(如前端 API 网关或 BFF 层)在构建内部请求时,将用户输入直接拼接到 URL 中而未做正确编码,攻击者就能通过注入特殊字符来“污染”服务端请求的参数,从而操纵内部 API 的行为。这就是服务器端参数污染(Server-Side Parameter Pollution,SSPP) 在 REST URL 场景中的核心表现

2 为什么 REST URL 容易产生参数污染

RESTful 设计强调以资源为中心,大量使用路径参数和查询参数:

  • 路径参数/api/users/123123 就是路径中的参数。
  • 查询字符串/api/search?q=keyword&limit=10

如果前端应用在处理用户输入时,只是简单地将用户输入值填入 URL 模板,例如:

# 内部拼接 URL 的典型危险代码
internal_url = f"http://backend/api/users/{user_input}/profile"

那么当 user_input 包含 URL 特殊字符(如 #?&/.. 等)时,整个 URL 的结构就会被破坏,从而被攻击者注入额外的参数或篡改路径。

3. 两种主要的注入位置
注入点 用户输入被放入的部分 可注入的典型字符 攻击目标
服务端 URL 路径 /api/users/{username} /..#? 改变请求的路径,访问其他端点
服务端查询字符串 /api/search?name={input} &=# 覆盖现有参数,添加新参数

本关(2.2)场景属于查询字符串注入,但为了全面理解,先梳理一下路径注入的原理,再从查询字符串深入。

4. 路径参数污染(REST URL Path Pollution)

当用户输入被直接拼接到服务端 URL 的路径段中时,可以使用路径遍历序列(如 ../)或者 #? 来截断原路径。

实例分析

假设前端接收一个用户名,然后请求内部 API:
http://internal-api/api/users/{username}/documents

如果正常用户名是 john,内部请求 URL 为:
http://internal-api/api/users/john/documents

如果攻击者提供用户名:../admin
拼接后的 URL 变为:
http://internal-api/api/users/../admin/documents
解析后实际路径为:
http://internal-api/api/admin/documents

这样就能访问 admin 用户的文档,造成越权。

更隐蔽的攻击:如果后端 API 还有其他端点,比如 http://internal-api/admin/delete-user?user=xx,攻击者可以输入: ../admin/delete-user?user=target#
拼接后:
http://internal-api/api/users/../admin/delete-user?user=target#/documents
此时 # 及之后的内容都被视为客户端片段,不会发到服务端,实际请求变为:
http://internal-api/api/admin/delete-user?user=target

——成功调用了内部管理接口,执行了删除操作。

探测方法:

  • 在用户名等路径参数中输入 #?/..,观察响应状态码和内容变化。
  • 如果返回 404 或不同资源,说明路径被修改。
  • 如果返回 500 或异常错误,可能泄露内部 URL 结构。
5. 查询字符串参数污染(Query String Pollution)

当用户输入被拼接到内部请求的查询字符串部分时,核心是利用 &= 注入新参数或覆盖已有参数。

场景模拟

前端提供一个重置密码的功能,URL 为:
POST /forgot-password
请求体:{"username":"wiener"}

服务端收到后,构造内部请求去获取该用户的重置令牌:
http://internal-api/api/users/wiener/reset_token

但如果服务端逻辑是将用户输入直接拼接到查询字段,例如:
http://internal-api/api/search?field=username&value={input}

攻击者提供一个带有特殊字符的用户名,就可能改变查询参数的含义。


6. 核心攻击向量:HTTP 参数污染 + 查询语法字符

很多框架(如 Flask、Express、Spring)在处理重复的查询参数时,会选择使用第一个值或最后一个值,或将其合并为数组。攻击者可以利用这一点,通过注入 &=覆盖敏感参数或添加参数。

示例:暴力重置管理员密码

某应用有以下内部 API:

  • GET /api/users/{username}/reset_token —— 返回该用户的密码重置令牌。
  • 前端 /forgot-password 接受 username,然后调用上述内部 API 获取令牌,并通过邮件/页面反馈给用户。

但前端在调用内部 API 时,可能以不安全的方式拼接 URL:

String url = "http://backend/api/users/" + username + "/reset_token";

这里的 username 直接来自用户输入。如果用户名只允许字母数字,风险较小。但如果用户名允许包含特殊字符(例如某些系统允许 @. 甚至 #?),攻击者就可以尝试:

输入用户名:../search?field=username&value=administrator#
拼接后的 URL 变成:
http://backend/api/users/../search?field=username&value=administrator#/reset_token
→ 实际请求:http://backend/api/search?field=username&value=administrator
这样攻击者就将原本的获取令牌请求变成了一个搜索请求,可能用来探测用户是否存在。

但是,如果要 直接拿到 administrator 的重置令牌,通常需要结合 查询字符串注入 的场景,即用户输入并不在路径中,而在查询参数里。


7. 查询字符串注入的典型利用:截断与参数覆盖

假设有一个密码重置流程:

  1. 用户输入 username,前端提交 POST /forgot-password 体为 {"username":"wiener"}

  2. 服务器内部会调用一个认证 API:
    GET /api/reset-token?user=wiener&role=user
    这里 role=user 是服务端硬编码添加的,确保只能重置普通用户密码。

  3. 如果 user 参数的值是从用户输入直接拼入的,没有过滤,攻击者就可以输入:

    wiener%26role=admin%26& 的 URL 编码)

    那么服务端如果对输入的 user 值进行解码后拼接,最终内部 URL 变成:
    GET /api/reset-token?user=wiener&role=admin&role=user

    根据后端框架解析规则,如果采用第一个 role 值,则 role=admin 生效,从而可能绕过权限检查,拿到管理员的重置令牌。

另一种利用方式:删减字段。如果内部 API 接受一个 fields 参数来指定返回字段,攻击者可以附加 &fields= 为空,导致返回额外数据或报错。

8. 防御措施
  • 使用结构化 API 调用库,不要手动拼接 URL。例如使用 requests 库的 params 参数,它会自动编码。
  • 对用户输入进行白名单校验,只允许安全字符(如字母数字、邮箱格式等)。
  • 在服务间通信时采用 POST+JSON 体传递参数,避免参数落入 URL 查询字符串。
  • 对内部 API 进行严格认证与授权,不盲目信任来源请求的参数,比如内部调用也要求携带认证头或签名。
  • 统一 URL 解析规则,例如防止框架对重复参数的歧义处理,始终取最严格的匹配。

靶场

了解到参数污染, 就可以测试这个靶场了,打开靶场

  1. 关卡要求:以 administrator 的身份登录并删除 carlos 。只给了账户名, 所以通过关卡的最终要求就是重置administrator的密码, 然后删除用户carlos, 那就利用忘记密码的请求包来尝试![image-20260426031341383](./images/API攻防二之RESTful API 安全测试实战/image-20260426031341383.png)

  2. 然后查看数据包

  3. 一般渗透测试中常规方法没用会对js文件进行分析, 用转子女神工具对js分析, 或者查看数据包, 分析到如下情况

    ![image-20260426034737642](./images/API攻防二之RESTful API 安全测试实战/image-20260426034737642.png)

    查看js代码,可以看到

    ![image-20260426034845144](./images/API攻防二之RESTful API 安全测试实战/image-20260426034845144.png)

    可以看到这是一个密码重置端点,它引用了 reset_token 参数.

    分析忘记密码的数据包

    ![image-20260426032038518](./images/API攻防二之RESTful API 安全测试实战/image-20260426032038518.png)

    看到这个考虑的参数污染

  4. 然后构造参数污染,

    csrf=5gZ1LcmV4SqOa2I03WFZR8hUtwT3AcNj&username=administrator&添加一个参数: x=y

    即: username=administrator%26x=y.

    前端服务器的操作:它构建了一个类似 https://api.internal/reset?username=administrator&x=y 的后端请求。

    发送数据包

  5. 服务端响应

    {"error": "Parameter is not supported."}

    即不支持的参数, 这表明内部 API 可能将 &x=y 解释为单独的参数,而不是用户名的一部分。

  6. 尝试使用 URL 编码的 # 字符截断服务器端查询字符串:

    即payload: &username=administrator%23

    返回一个 Field not specified 错误消息。这表明服务器端的查询可能包含一个名为 field 的额外参数,该参数被 # 字符移除了。

  7. 修改请求添加field参数, 并且赋予一个无效值, 并且用#截断对后截断查询字符串

    &username=administrator%26field=1%23

  8. 返回Invalid field 错误消息。这表明服务器端应用程序可能识别到了注入的字段参数。所以现在的问题是猜测field参数的值,通过BP的爆破模块进行爆破, 观看爆破数据包的响应, 包含用户名和电子邮件负载的请求都返回了 200 响应。

![image-20260426033458267](./images/API攻防二之RESTful API 安全测试实战/image-20260426033458267.png)

  1. field 参数的值从 x# 更改为 email

    username=administrator%26field=email%23
    发送请求。注意,这返回了原始响应。这表明 email 是一个有效的字段类型。
  2. 在前面的js文件中, 注意密码重置端点,它引用了 reset_token 参数:

  3. field 参数的值从 email 更改为 reset_token

    username=administrator%26field=reset_token%23

    发送请求。注意,这会返回一个密码重置令牌。记下这个令牌。

    这一步的理解:

    1. 发现“隐藏”参数 注入 %26field=x%23 后出现 Invalid field 错误,证明服务端内部请求中有一个 field 参数,且后端会校验它的值。暴力枚举后确认 email 是一个有效值。

    2. 推测内部 API 结构POST /forgot-password 流程和 forgotPassword.js 可以推断,服务端在收到 username 后,很可能会请求一个内部 API,格式类似:

      GET /api/user/reset-info?username=administrator&field=email

      前端通过 field 参数告诉内部 API“我需要哪个字段的数据”。正常功能下,前端会拿到 email 来发送重置链接。

    3. 篡改字段名获取敏感令牌 reset_token 是真正用来重置密码的凭据,但在正常流程中前端不会去请求它(它只生成后发往邮箱)。 我们利用参数污染注入 &field=reset_token,会把服务端请求变成:

      GET /api/user/reset-info?username=administrator&field=reset_token

      结果直接返回 administrator 的密码重置令牌,我们就能绕过邮箱验证,直接在浏览器访问 /forgot-password?reset_token=<令牌> 完成密码重置。

    ![image-20260426035855693](./images/API攻防二之RESTful API 安全测试实战/image-20260426035855693.png)

  4. 览器中,在地址栏中输入密码重置端点。将你的密码重置令牌作为 reset_token 参数的值。例如:

    /forgot-password?reset_token=qjmw18684wwh26jhq55mrvb64irrhlo4
  5. 然后就可以重新设置administrator的密码,充值后登录, 然后删除carlos

2.3 Finding and exploiting an unused API endpoint

这关给了一个账户,登录, 这关的要求是利用一个隐藏的 API 端点购买一件轻便的 l33t 皮革夹克

  1. 点击 l33t 皮革夹克的商品详情,抓包分析数据包,发现一个请求价格的数据包.

    ![image-20260426123038475](./images/API攻防二之RESTful API 安全测试实战/image-20260426123038475.png)

  2. 可以看到这是一个获取数据的API接口, 在重放中测试这个数据包

  3. 通过前面的http请求方法的学习, 用OPTIONS探测一下他支持的方法, Allow字段返回他支持GET和PATCH方法, PATCH是对PATCH —— 对资源进行部分修改。

    ![image-20260426123252488](./images/API攻防二之RESTful API 安全测试实战/image-20260426123252488.png)

  4. 修改请求方法为PATCH, 发送请求, 返回"error":"Only 'application/json' Content-Type is supported",

    ![image-20260426123437575](./images/API攻防二之RESTful API 安全测试实战/image-20260426123437575.png)

  5. 修改请求中数据类型为application/json, 再次发送请求, 返回price' parameter missing in body, 说明加诵数据中需要price的参数

![image-20260426123622715](./images/API攻防二之RESTful API 安全测试实战/image-20260426123622715.png)

  1. 在json中添加price的参数

    ![image-20260426123746702](./images/API攻防二之RESTful API 安全测试实战/image-20260426123746702.png)

  2. 成功修改价格为0, 返回浏览器, 刷新, 把商品添加到购物车,下单, 成功通关

2.4 Exploiting a mass assignment vulnerability

地址: https://portswigger.net/web-security/api-testing/lab-exploiting-mass-assignment-vulnerability

关卡要求: 利用一个批量赋值漏洞来购买一件轻便的 l33t 皮革夹克。

所以先认识一下什么是批量赋值:

“批量赋值”(Mass Assignment)可以通俗地理解为:后端程序因为“太懒”或“太信任”前端,直接把用户从浏览器传来的所有数据一股脑地赋值给程序中的核心对象。

可以把这个过程想象成“全盘接收”。

比如一个网站后台有一个 User 用户模型,它包含了 username(用户名)、email(邮箱)、password(密码)、isAdmin(是否是管理员)等属性。当用户通过注册页面请求更新信息时,正常的代码逻辑应该只针对收到的usernameemailpassword这三个字段进行修改。

如果使用了“批量赋值”技术,后端开发者写了一个方法:不管前端传来什么,全部都照单全收,直接发给数据库进行更新。 那么攻击者就可以在请求里多传一个 isAdmin=true,后端也会“照单全收”,从而把自己升级成管理员。

简单来说,批量赋值漏洞利用了开发者“信任所有输入”的懒惰

  1. 进入靶场, 登录给出的用账号, 这是我们的余额是0 , 无法购买任何东西, 所以要篡改数据让商品价格为零

  2. 到商品页面, 将商品添加到购物车, 到购物车,下单, 抓取数据包分析.

    ![image-20260426130952180](./images/API攻防二之RESTful API 安全测试实战/image-20260426130952180.png)

  3. 第一个POST请求时创建订单的数据包, 第二个是GET请求获取的 checkout 结账信息

    ![image-20260426131307936](./images/API攻防二之RESTful API 安全测试实战/image-20260426131307936.png)

  4. POST请求分析

    POST /api/checkout#355)
       → 服务器返回:余额不足
       → 页面跳转到 /cart?err=INSUFFICIENT_FUNDS
  5. 然后GET结账信息的数据包返回内容是

    {
        "chosen_discount": {
            "percentage": 0      #打折折扣字段
        },
        "chosen_products": [
            {
                "product_id": "1",
                "name": "Lightweight \"l33t\" Leather Jacket",
                "quantity": 1,
                "item_price": 133700
            }
        ]
    }

    GET 接口不该暴露折扣字段,但暴露了;

    在重放中重放这个GET请求.

  6. ==整理流程:自己建立发送的请求中的 JSON 结构 对比 原结账的 JSON 结构,十分相似,只是没有折扣部分,我们可以尝试在自己建立的请求中添加“折扣 100%”参数,实现零元购。==
  7. 原本的 POST 请求中只有 chosen_products,没有折扣字段。 我们把从 GET 响应里发现的 chosen_discount 字段,添加到 POST 请求的 JSON 结构中,看后端是否接受。

    ![image-20260426132739620](./images/API攻防二之RESTful API 安全测试实战/image-20260426132739620.png)

    发现后端确实接收了我们的请求, 接下俩修改折扣度为100, 发送请求

    ![image-20260426132825490](./images/API攻防二之RESTful API 安全测试实战/image-20260426132825490.png)

  8. 成功返回了接口结账的接口/cart/order-confirmation?order-confirmed=true, 到浏览器访问, 成功过关

整体流程

1. 你正常下单(钱不够)
→ POST /api/checkout#355)
→ 服务器返回:余额不足
→ 页面跳转到 /cart?err=INSUFFICIENT_FUNDS(#356)
2. 你在 Burp 里查看历史,发现之前还有一个 GET /api/checkout#367)
→ 把它发到 Repeater 里重新发一次
→ 响应体里看到了 "chosen_discount": {"percentage": 0}
3. 你把这个隐藏参数加到 POST 请求里:
{"chosen_discount": {"percentage": 100}}
4. 再次发送 POST /api/checkout
→ 订单价格变成 0
→ 购买成功 ✅

2.5 Exploiting server-side parameter pollution in a REST URL

把场地址: https://portswigger.net/web-security/api-testing/server-side-parameter-pollution/lab-exploiting-server-side-parameter-pollution-in-rest-url

参考文章: https://blog.csdn.net/m0_65361643/article/details/152942685

基础了解

什么是 API 文档发现?

API 文档发现,简单来说,就是在渗透测试的侦察阶段,通过一切可能的手段去定位那些描述目标 API 结构、端点、参数和认证方式的文件。

在真实环境下,开发人员为了方便协作,常会生成 API 文档,例如遵循 OpenAPI (Swagger) 规范的 swagger.jsonopenapi.json 文件。然而,如果这些文档因配置不当而被部署在公开可访问的目录下,就会成为一份详尽的“攻击路线图”。攻击者可以利用路径遍历等技术找到它,然后按图索骥,挖掘高价值漏洞。

路径遍历(Path Traversal)—— 在 REST URL 中的变形
1. 传统定义

路径遍历(或称目录遍历)攻击利用 ../(点点斜杠)序列,让文件系统向上导航,从而访问应用程序本不应暴露的文件或目录。例如:../../etc/passwd

2. 在 REST API 场景下的特殊含义

在 RESTful API 中,路径遍历不再只是读取本地文件,而是操纵 API 路由的层级结构

示例:正常内部 API 路径为:

GET /api/users/wiener/reset_token

如果 wiener 来自用户输入,攻击者可以输入:

../admin/reset_token

拼接后:

GET /api/users/../admin/reset_token

URL 标准化后变成:

GET /api/admin/reset_token

从而访问了本不该访问的管理接口。

3. 本关靶场中的实际利用
  • 用户输入 username=administrator/field/email,内部可能构造为: /api/internal/v1/users/administrator/field/email
  • 通过注入 ../ 向上跳转,最终访问到 /openapi.json(API 文档),从而发现更多端点结构和参数名。
4. 路径遍历 vs 参数污染的关系
手法 本质 在本关中的作用
路径遍历 改变资源的路径层级 访问非预期的 API 端点(如文档、管理功能)
参数污染 改变请求的参数语义 通过 # 截断、& 添加参数,改变后端处理逻辑
结合效果 两者通常同时使用 先用路径遍历找到隐藏 API,再用参数污染提取敏感数据
靶场测试
第一阶段:探路者——探测注入点并确认漏洞
首先,我们需要确认注入点的存在。

起点:在忘记密码页面输入管理员账户administrator,抓取这个POST /forgot-password请求,此时username是我们唯一能控制的地方。

探测三步曲:

在username后加#(即administrator#),服务器返回Invalid route错误。这证明#截断了URL,我们的输入被嵌入了后端请求的路径中。
改为administrator?,再次收到同样的错误,进一步确认了我们的输入确实在URL的路径部分。
测试./administrator,响应恢复正常,说明服务器正在解析路径语法。
最后测试../administrator,错误信息再次出现,这表明路径遍历语法生效,只是目标路径不存在。
这个阶段就像在执行一次“盲探”,通过观察服务器的“反应”,我们逐步拼凑出后端处理逻辑的全貌。

⚙️ 第二阶段:导航员——路径遍历与API发现
现在,我们利用路径遍历漏洞,在后端服务器里“导航”。

寻找根目录:我们持续增加../的数量,并用#截尾:

../../../%23 → Invalid route。

../../../../%23 → Not found!

这是一个关键突破!错误从应用层的“路由无效”变成了服务器的“未找到”。这说明我们成功跳出了API的路径范围,到达了Web服务器的某个根级别。

发现藏宝图:这个根级别是寻找秘密文件的绝佳位置。我们尝试访问常见的API文档文件名openapi.json。

构造username=../../../../openapi.json%23。

服务器这次没有报404,而是返回了一个包含新API端点的错误信息。我们成功发现了一个隐藏的内部API端点:/api/internal/v1/users/{username}/field/{field}。

🎯 第三阶段:建筑师——利用参数污染泄露重置令牌
找到这个秘密API后,我们开始尝试利用它。

参数污染初探:根据新端点结构,我们尝试控制username和field两个参数。

构造username=administrator/field/foo%23,服务器报错foo是无效字段。

构造username=administrator/field/email%23,服务器响应恢复正常。这说明我们成功了!我们用前端的username一个输入点,通过路径语法污染,同时控制了后端username和field两个参数。

寻找目标字段:我们需要找到一个有利用价值的字段。通过分析抓包中的JS文件(如forgotPassword.js),我们发现了一个关键参数passwordResetToken。

最终一击:

尝试username=administrator/field/passwordResetToken%23,但收到“API版本不支持”的错误。

这是一个至关重要的线索!说明这个字段是存在的。

结合路径遍历,我们向上跳出当前v1版本,再进入正确的路径,构造出终极Payload:
username=../../v1/users/administrator/field/passwordResetToken%23

发送这个请求,服务器的响应中就直接包含了administrator的密码重置令牌!

🏆 第四阶段:胜利者——获取权限与通关
拿到重置令牌后,后续操作就水到渠成了。

接管账户:在浏览器中访问/forgot-password?passwordResetToken=令牌值,为administrator账户设置一个新密码。

完成任务:用新密码登录administrator账户,进入管理后台,删除目标用户carlos,通关成功。

最后总结

一、核心收获

通过以上四个靶场的实战,可以归纳出 RESTful API 安全测试的三条核心经验

经验 说明
1. 接口信息本身就是攻击面 开发文档、JS 文件、OPTIONS 响应、GET 响应,都会泄露不该暴露的端点、方法和字段
2. 认证 ≠ 授权 四个靶场的漏洞无一例外:系统知道“你是谁”,但从没检查“你能不能做这个操作”
3. 后端对输入的“信任”是漏洞的根源 无论是拼接到 URL,还是直接赋值给对象,信任用户输入而不做校验,就会出问题

二、四类漏洞的速查对比表

漏洞类型 核心问题 检测方法 关键利用点
文档泄露 API 接口未授权暴露 阅读文档 / JS 文件 直接调用未授权接口
参数污染 服务端拼接 URL 未编码 注入 &#%23 篡改内部请求的参数
未使用的 API 端点 前端未调用但后端允许 OPTIONS 探测方法 PATCH / PUT / DELETE 越权
批量赋值 后端全盘接收用户参数 对比 GET/POST 的 JSON 结构 添加隐藏字段(如折扣、权限)

三、通用 API 测试方法论(可复用)

结合四个靶场的经验,可以提炼出一套适用于任何 API 的测试流程

第一步:信息收集

  • 抓取所有 API 请求(登录、浏览、下单、修改资料等)
  • 检查开发文档、Swagger、JS 文件中的接口定义

第二步:方法探测

  • 对每个 API 端点发送 OPTIONS 请求,记录允许的方法
  • 对 GET 响应中的字段保持敏感,对比 POST 请求体

第三步:参数试探

  • 在路径参数中尝试 ../#?
  • 在查询参数中尝试 &=
  • 在 JSON 体中加入响应中见过但请求中没有的字段

第四步:验证越权

  • 用低权限账号尝试访问高权限功能
  • 修改资源 ID 或用户名,访问他人数据

第五步:最终利用

  • 只要能造成实际影响(修改价格、重置密码、删除用户),就算成功

四、防御建议(给开发者 / 防守方)

如果你不是攻击者,而是需要修复这些问题,可以参考以下几点:

问题 防御措施
API 文档泄露 文档接口做访问控制,不使用 /swagger-ui.html 等默认路径暴露到公网
OPTIONS 泄露方法 生产环境关闭不必要的 HTTP 方法,只开放前端实际使用的方法
参数污染 不要手动拼接 URL,使用框架的参数绑定功能;对用户输入做白名单校验
批量赋值 使用白名单模式,明确指定哪些字段可以由用户修改;不要直接用请求体更新模型
越权 每个 API 都要检查“当前用户是否有权限操作目标资源”,不能只检查登录状态

五、最后一句

API 安全的核心,从来不是某个漏洞有多“高级”,而是你是否真正理解了:数据从哪里来,到哪里去,中间有没有人检查“该不该”。

← API攻防安全基础 API 安全基础 →