cybersecurity · intermediate · ~15 min · safe pentest lab

Count Owned Objects (IDOR-Safe Access Check)

Enforce per-object authorization on attacker-controlled identifiers: validate that each index is in range BEFORE dereferencing, then confirm ownership, applying deny-by-default so an IDOR attempt yields zero access.

Challenge

Insecure Direct Object Reference (IDOR)

An API endpoint lets a caller ask about a batch of object ids (rows, files, orders). A naive backend just counts or returns every id the client sends, trusting that the client would never ask for something it does not own. That trust is the vulnerability: an attacker enumerates ids they never should see, and out-of-range ids can even push the server into reading memory outside the ownership table.

The asset is an in-memory ownership table owner_of[0..nobj) mapping each object index to the id of the account that owns it. The threat is a caller supplying attacker-controlled ids[] containing indices they do not own, negative indices, or indices >= nobj.

Your task: implement count_owned so it returns how many of the requested ids[0..n) the caller_id may actually access. An id counts only when it is BOTH a valid index (0 <= id < nobj) AND owner_of[id] == caller_id. Deny by default and bounds-check every id BEFORE using it to index owner_of — never read the table out of range.

Edge cases

  • Empty request (n <= 0) returns 0.
  • Negative ids and ids >= nobj are rejected without indexing the table.
  • An id equal to nobj is one past the end and must be rejected.
  • Duplicate owned ids each count.

Example

count_owned(mixed, 5, caller, owner_of, nobj) -> 3

Input format

A pointer ids to n requested object ids (int, may be negative or huge). caller_id is the requesting account. owner_of is an array of nobj ints where owner_of[i] is the account that owns object i.

Output format

An int: the number of entries in ids[0..n) that are a valid index into owner_of AND owned by caller_id.

Constraints

0 <= result <= n. Never index owner_of with an index < 0 or >= nobj. n and nobj may be 0 or negative (treat as empty). ids and owner_of may be NULL when n/nobj indicate no work.

Starter code

int count_owned(const int *ids, int n, int caller_id, const int *owner_of, int nobj){
    /* TODO: implement bounds-safe ownership counting.
       This insecure stub trusts every id without checking bounds
       or ownership. Replace it. */
    (void)caller_id; (void)nobj;
    return n; /* wrong: counts all requested ids */
}

Common mistakes

Indexing owner_of[id] before checking bounds (out-of-range read); using id <= nobj instead of id < nobj (off-by-one one past the end); checking only ownership but not the lower bound, letting negative ids through; returning n outright; forgetting that duplicate valid ids should each count.

Edge cases to handle

Empty or non-positive n returns 0; negative ids rejected before indexing; id == nobj and id > nobj rejected; very large ids rejected without overflow into the table; duplicate owned ids counted each time; a caller with no owned objects gets 0.

Background lessons

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