GitHub Advisoryadvisory · Go
GHSA-chwm-m7g7-685g Dragonfly scheduler v1 and v2 gRPC unauthenticated SSRF via attacker-controlled PeerHost in DownloadTinyFile
MEDIUM
Official page ↗
- Aliases
- CVE-2026-54637
- Ecosystem
- Go
- Packages
- d7y.io/dragonfly/v2
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:P
- Weaknesses
- CWE-918
- Reviewed
- 2026-07-06
Affected packages
• Go d7y.io/dragonfly/v2: < 2.4.4-rc.3 → fixed in 2.4.4-rc.3
Description
Summary
The Dragonfly scheduler's v1 gRPC service contains an unauthenticated Server-Side Request Forgery (SSRF). When a peer reports a successful download of a TINY task, the scheduler calls `Peer.DownloadTinyFile()` and issues an HTTP `GET` to a host and port taken verbatim from the attacker-controlled `PeerHost.Ip` / `PeerHost.DownPort` fields of the gRPC request body. The HTTP client uses a bare `http.Transport` with no address validation, so a remote, unauthenticated client can force the scheduler to connect to arbitrary internal addresses, including `127.0.0.1` (loopback), `169.254.0.0/16` (link-local, e.g. cloud metadata), and RFC1918 ranges. The fetched response is stored in `Task.DirectPiece` and can subsequently be served to other peers, making this a read-SSRF with a data-exfiltration path.
The manager's preheat code path already wraps its HTTP client with `nethttp.NewSafeDialer()` (which rejects non-global-unicast destinations); the scheduler's `DownloadTinyFile` path is missing this guard (sibling gap).
Severity
Medium. The attack requires no authentication (the scheduler gRPC server runs with insecure transport credentials by default and has no auth interceptor), and the destination is fully attacker-controlled. Impact is limited to read-SSRF: blind reachability probing of internal hosts/ports plus exfiltration of up to `TinyFileSize` (128) bytes per task from internal HTTP services into `Task.DirectPiece`. It is not remote code execution, and `PeerHost.DownPort` is constrained by proto validation to `>= 1024`, which excludes destination port 80.
Affected component
• Repository: `dragonflyoss/dragonfly`
• Go module: `d7y.io/dragonfly/v2`
• Component: scheduler, v1 gRPC protocol
• Affected file: `scheduler/resource/standard/peer.go` (`DownloadTinyFile`), reached via `scheduler/service/service_v1.go` (`storeHost`, `RegisterPeerTask`, `ReportPeerResult`, `handlePeerSuccess`)
• Verified against: latest release `v2.4.4-rc.2` (commit `0822e3aecc3369017d6b25c9441ff6f318129b31`)
Description / Root cause
The scheduler exposes the v1 gRPC service without authentication by default:
• In `scheduler/scheduler.go` lines 235-246, mTLS is only configured when `cfg.Server.TLS != nil`; otherwise the server is created with `rpc.NewInsecureCredentials()`.
• The default configuration produced by `scheduler/config/config.go` `New()` (line 336) does not set `Server.TLS`, so the default deployment uses insecure credentials.
• The gRPC interceptor chain in `pkg/rpc/scheduler/server/server.go` lines 71-86 contains ratelimit, error-conversion, prometheus, zap-logging, validator, and recovery interceptors, but no authentication interceptor.
A remote client can therefore invoke `RegisterPeerTask` and `ReportPeerResult` without credentials. The `PeerHost` message carried in the request is consumed by `storeHost` in `scheduler/service/service_v1.go` lines 816-845, which copies `peerHost.Ip` and `peerHost.DownPort` directly into `resource.Host.IP` and `resource.Host.DownloadPort` with no destination-address restriction. The proto validator only requires `PeerHost.Ip` to be a syntactically valid IP (`net.ParseIP != nil`) and `DownPort` in `1024, 65535)`; it does not restrict the address to global-unicast, so `127.0.0.1`, `169.254.169.254`, and RFC1918 addresses all pass validation.
When the reported peer is a TINY task, [`handlePeerSuccess` lines 1176-1202 calls `peer.DownloadTinyFile()` and stores the result in `peer.Task.DirectPiece`. The sink, `DownloadTinyFile` lines 435-478, builds the URL from `net.JoinHostPort(p.Host.IP, p.Host.DownloadPort)` and dispatches it through a bare `http.Transport` (`TLSClientConfig: InsecureSkipVerify: true`) with no `DialContext`/socket control:
```golang
targetURL := url.URL{
Scheme: "http",
Host: net.JoinHostPort(p.Host.IP, strconv.Itoa(int(p.Host.DownloadPort))),
Path: fmt.Sprintf("download/%s/%s", p.Task.ID[:3], p.Task.ID),
RawQuery: fmt.Sprintf("peerId=%s", p.ID),
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL.String(), nil)
if err != nil {
return []byte{}, err
}
req.Header.Set(headers.Range, fmt.Sprintf("bytes=%d-%d", 0, p.Task.ContentLength.Load()-1))
p.Log.Infof("download tiny file %s, header is : %#v", targetURL.String(), req.Header)
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := client.Do(req)
```
By contrast, the manager's preheat path in `internal/job/image.go` line 211 sets `DialContext: nethttp.NewSafeDialer().DialContext`, and `safeSocketControl` in `pkg/net/http/http.go` lines 60-80 rejects any destination where `!ip.IsGlobalUnicast()`. The scheduler `DownloadTinyFile` path is missing this protection.
Proof of Concept / End-to-end verification
All three drivers below were built inside the `d7y.io/dragonfly/v2` module at commit `0822e3aecc3369017d6b25c9441ff6f318129b31` (release v2.4.4-rc.2), so they invoke the genuine, unmodified production sink `standard.Peer.DownloadTinyFile()` and the genuine `nethttp.NewSafeDialer()` / `safeSocketControl`. No reimplementation of the sink is used in the positive control.
1) Pinned build
```bash
git clone --depth 1 --branch v2.4.4-rc.2 \
https://github.com/dragonflyoss/dragonfly.git
cd dragonfly
git rev-parse HEAD # 0822e3aecc3369017d6b25c9441ff6f318129b31
go version # go1.26.1
```
2) Confirming the deployed scheduler binary is unauthenticated by default
The scheduler builds and serves the v1 gRPC service with insecure transport credentials when `Server.TLS` is unset (the shipped default). The relevant decision is at `scheduler/scheduler.go:235-246`:
```golang
if cfg.Server.TLS != nil {
// Initialize grpc server with tls.
transportCredentials, err := rpc.NewServerCredentials(cfg.Server.TLS.CACert, cfg.Server.TLS.Cert, cfg.Server.TLS.Key)
if err != nil {
logger.Errorf("failed to create server credentials: %v", err)
return nil, err
}
schedulerServerOptions = append(schedulerServerOptions, grpc.Creds(transportCredentials))
} else {
// Initialize grpc server without tls.
schedulerServerOptions = append(schedulerServerOptions, grpc.Creds(rpc.NewInsecureCredentials()))
}
```
and the v1 handlers `RegisterPeerTask` / `ReportPeerResult` are registered with no auth interceptor (`pkg/rpc/scheduler/server/server.go:71-90`). Any network client can therefore drive the chain `RegisterPeerTask -> (TINY task) -> ReportPeerResult -> handlePeerSuccess -> DownloadTinyFile` using a `PeerHost` whose `Ip`/`DownPort` point at an internal target.
3) Attacker-supplied host (exactly as storeHost ingests it)
`storeHost` (service_v1.go:816) constructs the resource host straight from the gRPC `PeerHost`:
```golang
host := resource.NewHost(
peerHost.Id, peerHost.Ip, peerHost.Hostname, peerHost.Hostname,
peerHost.Rpc…
Related
Alias · CVE (1)
- CVE-2026-54637 Dragonfly is an open source P2P-based file distribution and image acceleration system. vulnerability
Weakness · CWE (1)
- CWE-918 Server-Side Request Forgery weakness
References
- https://github.com/dragonflyoss/dragonfly/security/advisories/GHSA-chwm-m7g7-685g web
- https://github.com/dragonflyoss/dragonfly/security/advisories/GHSA-fhf9-m53m-863g web
- https://github.com/dragonflyoss/Dragonfly2/blob/main/scheduler/resource/standard/peer.go#L457-L459 web
- https://github.com/dragonflyoss/dragonfly package
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/internal/job/image.go#L211 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/pkg/net/http/http.go#L50-L80 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/pkg/rpc/scheduler/server/server.go#L71-L90 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/scheduler/resource/standard/peer.go#L435-L478 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/scheduler/scheduler.go#L235-L246 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/scheduler/service/service_v1.go#L1176-L1202 web
- https://github.com/dragonflyoss/dragonfly/blob/0822e3aecc3369017d6b25c9441ff6f318129b31/scheduler/service/service_v1.go#L816-L845 web