cybersecurity · intermediate · ~15 min · safe pentest lab

Detect Missing Security Response Headers

Practice deny-by-default validation of attacker-influenced input: bounds-check length before indexing, guard against NULL pointers, and perform case-insensitive exact matching without a buffer overrun. Reinforces why absent hardening headers (HSTS, CSP, X-Frame-Options) leave an endpoint exposed.

Challenge

Missing security headers

When auditing an HTTP response, a defender checks whether the server sent the hardening headers that browsers rely on. Three matter here:

  • Strict-Transport-Security (HSTS) forces HTTPS and blocks SSL-strip downgrades.
  • Content-Security-Policy (CSP) constrains script/resource origins, mitigating XSS.
  • X-Frame-Options stops the page from being framed, mitigating clickjacking.

If any of these is absent, the endpoint is exposed. Your job is to scan a list of the response's header names and report which required headers are missing as a bitmask: bit0 = HSTS, bit1 = CSP, bit2 = X-Frame-Options. 0 means all present.

The asset is the header-name array baked into the grading harness — a fixed, in-memory buffer. You never touch a live target, a socket, or the filesystem. HTTP header names are case-insensitive, so content-security-policy and Content-Security-Policy are the same header.

The insecure assumption to avoid: trusting that the caller handed you a valid, non-empty array. names may be NULL, n may be zero or negative, and individual entries may be NULL. Deny by default: treat anything you cannot positively confirm as missing (all three bits set), and bounds-check n before you ever index the array.

Example

missing_security_headers(only_csp, 3) -> 5

where only_csp contains just Content-Security-Policy among other unrelated headers, so HSTS (bit0) and X-Frame-Options (bit2) are missing: 0b101 = 5.

Input format

names: a pointer to an array of n C-string header names (may be NULL; individual entries may be NULL). n: the number of entries (may be 0 or negative). Comparison is case-insensitive.

Output format

An int bitmask of ABSENT required headers: bit0 Strict-Transport-Security, bit1 Content-Security-Policy, bit2 X-Frame-Options. Return 0 when all three are present.

Constraints

C11, freestanding function (harness supplies includes). No allocation, no I/O, no network, no filesystem. Match header names case-insensitively and exactly (a prefix like 'Content-Security-Policy-Report-Only' is a DIFFERENT header and must not count). Reject NULL array and n <= 0 by returning 0x7. Never dereference a NULL entry or read out of bounds. 0 <= valid n <= a few dozen.

Starter code

int missing_security_headers(const char *const *names, int n)
{
    /* TODO: inspect names[0..n) case-insensitively and return a bitmask
       of the ABSENT required headers (bit0 HSTS, bit1 CSP, bit2 XFO).
       Deny by default: reject NULL / non-positive n before indexing.
       This stub is insecure/incorrect: it claims everything is present. */
    (void)names; (void)n;
    return 0;
}

Common mistakes

Indexing names[i] before checking n <= 0 or names == NULL (out-of-bounds / NULL deref). Using strcmp (case-sensitive) so 'content-security-policy' is missed. Using a prefix/substring match so 'Content-Security-Policy-Report-Only' wrongly satisfies CSP. Starting the mask at 0 and setting bits for present headers instead of clearing bits for present ones. Dereferencing a NULL array entry.

Edge cases to handle

n == 0 or n < 0 (return all-missing, never index); names == NULL (return all-missing); a NULL entry inside the array (skip, do not dereference); mixed/upper/lower case names (must still match); a header whose name has a required header as a prefix (e.g. Content-Security-Policy-Report-Only) must NOT count as the required header; duplicates and unrelated headers.

Background lessons

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