PHP特性
PHP弱类型
在 PHP 中,不同类型的变量可以相互转换
比较
<?php
var_dump("admin"==0); // true
var_dump("1admin"==1); // true
var_dump("admin1"==1); // false
var_dump("admin1"==0); // true
var_dump("0e123456"=="0e4456789"); // true
var_dump(True=="a"); // true
var_dump(True==1); // true
var_dump(False=="0"); // true
var_dump(False==0); // true
?>
运算
<?php
$test=1 + "10.5"; // $test=11.5(float)
$test=1+"-1.3e3"; // $test=-1299(float)
$test=1+"bob-1.3e3"; // $test=1(int)
$test=1+"2admin"; // $test=3(int)
$test=1+"admin2"; // $test=1(int)
?>
这种题可以在本地多调试调试来找到需要的payload
例如将字符串转换为数字或将数字转换为字符串
科学计数法绕过
使用例
if($year==2022 && $year+1!==2023){ echo $flag; 此时传入202.2e1即可绕过
与数字或布尔值比较时,字符串会转为数字或布尔值
ctfshow web140
例:字符串’<’会被转换为0,因为不是一个有效的数字
PHP短标签
<? echo '123';?> #前提是开启配置参数short_open_tags=on
<?=(表达式)?> 等价于 <?php echo (表达式)?> #不需要开启参数设置
<% echo '123';%> #开启配置参数asp_tags=on,并且只能在7.0以下版本使用
<script language="php">echo '123'; </script> #不需要修改参数开关,但是只能在7.0以下可用。
- 可绕过过滤<?或php
函数特性
intval()
将给定变量转换成整型变量,获取变量的整数值
int intval ( var , base )
////var指要转换成 integer 的数量值,base指转化所使用的进制
var
可以是任何标量类型,变量不能是array和object格式除非
var
参数是字符串,否则 intval() 的base
参数不会有效果var
中存在字母的话遇到字母就停止读取但是e这个字母比较特殊,可以在PHP中不是科学计数法
如果
base
是 0,通过检测var
的格式来决定使用的进制:- 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);
- 否则,如果字符串以 “0” 开始,使用 8 进制(octal);
- 否则,如果字符串以”0b”开始,使用2进制;
- 否则,将使用 10 进制 (decimal)。
漏洞:通过使用指定的进制
base
转换(默认是十进制),返回变量var
的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。处理开头是数字的字符串时,返回值为开头的数
防止整数溢出:
<?php echo intval('4200000000000000000000');?>
回显:32位系统:2147483647;64位系统:9223372036854775807
利用方式
过滤某个数字时,我们可以利用它的进制转换来绕过
<?php echo intval(042); // 34 echo intval(0x1A); // 2 ?>
数组绕过
返回值
成功时返回 var 的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。if(a!=b){ if(a==b){ } } //输入a[]=1和b[]=2绕过(此时两个不同但是都返回1)
小数点绕过
小数点后的数字会直接舍去
echo intval(42); // 42 echo intval(4.2); // 4
单引号字母绕过
单引号传值的时候,它只识别字母前面的一部分
进行get传参时,默认加单引号
echo intval(1e10); // 1410065408 echo intval('1e10'); // 1
- 仅在php5下可实现1e10=1;php7下会转换成10的10次方
- 计算时1e10会自动转换为10的10次方
substr()
返回字符串的一部分
如果 参数start
是负数且 length
小于或等于 start
,则 length
为 0。
substr(string,start,length)
start
:正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾的指定位置开始
0 - 在字符串中的第一个字符处开始
length
:正数 - 从
start
参数所在的位置向后返回字符个数负数 - 从字符串末端指定位置向前返回字符个数
如果提取字符串失败则返回 FALSE,或者返回一个空字符串,所以在某些情况下可以利用这个特性传入数组实现绕过
strpos()
- 开头添加
+
或%0a
使返回数改变
strpos('01234', 0)
返回的结果是 0 对应的索引 0, 也就是 false
如果是 !strpos()
这种则会返回 true
代码使用了 if(!strpos($str, 0))
对八进制进行过滤, 可以在字符串开头加空格绕过
strpos() 遇到数组返回 null
strrpos() stripos() strripos() 同理
in_array()函数
判断一个值是否在数组中
in_array(value,array,type)
//value :要搜索的值
//array :被搜索的数组
//type : 类型,true全等 ,false非全等(默认)
没有设置第三个参数 就可以形成自动转换(弱类型比较忽略后面的字符串)
eg:n=1.php自动转换为1
is_file()函数
检查指定的文件是否是常规的文件。
如果文件是常规的文件,该函数返回 TRUE。
- 可运用伪协议,不会将伪协议当作文件
- 目录溢出:该函数最多能处理330位字符串(大概?),超出后会认为不是一个文件
/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php
is_numeric()函数
检测变量是否为数字或数字字符串
PHP 4, PHP 5, PHP 7
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE
在数字前加上空格,也会被is_numeric函数认为是数字
\n,\t,\f,\r,\v, .均可绕过
mt_srand函数&伪随机数漏洞
播种 Mersenne Twister 随机数生成器
mt_srand(seed);
seed
:可选,规定播种值,设置之后即为伪随机
伪随机存在可预测性:
知道种子后,可以确定输出伪随机数的序列
知道随机数序列,可以确定你的种子
可以使用工具php_mt_seed爆破seed:下载地址www.openwall.com/php_mt_seed
用法:在php_mt_seed文件夹下执行命令
make
time ./php_mt_seed 123878781
trim()函数
移除字符串两侧的空白字符或其他预定义字符
ltrim()
移除字符串左侧的空白字符或其他预定义字符。rtrim()
移除字符串右侧的空白字符或其他预定义字符。
执行成功时返回删除了string字符串首部和尾部空格的字符串,发生错误时返回空字符串(””)。如果任何参数的值为NULL,trim() 函数返回NULL
- trim()函数过滤空格以及
\n\r\t\v\0
,但不会过滤\f (%0c)
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组
$_SERVER[‘argv’]
传递给该脚本的参数,以数组方式传回里面的内容
<?php
$a=$_SERVER['argv'];
var_dump($a);
这里分别测试一下传入参数1
,a=1&b=1
,a=1+b=1
从这里可以发现几个问题
&
无法分割参数,真正能分割参数的是+
- 等号无法赋值,而是会直接被传进去当作参数
$_SERVER[‘QUERY_STRING’]
查询字符串,一般用来返回get请求中
?
后面的内容
注意:这个函数匹配的是原始数据,就是没有url编码过的数据
所以当我们用url编码的时候,是可以绕过对$_SERVER['QUERY_STRING']
中的值的匹配
gettext()函数
可以直接输出文本,需要php扩展目录下有php_gettext.dll
拓展函数:
_()
==gettext()
get_defined_vars()函数
返回由所有已定义变量所组成的数组
和phpinfo()
一样是无参方法,可以直接被call_user_func
调用
class_exists()函数
PHP中的内置函数,用于检查是否定义了给定的类
class_exists( string $class_name, bool $autoload = TRUE )
- $class_name:它拥有需要检查其存在的类名
- $autoload:它检查默认情况下是否调用
__autoload
__autoload()函数
尝试加载未定义的类
通过定义这个函数来启用类的自动加载
pathinfo函数
返回文件路径的信息
pathinfo ($path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ] )
如果没有传入 options
,将会返回包括以下单元的数组array:
dirname:目录路径
basename:文件名
extension(如果有):文件后缀名。注:当出现多个.
时,结果为最后一个.
后面的内容。可以利用这个特性实现对后缀名检测的绕过。如/../../../../1.php
,还是可以检测出php
filename:不包含后缀的文件名
根据/
获取文件名,根据.
获取到文件后缀名,再将不包含后缀的文件名和文件后缀名进行拼接
测试:
<?php
highlight_file(__FILE__);
$name = $_GET['name'];
var_dump($name);
$pathinfo_name=pathinfo($name);
var_dump($pathinfo_name);
?>
我们正常传入一个路径/1.php
可以看到都被正常解析了
但是如果我们构造一个特殊的路径1.php/.
呢?
可以看到此时后缀名和文件名都解析失败了
正则特性
preg_match()
判断输入的值是否存在指定字符
正则中的
$
:匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则$
也匹配 ‘\n’ 或 ‘\r’。要匹配$
字符本身,请使用$
。正则中的
.
:匹配除换行符外的任意一个字符*
:匹配0次、或1次、或多次其前面的字符+
:匹配1次或多次前面的字符?
:匹配0次或1次其前面的字符模式修正符:
- i:在和模式进行匹配时不区分大小写
- m:多行匹配
- s:匹配所有的字符,包括换行符
- U:禁止贪婪匹配
漏洞:无法处理数组
常规绕过
无视通配符
ctfshow web130
preg_match('/.+?ctfshow/is', $f)
?作为通配符可以匹配至少一个任意字符,但是如果没有这个字符则不匹配,因此ctfshow可以直接绕过
溢出绕过/PCRE回溯
ctfshow web130、131
可以看p神的文章:https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
PHP为了防止DOS攻击。给pcre设置了一个回溯上限。默认是100万
var_dump(ini_get('pcre.backtrack_limit'))
// string(7) "1000000"
当待匹配的字符串超过100W,函数就会返回False。也就没未匹配到字符。
所以当正则表达式的限制与某些条件冲突时,我们可以利用str_repeat
函数进行重复匹配实现溢出绕过
因为太长了所以直接用python脚本发送请求
import requests
url = "http://ca01df51-1030-4af4-a39d-ce3852f2ae35.challenge.ctf.show/"
data = {'f': 'very' * 250000 + '36Dctfshow'}
res = requests.post(url, data=data)
print(res.text)
%0a换行绕过
\n
换行符即%0a
demo1:
<?php
$str = $_GET['b'];
if (preg_match('/^.flag/i', $str)) {
echo 'waf';
} else {
echo 'OK~';
}
上面也说过了,正则中的.
会匹配除换行符外的任意一个字符,那当我们用%0a
换行的时候就不会被正则识别到,从而能实现绕过
demo2:
if (preg_match('/^flag$/', $_GET['a']) && $_GET['a'] !== 'flag') {
echo $flag;
}
这个正则匹配是以flag开头和结尾的,即只匹配flag这个字符串
而非多行模式m
下,$
会忽略在结尾的%0a,所以传入flag%0a
即可绕过
命名空间绕过
ctfshow web147
对于以下正则
/^[a-z0-9_]*$/isD
因为匹配的是参数的开头和结尾,我们想调用函数的话就需要用到php的默认命名空间\
,所有原生函数和类都在这个命名空间中
普通调用一个函数,如果直接写函数名function_name()
调用,调用的时候其实相当于写了一个相对路径; 而如果写\function_name()
这样调用函数,则其实是写了一个绝对路径
数组绕过
上面介绍正则匹配函数的时候我们说过这个函数无法处理数组
foreach($_POST as $var){
if(preg_match("/[a-zA-Z0-9]/",$var)){
die("fxxk");
}
}
因此如果我们需要传入POST参数,此时可以用数组实现绕过
ereg()函数
用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false
搜索字母的字符是大小写敏感的
int ereg(string pattern, string originalstring, [array regs]);
可选的输入参数regs包含由正则表达式中的括号组成的所有匹配表达式的数组
- 漏洞:
- NULL截断:使用
%00
截断前后语句的正则匹配 - 只能处理字符串,遇到数组做参数返回NULL
- NULL截断:使用
preg_replace和正则e模式
路径绕过
原型
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
只匹配flag.php
,所以可以加入当前路径
./flag.php
var/www/html/flag.php
目录穿越
读取文件时可以利用以下语句访问
../../../../etc/passwd
../../../../var/www/html
注:若路径中有不存在的文件夹会直接跳过此段路径查看
../aaa/../../../var/www/html == 上面那段语句
数字和运算符一起执行命令
1+phpinfo()+1
1*phpinfo()
1|phpinfo()|1
这样可以显示phpinfo.php
同理存在eval的情况下可以执行system()
运算符优先级
&&
>||
>=
>and
>or
and与=
赋值语句中出现=
和多个and
时只会赋第一个值
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
=的运算符比and高
对于v0的值只需要看v1,而v2、v3是干扰
&&与||
ctfshow web132
对于
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
由于优先级的原因,前两个条件会先进行一次&&运算,结果通常为0,然后与最后一个条件进行||运算,只要让$username ==="admin"
,就能变成0||1
即1
内置类
题目有调用类语句时可用
如:
echo $v1($v2);
php原生类
直接看我的另一篇博客:https://c1oudfl0w0.github.io/blog/2023/04/30/PHP%E5%8E%9F%E7%94%9F%E7%B1%BB/
反射类ReflectionClass
通过ReflectionClass,我们可以得到相关类的所有信息,接下来把类名传递即可
<?php
class A{
public static $flag="flag{123123123}";
const PI=3.14;
static function hello(){
echo "hello</br>";
}
}
$a=new ReflectionClass('A'); //实例化
var_dump($a->getConstants()); 获取一组常量
输出
array(1) {
["PI"]=>
float(3.14)
}
var_dump($a->getName()); 获取类名
输出
string(1) "A"
var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
["flag"]=>
string(15) "flag{123123123}"
}
var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(5) "hello"
["class"]=>
string(1) "A"
}
}
编码绕过
十六进制
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
- v1传入hex2bin函数
- v2传入base64后的16进制ascll码
- v3传入php伪协议写入文件
url编码
字符编码
chr(47).chr(102).chr(49).chr(97).chr(103)
数组绕过
md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) = null
preg_match(pattern,Array()) = false
strcmp(Array(), “abc”) = null
strpos(Array(),”abc”) = null
strlen(Array()) = null
而null/false == 0
变量覆盖
- post传入的参数并不能实现变量交换
用我们自定义的参数值替换程序原有的变量值
$$
extract()函数
parse_str()函数
import_request_variables()函数
$$
将之前定义的变量的值重新定义新的变量
$a=b
$b=c
$$a = $($a) = $b = c
$hell="abc";
$$hell="def";等同于$abc="def";
$GLOBALS
返回全局作用域中可用的全部变量
可在过滤相关命令执行函数情况下使用
extract()函数
将数组中的变量导入到当前的符号表
extract(array,extract_rules,prefix)
//array 必需的,规定要使用的数组
//extract_rules 可有可无,如果为空,则默认为EXTR_OVERWRITE
//prefix可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。
parse_str()函数
把查询字符串解析到变量中
PHP4+
parse_str(string,array)
没有返回值
//string 必需。规定要解析的字符串。
//array 可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
// 如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
例
<?php
parse_str("name=Peter&age=43",$myArray);
print_r($myArray);
?>
输出
Array ( [name] => Peter [age] => 43 )
foreach()函数
array_merge()函数
突破substr限制
ctfshow web133
对以下语句的绕过
eval(substr($F,0,6));
可以用分号;
进行截断,然后用+
,后面带上要执行的命令
$_GET,$_POST,$_COOKIE
ctfshow web134
php中传参的主要代码如下
$_GET['key']
$_POST['key']
但是像_GET
,_POST
,_COOKIE
这种超全局变量本身也是参数
所以我们可以直接对其进行传参,如?_GET=flag
,此时参数内部为:
["_GET"]=> string(4) "flag"
也可以传入数组形式的参数,如?_POST[a]=1&_POST[b]=12
,此时参数内部为:
["_GET"]=>
array(1) {
["_POST"]=>
array(2) {
["a"]=>
string(1) "1"
["b"]=>
string(2) "12"
}
}
["_POST"]=>
array(2) {
["a"]=>
string(1) "1"
["b"]=>
string(2) "12"
}
$_REQUEST
当GET和POST有相同的变量时,优先匹配POST的变量
非法传参
当变量名中出现点和空格时,变量名中的
点
和空格
被转换成下划线
版本:php < 8
在变量解析中,php会把请求参数中的非法字符转为下划线
如果参数中出现中括号
[
,中括号会被转换成下划线_
,但是会出现转换错误导致接下来如果该参数名中还有非法字符
并不会继续转换成下划线_
,也就是说如果中括号[
出现在前面,那么中括号[
还是会被转换成下划线_
,但是因为出错导致接下来的非法字符并不会被转换成下划线_
md5等效值绕过
弱类型绕过
以0e开头的数字后面会被忽略(科学计数法)
即返回
0
QNKCDZO 240610708 s878926199a s155964671a s1091221200a
数组绕过
原理是md5等函数不能处理数组,导致函数返回Null。而Null是等于Null的,导致了绕过。
强类型绕过
用MD值完全相同的字符来进行绕过
psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2 psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2
自身和md5后都是0e开头
0e215962017
两次md5后还是0e开头
0e1138100474
爆破开头:
import hashlib
def md5_hash(text):
return hashlib.md5(text.encode()).hexdigest()
target_prefix = "666"
found = False
for i in range(1000000):
# 将当前数字作为字符串进行MD5哈希两次
text = str(i)
hash1 = md5_hash(text)
hash2 = md5_hash(hash1)
# 检查哈希结果的开头是否为目标前缀
if hash2.startswith(target_prefix):
print("找到匹配的字符串:", text)
found = True
break
if not found:
print("未找到匹配的字符串。")
爆破0e开头:以上面那个两次md5和原始字符串比较为例
import hashlib
from tqdm import *
def md5_encrypt(value):
return hashlib.md5(value.encode()).hexdigest()
def find_values():
values = []
for i in trange(10000,100000000000):
value = f"0e{i}"
md5_1 = md5_encrypt(value)
md5_2 = md5_encrypt(md5_1)
if md5_2.startswith("0e") and md5_2[2:].isdigit():
values.append((value, md5_1, md5_2))
return values
result = find_values()
print(result)
有需要的话自改
SHA1
弱类型
数组绕过
aaO8zKZF aaK1STfY
强类型绕过
array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
md4
$_GET['hash1'] == hash('md4',$_GET['hash1']))
hash1=0e251288019
爆破出的另一个结果:0e001233333333333334557778889