đź§  Server-side Request Forgery

What is it?

  • Concept: A server-side component (a webhook fetcher, URL “preview”/screenshot service, PDF/report generator, redirect follower…) makes an outbound request to a URL you influence. Since the request originates from the server, it lands wherever the server can reach — internal-only endpoints, localhost-bound services, the cloud metadata endpoint, or hosts behind network segmentation you’d otherwise never touch directly.
  • Impact: Bypassing IP/localhost-based auth checks, reading internal-only APIs, and — if the fetching library speaks more than HTTP — smuggling raw protocol traffic to internal services (Redis, gRPC, SMTP) for further exploitation/RCE.

How it works

  1. Find a feature where the server fetches a URL you can influence (webhook registration, “fetch a link to preview it”, screenshot/report generation, redirect-follow, image/file fetch-by-URL).
  2. Work out what’s actually being validated — hostname string, IP, scheme, whether redirects get re-checked.
  3. Craft a URL that satisfies the check but still routes to the real target, or exploits what the check doesn’t re-validate (e.g. a redirect hop).

Exploitation

Prerequisites

  • A server-side component that fetches an attacker-influenceable URL
  • The destination isn’t fully re-validated (hostname allowlist, IP range, scheme, post-redirect)

Attack Vectors

Direct — sometimes there’s nothing to bypass.

GET /guardian?quote=http://localhost:1337/think

Not every SSRF sink filters anything at all — check the obvious payload before reaching for a bypass. (from HTB - Secret Alley)

Bypassing a suffix-based hostname allowlist.

if (!direction.endsWith("localhost") && direction !== "localhost")
    return error;

endsWith() checks a literal string suffix, not a real domain boundary. A hostname that merely ends with the allowed word satisfies the check even if it’s an unrelated, attacker-registrable domain (e.g. for a corp-suffix check like endsWith("internal.corp"), registering notreallyinternal.corp passes too). Always validate hostnames by exact match, or a proper domain-boundary check (. immediately before the suffix) — never raw string suffix. (pattern from HTB - Secret Alley)

Reaching an SSRF-capable endpoint hidden behind virtual-host routing. If the vulnerable endpoint lives on a different vhost than the one your request naturally resolves to (a default_server silently swallowing unmatched Host headers), it can 404 even though it exists — the request never reached the right vhost, SSRF isn’t the problem yet.

# HTTP/1.0 doesn't require a Host header — an empty one reveals what the default vhost is actually bound to
curl --http1.0 -H 'Host:' http://target/anything
 
# Now hit the real endpoint with the correct Host header
curl -H 'Host: guardian.<leaked-domain>' 'http://target/guardian?quote=http://localhost:1337/think'

(from HTB - Secret Alley)

Bypassing a Referer-based anti-SSRF check via meta-refresh. If the “don’t let people redirect me to myself” check inspects the Referer header instead of validating the actual destination, redirect through an HTML <meta http-equiv="refresh"> tag instead of a Location header or JS location.href — meta-refresh navigations don’t send a Referer at all:

<html>
  <head>
    <meta http-equiv="refresh" content="0;url=http://localhost/flag">
  </head>
</html>

Host this externally and get the target’s own headless-browser/screenshot service to visit it. When it follows the meta-redirect, the target itself issues the follow-up request from its own loopback — any “is this really localhost” check now trivially passes, because it genuinely is. (from HTB - baby CachedView)

cloudflared tunnel --protocol http2 --url http://localhost:5000
# ngrok's interstitial warning page breaks headless bots; cloudflared has none

Protocol smuggling past the HTTP layer. If the fetching library supports schemes beyond http(s):// (most curl-backed fetchers do by default), SSRF isn’t capped at “read an internal HTTP page.” gopher:// lets you send arbitrary raw bytes — enough to speak to non-HTTP internal services directly: Redis, SMTP, or (as in HTB - Artificial University) a raw gRPC/HTTP2 frame to an internal gRPC service, turning SSRF into RCE against a service that was never meant to be internet-reachable. Building the exact byte sequence is protocol-specific and needs a purpose-built encoder — the takeaway is to check which schemes the fetcher accepts before assuming the damage stops at HTTP.

Localhost bypass generator, for weak IP/hostname blacklists:

def generate_localhost_bypasses(ip: str = "127.0.0.1") -> list[str]:
    """Alternate representations of an IP to throw at a regex/string-based SSRF blacklist."""
    o = [int(x) for x in ip.split(".")]
    decimal = (o[0] << 24) + (o[1] << 16) + (o[2] << 8) + o[3]
 
    return [
        ip,                                                              # 127.0.0.1
        f"{o[0]}.{o[3]}",                                                 # 127.1        (short form)
        f"0{o[0]:o}.0{o[1]:o}.0{o[2]:o}.0{o[3]:o}",                       # octal
        f"0x{o[0]:02x}{o[1]:02x}{o[2]:02x}{o[3]:02x}",                    # hex, single token
        str(decimal),                                                     # decimal, single token
        f"::ffff:{ip}",                                                   # IPv4-mapped IPv6
        "[::1]",                                                          # IPv6 loopback literal
        f"{ip}.nip.io",                                                   # public DNS that resolves back to ip
        "localtest.me",                                                   # always resolves to 127.0.0.1
    ]
 
for bypass in generate_localhost_bypasses():
    print(bypass)

Notes

  • Allowlists checked before following a redirect are trivially bypassed by redirecting to the disallowed target after the check passes — if the fetcher follows redirects, re-validate on every hop, or just don’t follow them.
  • 169.254.169.254 (cloud metadata endpoint) is the highest-value target when the vulnerable fetcher runs inside a cloud VM — try it before anything else if the environment looks cloud-hosted.
  • A “preview/screenshot this URL” feature is functionally identical to any other SSRF sink. Anything that drives a headless browser (Selenium, Puppeteer) or a PDF/report renderer to a URL and hands you back the result is worth treating as SSRF, not just literal fetch-and-forward endpoints — see HTB - baby CachedView.

Mitigation

  • Allowlist by exact hostname match or resolved IP, not string-suffix checks — resolve the hostname and validate the actual IP against a strict allowlist, ideally re-checked after DNS resolution (helps against (incomplete) DNS Rebinding too).
  • Disable redirect-following on server-side fetches, or re-validate the destination on every redirect hop.
  • Restrict the URL scheme explicitly to http/https — reject gopher://, file://, dict://, etc. at the fetcher level.
  • Network-level segmentation: the fetching service shouldn’t have route-level access to internal-only services (metadata endpoint, Redis, internal gRPC) in the first place — don’t rely on the application layer as the only control.
FileCreated
(incomplete) HTB - Secure NotesMay 22, 2026
BKSEC - Report the Violation 2Monday, February 23rd 2026, 9:50:38 pm
HTB - Artificial UniversityThursday, April 9th 2026, 11:36:53 pm
HTB - baby CachedViewFriday, April 10th 2026, 8:25:22 am
HTB - Secret AlleyFriday, May 22nd 2026, 2:43:18 pm
HTB - Tornado ServiceWednesday, April 15th 2026, 7:51:50 pm
UMassCTF - Building Blocks MarketSaturday, April 11th 2026, 11:41:56 pm
UMassCTF - Turncoat’s TreasureMonday, April 13th 2026, 9:48:21 pm