VNX-351: Insufficient Type Distinction
PHP in_array()/array_search() without strict mode on a request or JSON value, loose ==/!= on json_decode() output, strcmp() of a request value compared loosely with 0, and loosely compared hashes, where a value of another type passes the check.
| Field | Value |
|---|---|
| Rule ID | VNX-351 |
| Severity | Low |
| SARIF level | note |
| Kind | sast |
| Languages | PHP |
| CWE | CWE-351 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:L/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:L/DI:H/EX:L/EC:N/P:L |
| Tags | insufficient-type-distinction cwe-351 |
| Source | internal/sast/rules/vnx-351.rego |
Overview
VNX-351 maps to CWE-351: Insufficient Type Distinction. PHP’s loose comparison does not distinguish types: a JSON true equals every non-empty string, and numeric strings such as '1e0' or ' 1' equal numbers. The rule reports:
in_array()andarray_search()withouttrueas the third argument whose needle isjson_decode()output, or a request value ($_GET,$_POST,$_REQUEST,$_COOKIE,$_SERVER, Laravel$request->input()) checked against a list that is not provably made of non-numeric string literals;- a loose
==,!=or<>on an element or property of ajson_decode()result (of anything but a file on disk); strcmp(),strcasecmp()orstrncmp()of an uncast request value compared loosely with 0 (== 0,!strcmp(...)): sent aspassword[]=, the value is an array,strcmp()returnsnull, andnull == 0;- a loose
==/!=onmd5(),sha1(),hash(),hash_hmac(),crypt()orcrc32()output on a line that uses a request value: two0e…digests are equal as numbers (“magic hashes”).
The needle may be inline or a variable assigned from the source in the 15 lines above.
Why This Matters
An allowlist or credential check that compares loosely accepts values the author never meant to allow. {"password": true} passes $input['password'] == $stored; in_array("1e0", [1, 2, 3]) is true.
Not reported: hash_equals(), === on hashes, strcmp() with a (string) cast or === 0, strict calls, constant needles, server-side needles, request strings checked against a list of non-numeric strings (a request string can only equal one of those exactly), ===/!==, comparisons with null or '', json_decode() of a file on disk, comments and string literals. Java class-by-name comparison is VNX-CWE1025-AUTO’s concern.
What Gets Flagged
$allowed_ids = [1, 2, 3];
if (in_array($_GET['id'], $allowed_ids)) {
$input = json_decode(file_get_contents('php://input'), true);
if ($input['password'] == $user['password_plain']) {
if (strcmp($_POST['password'], ADMIN_PASSWORD) == 0) {
if (md5($pass) == $row['pw_md5']) {
Remediation
- Pass
trueas the third argument toin_array()andarray_search(). - Check the type of decoded JSON values (
is_string,is_int) and compare with===, or withhash_equals()for secrets.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-351
// nosec vnx-351
Or record an ignore rule: vulnetix ignore add --rule vnx-351 --reason "...". See Suppressing findings.