Server-side request forgery (SSRF)

Backend security Mid-level Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

What do the three checks print?

Python 3.13
import ipaddress
import socket
from urllib.parse import urlsplit


def safe_target(url: str) -> bool:
    parts = urlsplit(url)
    if parts.scheme != "https" or not parts.hostname:
        return False
    infos = socket.getaddrinfo(parts.hostname, 443)
    for info in infos:
        ip = ipaddress.ip_address(info[4][0])
        if not ip.is_global:
            return False
    return True


print(safe_target("https://169.254.169.254/latest/meta-data/"))
print(safe_target("http://example.com/"))
print(safe_target("https://127.0.0.1/admin"))

Options
Hint 1 of 1

What does is_global say about 169.254.x.x?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A False, False, False

Correct. Link-local metadata IP, plain http, and loopback are all rejected.

The check needs https, then resolves the name and refuses any address that isn't global. The metadata address is link-local, the second URL is plain http, and 127.0.0.1 is loopback, so all three fail.

Why not the others?
  • B True, False, False. 169.254.169.254 is link-local, not global.
  • C False, True, False. The scheme must be https.
  • D False, False, True. 127.0.0.1 is loopback.

30-second answer

An attacker can point it at internal addresses: the cloud metadata service at 169.254.169.254 (which can hand out credentials), localhost admin ports or private databases. Defend in layers: allow only https and expected ports, resolve the host and reject private, loopback and link-local addresses, pin the request to the address you checked (so DNS can't change between check and fetch), don't follow redirects blindly, and run fetchers in a network zone that can't reach internals.

Practise saying it · 30 s. No recording, just a timer

Explain SSRF.

Say your answer out loud, then check which of these points you covered:

  • Metadata and internal targets
  • Scheme and port allow-list
  • Check resolved IP
  • Pin, recheck redirects
  • Isolated egress

Deep dive

Require IMDSv2 on AWS, which needs a PUT token request that simple SSRF can't make.

Re-check every redirect hop.

Limit response size and time.

An egress proxy with an allow-list is the strongest control.

Reject non-public targets (one layer, not the whole defence) Ran on Python 3.13.5 · mypy --strict
Python 3.13
import ipaddress
import socket
from urllib.parse import urlsplit


def safe_target(url: str) -> bool:
    parts = urlsplit(url)
    if parts.scheme != "https" or not parts.hostname:
        return False
    infos = socket.getaddrinfo(parts.hostname, 443)
    for info in infos:
        ip = ipaddress.ip_address(info[4][0])
        if not ip.is_global:
            return False
    return True


print(safe_target("https://169.254.169.254/latest/meta-data/"))
print(safe_target("http://example.com/"))
print(safe_target("https://127.0.0.1/admin"))

Common mistakes

  • Blocking the string 'localhost' only.
  • Checking the hostname but not the resolved IP.
  • Following redirects to internal addresses.

Senior take & follow-ups

Name the metadata risk, then layered defences: scheme, resolved-IP checks, pinning, redirects, network isolation.

Follow-up questions

Think of your answer first, then open the hint.

  1. What is DNS rebinding?

    Show hint

    A name resolves to a public IP when checked and a private one when fetched.

  2. Why is metadata the big target?

    Show hint

    It can return cloud credentials for the instance role.

Suggest a fix if something here is wrong or out of date.