cybersecurity · intermediate · ~15 min · safe pentest lab

SSRF Defence: Reject Private IPv4 Ranges

Classify an IPv4 address against CIDR-defined private ranges using octet arithmetic, and apply deny-by-default reasoning to block SSRF pivots to internal, loopback, and cloud-metadata hosts.

Challenge

The problem

Server-Side Request Forgery (SSRF) happens when a service fetches a URL supplied by an attacker. If your fetcher does not classify the destination address, an attacker can point it at 169.254.169.254 (cloud metadata), 127.0.0.1 (loopback), or an internal 10.x/192.168.x host and pivot into infrastructure that was never meant to be reachable from the outside.

The insecure assumption is "any address the resolver returned is safe to connect to." A hardened fetcher must deny by default: only allow the request after it has confirmed the resolved address is a routable, public destination.

Your task

Implement is_private_ipv4. It receives a resolved IPv4 address as a fixed 4-byte array (ip[0] is the most significant octet). Return 1 if and only if the address falls in one of these private / non-routable ranges, otherwise return 0:

  • 10.0.0.0/8
  • 172.16.0.0/12
  • 192.168.0.0/16
  • 127.0.0.0/8 (loopback)
  • 169.254.0.0/16 (link-local, includes cloud metadata)

The caller uses your 1 result to reject the outbound request. The array is a fixed in-harness buffer; you never touch the network or filesystem.

Edge cases

  • 172.15.x and 172.32.x are public — only the second octet range 16..31 is private for the /12.
  • 192.167.x is public; only 192.168.x is private.
  • 0.0.0.0 and 255.255.255.255 are not in the listed private ranges, so they return 0 here.

Example

is_private_ipv4(metadata_ip) -> 1   // metadata_ip = {169,254,169,254}
is_private_ipv4(google_dns)  -> 0   // google_dns  = {8,8,8,8}

Input format

A read-only array of exactly 4 unsigned char octets, most-significant octet first (ip[0].ip[1].ip[2].ip[3]).

Output format

int: 1 if the address is in a private/non-routable range listed above, 0 otherwise.

Constraints

Each octet is 0..255. Read only the 4 provided bytes; never index past the fixed buffer. No network, filesystem, or dynamic allocation. Pure classification of the passed-in address.

Starter code

int is_private_ipv4(const unsigned char ip[4]){
    /* TODO: return 1 only for the private/non-routable ranges:
       10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,
       127.0.0.0/8, 169.254.0.0/16. Deny by default.
       This stub is not implemented. */
    (void)ip;
    return -1;
}

Common mistakes

Treating the whole 172.0.0.0/8 as private instead of only the /12 (second octet 16..31); checking 192.x instead of 192.168.x; forgetting loopback (127/8) or link-local (169.254/16); comparing signed values so a byte >127 misbehaves; reading ip[4] or beyond the 4-byte buffer.

Edge cases to handle

172.15.255.255 and 172.32.0.0 are public (only 172.16-172.31 are private); 192.167.x is public while 192.168.x is private; 169.253.x is public while 169.254.x is link-local; 10.255.255.255 is still private; 0.0.0.0 and 255.255.255.255 return 0 for this list.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.