# REPRO-2026-00253: Pinpoint through 3.1.0 allows authenticated users to register internal webhook URLs, leading to SSRF when alarm webhooks are delivered. ## Summary Status: published Severity: medium Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00253 CVE: CVE-2026-57947 ## Package Name: pinpoint-apm/pinpoint Ecosystem: maven Affected: <= 3.1.0 (affected from 0 through 3.1.0) Fixed: Not specified in advisory; GitHub issue milestone 3.1.1; no fixed commit identified ## Root Cause # RCA Report: CVE-2026-57947 — Pinpoint Webhook SSRF ## Summary Pinpoint 3.1.0 allows any authenticated user (minimum `ROLE_USER`) to register arbitrary webhook URLs through the `/api/webhook` and `/api/application/webhook` endpoints. The URL validation in `WebhookController.validateURL()` only checks that the supplied string is syntactically a valid URL (`new URL(...).toURI()`), with no filtering of private IP ranges, loopback addresses, or link-local metadata endpoints. The same lack of SSRF protection exists in the batch alarm sender (`WebhookSenderImpl.validateURL()`). When an alarm rule fires, the Pinpoint batch server performs a real HTTP POST to every registered webhook URL, so an attacker can coerce the server into sending requests to internal services such as `127.0.0.1` or `169.254.169.254`. ## Impact - **Package/component affected:** `maven:com.navercorp.pinpoint:pinpoint-web` (also `pinpoint-batch`, which contains the alarm sender) - **Affected versions:** Pinpoint through 3.1.0 (per CVE record and VulnCheck advisory) - **Risk level and consequences:** Medium-to-high. A authenticated user can force the Pinpoint server to issue server-side requests to internal network resources, including cloud metadata endpoints (e.g., AWS IMDSv1 `http://169.254.169.254/`). The webhook payload contains user-group member details, so a controlled internal endpoint can also exfiltrate names, e-mails, and phone numbers. This enables internal port scanning, metadata credential theft, and lateral movement from the Pinpoint server IP. ## Impact Parity - **Disclosed/claimed maximum impact:** SSRF — the server makes unauthorized outbound HTTP requests to internal/attacker-controlled URLs when alarm thresholds are breached. - **Reproduced impact from this run:** The reproduction started the real `pinpointdocker/pinpoint-web:3.1.0` container, authenticated as a non-admin user, registered webhooks pointing to `http://169.254.169.254/...`, `http://127.0.0.1/...`, and a controlled internal listener (`http://ssrf-listener:9999/callback`). It then started the real `pinpointdocker/pinpoint-batch:3.1.0` alarm job and observed the batch server make a real HTTP POST to the controlled listener. - **Parity:** `full` for the SSRF claim — the server-side request was demonstrated end-to-end across the real product. - **Not demonstrated:** Actual extraction of cloud metadata credentials, because no live metadata service was present in the sandbox. The listener stands in for the attacker-controlled internal endpoint and proves the server-side request path. ## Root Cause The vulnerable code is in the webhook module of Pinpoint 3.1.0: - `webhook/src/main/java/com/navercorp/pinpoint/web/webhook/controller/WebhookController.java` (lines 117–120): ```java private void validateURL(Webhook webhook) throws MalformedURLException, URISyntaxException { URL u = new URL(webhook.getUrl()); webhook.setUrl(u.toURI().toString()); } ``` This only validates URL syntax. It does **not** reject `127.0.0.1`, `169.254.169.254`, `10.0.0.0/8`, etc. - `batch-alarmsender/src/main/java/com/navercorp/pinpoint/batch/alarm/sender/WebhookSenderImpl.java` (lines 132–135): ```java private String validateURL(String url) throws MalformedURLException, URISyntaxException { URL u = new URL(url); return u.toURI().toString(); } ``` The same missing checks are applied before the alarm sender calls `restTemplate.exchange(validatedUrl, HttpMethod.POST, httpEntity, String.class)`. There is no post-DNS guard, no IP allowlist/denylist, and no redirect protection. Any scheme/host/address accepted by `java.net.URL` is accepted by the application. The issue was disclosed as GitHub issue #13857 ("Webhook URL missing SSRF protection allows authenticated users to reach internal network resources"). A patched version is not explicitly stated in the advisory, but the issue is closed against the 3.1.1 milestone. ## Reproduction Steps The full reproduction is captured in `bundle/repro/reproduction_steps.sh`. At a high level it: 1. Brings up the real Pinpoint backend (`pinpoint-hbase:3.1.0`, MySQL, ZooKeeper, Redis) and the vulnerable `pinpoint-web:3.1.0` container with basic authentication enabled. 2. Inserts a fake application row into the HBase `ApplicationIndex` table so the alarm batch job recognises the target application. 3. Starts a controlled internal HTTP listener (`ssrf-listener`) inside the same Docker network. 4. Authenticates as `testuser` (a user with only `ROLE_USER`) and registers three webhooks: - `http://169.254.169.254/latest/meta-data/iam/security-credentials/` - `http://127.0.0.1:9999/ssrf-poc` - `http://ssrf-listener:9999/callback` (via the `/api/application/webhook` variant) 5. Verifies that all three internal URLs are stored in the database via `GET /api/webhook?applicationId=`. 6. Authenticates as `adminuser`, creates a user group, and creates an alarm rule (`TOTAL COUNT`, threshold `0`) linked to the listener webhook with `webhookSend=true`. 7. Starts the real `pinpoint-batch:3.1.0` container with `alarmJob` enabled and waits for the batch server to deliver a POST to the controlled listener. Expected evidence: - Registration responses contain `{"result":"SUCCESS"}` and the stored webhook list contains the internal URLs. - `bundle/logs/ssrf-listener.log` shows a POST request from `Apache-HttpClient/5.4.4 (Java/17.0.19)` to `/callback`. - `bundle/logs/ppssrf-batch.log` shows `Successfully sent webhook : Webhook{...url='http://ssrf-listener:9999/callback'...}`. ## Evidence - `bundle/logs/reproduction_steps.log` — full console output of the reproduction, including 401 for unauthenticated access, successful login, webhook registrations, and the listener receiving the POST. - `bundle/logs/ppssrf-web.log` — Pinpoint Web startup logs showing the active Spring profiles (`release`, `basicLogin`) and the vulnerable `webhookEnable=true` configuration. - `bundle/logs/ppssrf-batch.log` — batch alarm job logs showing the `TOTAL COUNT` checker detecting the alarm and the successful webhook send: ``` ResponseCountChecker result is true for application (test-app-...). value is 0. (threshold : 0). Successfully sent webhook : Webhook{webhookId='12'...url='http://ssrf-listener:9999/callback'...} ``` - `bundle/logs/ssrf-listener.log` — the controlled listener records the server-side POST: ``` LISTENER_POST path=/callback headers={...'User-Agent': 'Apache-HttpClient/5.4.4 (Java/17.0.19)'...} LISTENER "POST /callback HTTP/1.1" 200 - ``` - `bundle/repro/artifacts/stored_webhooks.json` — JSON list of stored webhooks, including `http://169.254.169.254/...` and `http://127.0.0.1:9999/ssrf-poc`. - `bundle/repro/artifacts/register_metadata.json`, `register_loopback.json`, `register_listener.json` — each contains `{"result":"SUCCESS"}`. - `bundle/repro/artifacts/create_alarm.json` — alarm rule created with `webhookSend=true` and linked to the listener webhook. ## Recommendations / Next Steps 1. **Fix approach:** Add a strict URL validator that rejects private/reserved/loopback/link-local IP addresses and metadata endpoints both at registration time and in the batch sender. A robust fix should also: - Resolve the hostname to IP addresses and check the resolved set against an allowlist/denylist (post-DNS guard). - Disable HTTP redirects or validate redirect targets. - Optionally require an explicit allowlist of webhook domains/IPs configurable by the operator. 2. **Upgrade guidance:** Upgrade to a patched release > 3.1.0 once available. Until then, restrict access to the `/api/webhook` and `/api/alarmRule` endpoints and apply network-level egress controls on the Pinpoint server. 3. **Testing recommendations:** Regression tests should assert that `127.0.0.1`, `169.254.169.254`, `10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`, and `::1` are rejected by the webhook registration endpoint, and that the alarm sender does not send to those addresses even when already stored in the database. ## Additional Notes - **Idempotency:** The script uses a unique timestamp suffix for the application name, user group, and HBase row qualifier, so repeated runs do not collide with leftover MySQL/HBase state. Two consecutive executions with `REUSE_STACK=1` both completed successfully. - **Limitations:** The reproduction seeds a fake `ApplicationIndex` row in HBase so the alarm batch job recognises the target application. In a real deployment this row is created automatically when a Pinpoint agent reports for the application. Seeding the row is a sandbox-only convenience and does not change the vulnerability path. - **Clean-run note:** A full clean run requires several minutes for the HBase container to bootstrap. The script is designed to perform that bootstrap automatically when `REUSE_STACK` is not set. ## Reproduction Details Reproduced: 2026-07-06T09:03:21.246Z Duration: 3553 seconds Tool calls: 338 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00253 pruva-verify CVE-2026-57947 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00253&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00253/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-57947 - Source: https://github.com/pinpoint-apm/pinpoint/issues/13857 (researcher disclosure), https://www.vulncheck.com/advisories/pinpoint-server-side-request-forgery-via-alarm-webhook-registration, https://www.cve.org/CVERecord?id=CVE-2026-57947 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 18078 bytes) - bundle/repro/rca_report.md (analysis, 8886 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 13959 bytes) - bundle/vuln_variant/rca_report.md (analysis, 9357 bytes) - bundle/artifact_promotion_manifest.json (other, 8521 bytes) - bundle/vuln_variant/source_identity.json (other, 902 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1621 bytes) - bundle/repro/validation_verdict.json (other, 775 bytes) - bundle/repro/runtime_manifest.json (other, 1103 bytes) - bundle/logs/reproduction_steps.log (log, 8835 bytes) - bundle/logs/ppssrf-web.log (log, 168926 bytes) - bundle/logs/ppssrf-batch.log (log, 199898 bytes) - bundle/logs/ssrf-listener.log (log, 780 bytes) - bundle/logs/vuln_variant/harness.log (log, 3699 bytes) - bundle/logs/vuln_variant/reproduction_steps.log (log, 21404 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 11088 bytes) - bundle/vuln_variant/variant_manifest.json (other, 4241 bytes) - bundle/vuln_variant/validation_verdict.json (other, 1113 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 1457 bytes) - bundle/logs/vuln_variant/compile.log (log, 0 bytes) - bundle/vuln_variant/artifacts/source_identity.txt (other, 156 bytes) - bundle/vuln_variant/artifacts/src/webhook/support/WebhookUrlValidator.java (other, 9468 bytes) - bundle/vuln_variant/artifacts/src/batch/alarm/sender/WebhookDnsResolver.java (other, 2165 bytes) - bundle/vuln_variant/artifacts/src/test/VariantBypassHarness.java (other, 8641 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00253 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00253/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00253 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev