These notes are public, opinionated, and evolving — read abdelkader.ma for the long-form posts.
PHP Securitystrcmp() Array Injection

strcmp() Array Injection

strcmp is for comparing two strings. Hand it an array and it emits a warning and returns NULL. The vulnerable pattern compares the return value with 0, and NULL == 0 is true.

Why

if (strcmp($_GET['token'], $real_token) == 0) {
  grant_access();
}

Attack: ?token[]=anything. strcmp(array, string) returns NULL, NULL == 0 is true, branch taken.

Same shape with strcasecmp, and with the shorthand !strcmp(...)!NULL is true.

Search patterns

rg -n 'strcmp\(|strcasecmp\(' --type=php
 
# The dangerous shape: return value compared loosely with 0
rg -n 'strcmp\([^)]+\)\s*==\s*0' --type=php
rg -n '!\s*strcmp\(' --type=php

Test inputs

  • ?param[]=test
  • ?param[]= (empty array element)
  • ?param[][]=nested
  • Body: param[]=test&param[]=test2
  • JSON body: {"param": ["test"]} — when the framework merges JSON into $_POST

Audit focus

For each strcmp / strcasecmp hit:

  1. Origin of each argument$_GET, $_POST, decoded JSON, headers, cookies — any of these can be an array.
  2. Comparison form== 0 is the bug. === 0 is fine. !strcmp(…) is the same bug as == 0.
  3. Type guard — is there an is_string() (or is_scalar()) check before the call? If not, the shape works.
  4. Authentication / authorization context — token validation, signature comparison, role check, password equality (yes, still seen).

Fix

Type-check first; constant-time-compare second.

if (!is_string($_GET['token'])) {
  http_response_code(400);
  exit;
}
 
if (hash_equals($real_token, $_GET['token'])) {
  grant_access();
}

hash_equals is:

  • type-strict (returns false for non-string args without juggling)
  • constant-time (resistant to byte-by-byte timing attacks)

Same pattern in PHP-side framework code: Laravel’s Hash::check($plain, $hashed) already does this internally. The danger zone is hand-rolled comparison helpers in small apps and legacy plugins.