CVE-2026-57947: Verified Reproduction
CVE-2026-57947: Pinpoint through 3.1.0 allows authenticated users to register internal webhook URLs, leading to SSRF when alarm webhooks are delivered.
CVE-2026-57947 is verified against pinpoint-apm/pinpoint · maven. Affected versions: <= 3.1.0 (affected from 0 through 3.1.0). Fixed in Not specified in advisory; GitHub issue milestone 3.1.1; no fixed commit identified. Vulnerability class: SSRF. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00253.
What Is CVE-2026-57947?
CVE-2026-57947 is a medium-severity (CVSS 6.3) server-side request forgery (CWE-918) in Pinpoint. An authenticated user can register webhook URLs that resolve to internal hosts or cloud metadata endpoints, and when an alarm fires Pinpoint issues the request. Pruva reproduced it (reproduction REPRO-2026-00253).
CVE-2026-57947 Severity & CVSS Score
CVE-2026-57947 is rated medium severity, with a CVSS base score of 6.3 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
Affected pinpoint-apm/pinpoint Versions
pinpoint-apm/pinpoint · maven versions <= 3.1.0 (affected from 0 through 3.1.0) are affected.
How to Reproduce CVE-2026-57947
pruva-verify REPRO-2026-00253 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00253/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-57947
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
authenticated user registers an arbitrary webhook URL through /api/webhook or /api/application/webhook
- POST /api/webhook (or /api/application/webhook) stores internal URL; alarm batch job sends HTTP POST to that URL
How the agent worked
Root Cause and Exploit Chain for CVE-2026-57947
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.
- Package/component affected:
maven:com.navercorp.pinpoint:pinpoint-web(alsopinpoint-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.0container, authenticated as a non-admin user, registered webhooks pointing tohttp://169.254.169.254/...,http://127.0.0.1/..., and a controlled internal listener (http://ssrf-listener:9999/callback). It then started the realpinpointdocker/pinpoint-batch:3.1.0alarm job and observed the batch server make a real HTTP POST to the controlled listener. - Parity:
fullfor 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):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):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:
- Brings up the real Pinpoint backend (
pinpoint-hbase:3.1.0, MySQL, ZooKeeper, Redis) and the vulnerablepinpoint-web:3.1.0container with basic authentication enabled. - Inserts a fake application row into the HBase
ApplicationIndextable so the alarm batch job recognises the target application. - Starts a controlled internal HTTP listener (
ssrf-listener) inside the same Docker network. - Authenticates as
testuser(a user with onlyROLE_USER) and registers three webhooks:http://169.254.169.254/latest/meta-data/iam/security-credentials/http://127.0.0.1:9999/ssrf-pochttp://ssrf-listener:9999/callback(via the/api/application/webhookvariant)
- Verifies that all three internal URLs are stored in the database via
GET /api/webhook?applicationId=<app>. - Authenticates as
adminuser, creates a user group, and creates an alarm rule (TOTAL COUNT, threshold0) linked to the listener webhook withwebhookSend=true. - Starts the real
pinpoint-batch:3.1.0container withalarmJobenabled 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.logshows a POST request fromApache-HttpClient/5.4.4 (Java/17.0.19)to/callback.bundle/logs/ppssrf-batch.logshowsSuccessfully 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 vulnerablewebhookEnable=trueconfiguration.bundle/logs/ppssrf-batch.log— batch alarm job logs showing theTOTAL COUNTchecker 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, includinghttp://169.254.169.254/...andhttp://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 withwebhookSend=trueand linked to the listener webhook.
Recommendations / Next Steps
- 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.
- Upgrade guidance: Upgrade to a patched release > 3.1.0 once available. Until then, restrict access to the
/api/webhookand/api/alarmRuleendpoints and apply network-level egress controls on the Pinpoint server. - 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::1are 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=1both completed successfully. - Limitations: The reproduction seeds a fake
ApplicationIndexrow 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_STACKis not set.
CVE-2026-57947 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
docker ps && echo docker available{"cwd":"/data/pruva/runs/37f6347b-a435-4fc6-8a66-89724db7070e","duration_ms":124,"exit_code":0,"stderr":"","stdout":"CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\ncf8c5fcf1692 wordpress:php8.2-apache \"docker-entrypoint.s…\" 3 minutes ago Up 3 minutes 0.0.0.0:18083->80/tcp, [::]:18083->80/tcp blocksy_test-wp_vuln-1\n3ffa4688f443 wordpress:php8.2-apache \"docker-entrypoint.s…\" 4 minutes ago Up 3 minutes 0.0.0.0:18082->80/tcp, [::]:18082->80/tcp … [truncated]Artifacts and Evidence for CVE-2026-57947
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-57947
Upgrade pinpoint-apm/pinpoint · maven to Not specified in advisory; GitHub issue milestone 3.1.1; no fixed commit identified or later.
FAQ: CVE-2026-57947
How is CVE-2026-57947 triggered?
Which versions of Pinpoint are affected by CVE-2026-57947?
How can I reproduce CVE-2026-57947?
References for CVE-2026-57947
Authoritative sources for CVE-2026-57947 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.