Awesome-claude-code check-type-juggling
Detects PHP type juggling vulnerabilities. Identifies loose comparison with user input, in_array without strict mode, switch statement type coercion, and hash comparison bypasses.
install
source · Clone the upstream repo
git clone https://github.com/dykyi-roman/awesome-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dykyi-roman/awesome-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/check-type-juggling" ~/.claude/skills/dykyi-roman-awesome-claude-code-check-type-juggling && rm -rf "$T"
manifest:
skills/check-type-juggling/SKILL.mdsource content
Type Juggling Security Check (A03:2021)
Analyze PHP code for type juggling vulnerabilities exploiting PHP's loose comparison behavior.
Detection Patterns
1. Loose Comparison with User Input
// CRITICAL: Loose == comparison with user input if ($request->get('role') == 'admin') { } // '0' == 'admin' is false, but 0 == 'admin' is true! if ($token == $expectedToken) { } // Type juggling bypass possible // CRITICAL: Password comparison if ($password == $storedHash) { } // NEVER use == for security checks // CORRECT: Strict comparison if ($request->get('role') === 'admin') { } if (hash_equals($expectedToken, $token)) { } // Timing-safe comparison
2. in_array Without Strict Mode
// CRITICAL: in_array defaults to loose comparison $allowedRoles = ['admin', 'editor', 'viewer']; if (in_array($request->get('role'), $allowedRoles)) { } // in_array(0, ['admin', 'editor']) === true! (0 == 'admin' is true) // in_array(true, ['admin']) === true! // VULNERABLE: Checking allowed values $allowedStatuses = ['active', 'inactive']; if (in_array($input, $allowedStatuses)) { } // true matches any string! // CORRECT: Always use strict mode if (in_array($request->get('role'), $allowedRoles, true)) { }
3. Switch Statement Type Coercion
// VULNERABLE: Switch uses loose comparison switch ($request->get('action')) { case 0: // Matches any non-numeric string! $this->deleteAll(); break; case 'view': $this->view(); break; } // Input 'view' matches case 0 first! (if 0 is before 'view') // CORRECT: Use match (strict comparison) $result = match ($request->get('action')) { 'view' => $this->view(), 'edit' => $this->edit(), default => throw new InvalidActionException(), };
4. Hash Comparison Bypass
// CRITICAL: strcmp() returns 0 for array input if (strcmp($input, $expected) == 0) { } // strcmp([], 'password') returns NULL, and NULL == 0 is true! // CRITICAL: md5/sha1 magic hashes if (md5($input) == '0') { } // md5('240610708') = '0e462097431906509019562988736854' // '0e...' == '0' is true (scientific notation: 0 * 10^... = 0) // CRITICAL: Loose comparison of hashes if (md5($a) == md5($b)) { } // Two different inputs can have 0e... hashes → both equal 0 // CORRECT: hash_equals for hash comparison if (hash_equals($expectedHash, md5($input))) { }
5. Null Coalescing with Loose Types
// VULNERABLE: isset + loose comparison if (isset($data['admin']) && $data['admin'] == true) { $this->grantAdminAccess(); // 'yes', '1', 1, true all pass } // VULNERABLE: Empty check if (!empty($request->get('verified'))) { // '0' is empty, but 'false' is not — inconsistent } // CORRECT: Explicit type check if (($data['admin'] ?? false) === true) { $this->grantAdminAccess(); }
6. Array Key Type Juggling
// VULNERABLE: Numeric string keys become integers $permissions = ['0' => 'none', '1' => 'read', '2' => 'write']; $level = $request->get('level'); // String from request $permission = $permissions[$level]; // '01' !== 1, but both exist in different contexts // VULNERABLE: Boolean key $config = [true => 'enabled', false => 'disabled']; // true becomes 1, false becomes 0 as array keys
7. JSON Decode Type Juggling
// VULNERABLE: JSON sends integer where string expected $data = json_decode($request->getContent(), true); if ($data['token'] == $validToken) { } // JSON: {"token": 0} → 0 == "any-string" is true! // CORRECT: Validate type after decode $data = json_decode($request->getContent(), true); if (!is_string($data['token'] ?? null)) { throw new InvalidInputException('Token must be a string'); } if (hash_equals($validToken, $data['token'])) { }
Grep Patterns
# Loose comparison with variables Grep: "\\\$.*==\s*['\"]|['\"].*==\s*\\\$" --glob "**/*.php" Grep: "==\s*true|==\s*false|==\s*null|==\s*0\b" --glob "**/*.php" # in_array without strict Grep: "in_array\([^)]+\)(?!.*true)" --glob "**/*.php" # switch instead of match Grep: "switch\s*\(\\\$.*request\|switch\s*\(\\\$.*input\|switch\s*\(\\\$.*data" --glob "**/*.php" # strcmp with loose comparison Grep: "strcmp\(.*==\s*0|strcmp\(.*!=\s*0" --glob "**/*.php" # Hash comparison with == Grep: "md5\(.*==|sha1\(.*==|hash\(.*==" --glob "**/*.php" # array_search without strict Grep: "array_search\([^)]+\)(?!.*true)" --glob "**/*.php"
Severity Classification
| Pattern | Severity |
|---|---|
| Token/hash comparison with == | 🔴 Critical |
| Authentication check with == | 🔴 Critical |
| in_array without strict on security check | 🔴 Critical |
| JSON decode + loose comparison | 🟠 Major |
| switch on user input (instead of match) | 🟠 Major |
| in_array without strict (non-security) | 🟡 Minor |
| General loose == usage | 🟡 Minor |
PHP Type Juggling Reference
| Comparison | Result | Why |
|---|---|---|
| | String cast to int = 0 |
| | Both falsy |
| | Both falsy |
| | Both = 0 (scientific notation) |
| | Non-empty string is truthy |
| | Empty array is falsy |
Output Format
### Type Juggling: [Description] **Severity:** 🔴/🟠/🟡 **Location:** `file.php:line` **CWE:** CWE-1025 (Comparison Using Wrong Factors) **OWASP:** A03:2021 — Injection **Issue:** [Description of the type juggling vulnerability] **Exploit:** Input `0` (integer) matches any non-numeric string via loose comparison. **Code:** ```php // Vulnerable code with ==
Fix:
// Fixed with === or hash_equals()