cybersecurity · intermediate · ~15 min · safe pentest lab
Implement a deny-by-default SSRF egress filter that correctly classifies loopback, RFC 1918 private, and link-local IPv4 ranges (including the cloud metadata address) using precise CIDR boundary checks on octet values.
A server-side URL fetcher (image proxy, webhook, PDF renderer) resolves a user-supplied hostname and then connects to the resulting IPv4 address. If it connects to internal addresses, an attacker performs Server-Side Request Forgery (SSRF): they make your server reach loopback services, private LANs, or the cloud metadata endpoint 169.254.169.254 to steal IAM credentials.
The insecure assumption is "the user gave me a URL, so the destination is external." DNS can resolve to anything, so the resolved IP must be validated by an allow-by-default-deny guard before the socket is opened.
You receive the already-resolved address as a fixed 4-byte array ip (ip[0] is the high octet). Implement:
int ssrf_blocked(const unsigned char ip[4]);
Return 1 if the fetcher MUST BLOCK this address, else 0 (allow). Block these ranges:
127.0.0.0/810.0.0.0/8172.16.0.0/12 (i.e. 172.16.x.x through 172.31.x.x)192.168.0.0/16169.254.0.0/16 (which contains the cloud metadata address 169.254.169.254)This operates only on the fixed in-harness buffer. It never touches a live target, a socket, or the filesystem. Deny (block) the sensitive ranges precisely and allow everything else.
ssrf_blocked(metadata) -> 1 (metadata = {169,254,169,254})ssrf_blocked(public_dns) -> 0 (public_dns = {8,8,8,8})A const unsigned char array ip[4] holding one resolved IPv4 address, most-significant octet first (ip[0].ip[1].ip[2].ip[3]).
int: 1 if the address falls in a blocked (internal/loopback/link-local/private) range, 0 if it is safe to fetch.
Read exactly the 4 bytes ip[0..3]; never index past the array. Octet values are 0-255. No allocation, no I/O, no network. Pure classification of the fixed buffer. The 172.16/12 range is only 172.16.x.x through 172.31.x.x, not all of 172.x.
int ssrf_blocked(const unsigned char ip[4]){
/* TODO: inspect ip[0..3] and block SSRF-sensitive ranges:
127/8 loopback, 10/8, 172.16/12, 192.168/16, 169.254/16.
Deny by default for those; allow (return 0) everything else.
Insecure stub below allows nothing to be blocked. Replace it. */
(void)ip;
return -1;
}Blocking all of 172.x instead of only 172.16-172.31 (over-blocks) or using 172.16 <= b < 32 wrong. Special-casing only 169.254.169.254 while forgetting the rest of 169.254/16. Treating 192.168 as 192.x. Returning 0/1 inverted (allow when should block). Reading ip[4] or a length byte that doesn't exist. Comparing signed chars so high octets like 169/172/192 wrap negative.
172.15.x and 172.32.x must be ALLOWED (just outside 172.16/12); 172.16.0.0 and 172.31.255.255 are the inclusive block boundaries. 127.255.255.255 is still loopback. 192.167.x and 192.169.x are allowed; only 192.168 is private. 169.254.169.254 is a link-local address and must be blocked like the rest of 169.254/16. 0.0.0.0 and 255.255.255.255 are outside the listed private ranges, so this guard allows them (0 = allow).
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.