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=phpTest inputs
?param[]=test?param[]=(empty array element)?param[][]=nested- Body:
param[]=test¶m[]=test2 - JSON body:
{"param": ["test"]}— when the framework merges JSON into$_POST
Audit focus
For each strcmp / strcasecmp hit:
- Origin of each argument —
$_GET,$_POST, decoded JSON, headers, cookies — any of these can be an array. - Comparison form —
== 0is the bug.=== 0is fine.!strcmp(…)is the same bug as== 0. - Type guard — is there an
is_string()(oris_scalar()) check before the call? If not, the shape works. - 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
falsefor 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.