| tiêu đề | FIT2CLOUD MeterSphere v3.6.9-lts (earlier 3.x LTS releases share the same import code path) server-side request forgery (CWE-918) |
|---|
| Mô tả | API-definition import makes the server fetch a caller-supplied URL (project member)
API-definition import makes the server fetch a caller-supplied URL (project member)
Data flow: HTTP multipart part request.swaggerUrl at POST /api/definition/import → Swagger3ParserApiDefinition.testUrlTimeout -> new URI(swaggerUrl).toURL().openConnection().connect() and OpenAPIParser.readLocation
AFFECTED INTERFACES
| Interface |
|---|
| POST /api/definition/import multipart request {projectId, platform=Swagger3, swaggerUrl} |
| POST /api/definition/schedule/add (JSON projectId, name, swaggerUrl, enable, value) |
| POST /api/definition/schedule/update (JSON id, projectId, name, swaggerUrl, enable, value) |
Primary route detail:
POST /api/definition/import (multipart/form-data), part request:
{"projectId":"<project>","platform":"Swagger3","swaggerUrl":"http://<host>:<port>/<path>"}
The optional file part is not needed. The route is registered on the API-definition controller and
carries @RequiresPermissions(PROJECT_API_DEFINITION:READ+IMPORT). The default project_member role
is granted exactly that permission by the application's own seed data
(backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql:218), so an
ordinary project member - not a system administrator - can call the route. What was tested: the two
scheduled routes were exercised with an ordinary project-member account (roles
project_member and org_member, no administrator role); the manual import route was exercised with
the built-in administrator account in the isolated instance, and the permission it checks is the same
PROJECT_API_DEFINITION:READ+IMPORT permission that the shipped seed data grants to project_member.
ROOT CAUSE
Source (HTTP multipart part):
- ImportRequest.swaggerUrl (backend/services/api-test/src/main/java/io/metersphere/api/dto/request/ImportRequest.java:16),
populated from the request part of POST /api/definition/import
(ApiDefinitionController.testCaseImport, .../api/controller/definition/ApiDefinitionController.java:295-300).
Sink (server-side HTTP request to the caller-chosen URL, twice):
- ApiDefinitionImportService.apiDefinitionImport(...) selects the Swagger3 parser for
platform=Swagger3 and calls Swagger3ParserApiDefinition.parse(...).
- Swagger3ParserApiDefinition.testUrlTimeout(String swaggerUrl)
(.../parser/api/dataimport/Swagger3ParserApiDefinition.java:57-70) does
new URI(swaggerUrl).toURL().openConnection().connect() with a 3 s connect timeout and no host,
scheme or address validation. The local canary received this request.
- Directly afterwards the same parser calls
new OpenAPIParser().readLocation(request.getSwaggerUrl(), auths, null), which performs the second
server-side fetch of the same URL and returns the body to the importer.
Neither call consults a destination allowlist, private-address policy or DNS check. The root cause is
treating a caller-supplied URL as a trusted import source: the import permission is intended to let a
project member import an API description, not to grant the server-side ability to reach arbitrary
internal endpoints (http://127.0.0.1:*, RFC1918, link-local, cloud metadata).
PER-ROUTE DELTAS
- Ordinary-member SSRF through the scheduled Swagger import
- HTTP JSON field swaggerUrl (plus cron field value) at POST /api/definition/schedule/add → ApiDefinitionScheduleService.createSchedule persists the URL -> Quartz SwaggerUrlImportJob -> Swagger3ParserApiDefinition.testUrlTimeout/parse -> URI.toURL().openConnection()
- Ordinary-member SSRF through the scheduled Swagger import update
- HTTP JSON field swaggerUrl (with id of an api_definition_swagger row owned by the caller) at POST /api/definition/schedule/update → ApiDefinitionScheduleService.updateSchedule persists the URL -> Quartz SwaggerUrlImportJob -> Swagger3ParserApiDefinition.testUrlTimeout/parse -> URI.toURL().openConnection()
PROOF OF CONCEPT
Baseline: commit cf7a649a71f4db27c42547d7aa62816826d645ff, server on 127.0.0.1:18080. The
destination is a test-local HTTP listener at 192.168.96.4:18081 that logs every request.
1. Sign in (the session cookie the import routes require):
$ curl -s -c cookies.txt -X POST 'http://127.0.0.1:18080/login' \
-H 'Content-Type: application/json' -d '{"username":"<user>","password":"<throwaway>"}'
HTTP/1.1 200
2. Import an API definition whose Swagger source is the listener:
$ curl -s -b cookies.txt -X POST 'http://127.0.0.1:18080/api/definition/import' \
-F 'request={"projectId":"100001100001","platform":"Swagger3","swaggerUrl":"http://192.168.96.4:18081/ssrf-canary"};type=application/json'
HTTP/1.1 500
3. What the listener logged - the server-side fetch performed by
Swagger3ParserApiDefinition.testUrlTimeout:
$ cat mock-requests.log
GET /ssrf-canary
The import itself ends in an HTTP 500 and persists nothing, but the request to the caller-chosen URL
has already been sent, and the same URL is fetched a second time when OpenAPIParser.readLocation
reads the document.
4. Negative control: the same call without a session returns HTTP 302 and produces no listener
hit.
The two scheduled variants (POST /api/definition/schedule/add and
POST /api/definition/schedule/update) carry the same swaggerUrl field and are fetched by the
Quartz job; those routes were exercised with an ordinary project-member account. The manual import
route was exercised with the built-in administrator account in the isolated instance; the permission
it checks (PROJECT_API_DEFINITION:READ+IMPORT) is granted to the default project_member role by
the shipped seed data.
IMPACT
See the interface and root-cause sections: an actor who satisfies the stated preconditions makes the server perform a request or operation on a destination/target of the attacker's choosing, crossing the trust boundary described above.
- Project repository: https://github.com/metersphere/metersphere
- Security policy: `SECURITY.md` in that repository.
- **Vendor contact**: MeterSphere / FIT2CLOUD security contact - [email protected]
- **Status**: vendor notified 2026-09-14 by email to [email protected]; thread subject: `SSRF in MeterSphere API-definition import (swaggerUrl) reachable by an ordinary project member`; not publicly disclosed; no CVE assigned
- **Duplicate check**: public advisories (NVD/OSV/GitHub) and the project issue tracker were searched before reporting; please tell us if this duplicates something already tracked internally.
|
|---|
| Người dùng | lucasg2g (UID 84737) |
|---|
| Đệ trình | 15/09/2026 09:21 (cách đây 29 ngày) |
|---|
| Kiểm duyệt | 12/10/2026 15:08 (27 days later) |
|---|
| Trạng thái | được chấp nhận |
|---|
| Mục VulDB | 416896 [fit2cloud MeterSphere đến 3.6.9-lts Swagger3 Parser Swagger3ParserApiDefinition.java Swagger3ParserApiDefinition.testUrlTimeout swaggerUrl nâng cao đặc quyền] |
|---|
| điểm | 17 |
|---|