cybersecurity · beginner · ~15 min · safe pentest lab

SSRF Defense: URL Scheme Allow-List

Learn to build a deny-by-default input gate as the first layer of SSRF defense: allow-list known-safe URL schemes case-insensitively, reject everything else (file/gopher/ftp/dict/...), and bounds-check the input so a short or NULL string can never cause an over-read.

Challenge

SSRF starts at the URL scheme

A Server-Side Request Forgery (SSRF) vulnerability lets an attacker make your server fetch a URL of their choosing. The asset at risk is everything your server can reach that the attacker cannot: cloud metadata endpoints, internal admin panels, localhost services, and the local filesystem. The insecure assumption is "any string that looks like a URL is safe to fetch." An attacker who controls the URL will hand you file:///etc/passwd, gopher://127.0.0.1:11211/ (to talk to Memcached), dict://, or ftp:// to pivot into places a plain web fetch never should.

The first, cheapest defensive layer is a scheme allow-list: before a fetcher ever opens a connection, it must confirm the URL uses an approved transport. You are implementing that gate.

Your task

Implement allowed_url_scheme(const char *url). Return 1 iff url begins with http:// or https://, compared case-insensitively (HTTP://, HtTpS:// are fine). Return 0 for every other scheme and for malformed input. This is a deny-by-default check: anything you are not certain about is rejected.

The function reads a NUL-terminated C string only. It never opens a socket, resolves a host, or touches the filesystem — it is pure string validation over a buffer the harness owns.

Edge cases you must handle

  • url == NULL must return 0 (do not dereference it).
  • The empty string "" returns 0.
  • A truncated prefix like "http:/" (one slash short) returns 0 — do not read past the terminator while comparing.
  • The scheme must be at the start: "xhttp://" returns 0.

Example

allowed_url_scheme(http_url)   -> 1
allowed_url_scheme(file_url)   -> 0
allowed_url_scheme(NULL)       -> 0

Input format

A single argument const char *url: either NULL or a NUL-terminated C string holding a candidate URL from an untrusted source.

Output format

int: 1 if url begins with the http:// or https:// scheme (case-insensitive), otherwise 0.

Constraints

C11, freestanding function (harness provides all includes). No network, filesystem, or heap access required. Must not dereference NULL and must not read past the string's NUL terminator. Comparison of scheme characters is case-insensitive; the rest of the URL is ignored.

Starter code

int allowed_url_scheme(const char *url){
    /* TODO: return 1 only if url starts with http:// or https:// (case-insensitive),
       else 0. Remember: NULL => 0, deny by default, do not read past the terminator.
       This insecure stub allows every URL and must be replaced. */
    (void)url;
    return -1;
}

Common mistakes

Using strncmp with a length longer than the string so you read past the NUL; forgetting the NULL check and crashing; doing a case-sensitive compare so HTTP:// is wrongly rejected; matching the scheme anywhere in the string instead of only at the start; accidentally allowing anything that merely contains "://"; treating the empty string as valid.

Edge cases to handle

NULL pointer returns 0 without dereferencing; empty string returns 0; a prefix that is one character short of a full scheme ("http:/") returns 0 because the comparison stops at the NUL; uppercase and mixed-case schemes are accepted; a scheme that appears but not at offset 0 ("xhttp://") is rejected; the exact minimal match "http://" returns 1.

Background lessons

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