PHP中hash绕过和常用函数

PHP中hash绕过和常用函数

引言:在ctf比赛中常常会涉及到一些哈希密码的绕过问题。今天基于一些PHP的特性,记录一些绕过方法。

1.magic hash

在php的比较中存在强比较***“a=b”,用三个等号来连接;和用两个等号连接的“ab“***的弱比较。

在弱比较类型中不比较数据类型,只比较值的大小。因此会存在在比较过程中发生类型转化的漏洞。而在php中我们能够清楚的知道科学计数法的表示方法是如下的:

  • 1e29——表示1*10^29

但是与此同时一些不合规的科学计数法表示方法——形如0e68468688686,以0e开头后面纯数字的类型在php中就会被识别为数值上的0,于是就有了这样的hash值在弱比较方法中被识别为这种不合规的科学计数法数字,被判定为相等的情况。

例如:
0e462097431906509019562988736854==0e405967825401955372549139051580返回为True。

因此在此中情况下被弱比较类型判定为相等的hash值就被成为magic hash:

其一些示例值为:

Plaintext MD5 Hash
240610708 0e462097431906509019562988736854
QLTHNDT 0e405967825401955372549139051580
QNKCDZO 0e830400451993494058024219903391
PJNPDWY 0e291529052894702774557631701704
NWWKITQ 0e763082070976038347657360817689
NOOPCJF 0e818888003657176127862245791911
MMHUWUV 0e701732711630150438129209816536
MAUXXQC 0e478478466848439040434801845361
IHKFRNS 0e256160682445802696926137988570
GZECLQZ 0e537612333747236407713628225676
GGHMVOE 0e362766013028313274586933780773
GEGHBXL 0e248776895502908863709684713578
EEIZDOI 0e782601363539291779881938479162
DYAXWCA 0e424759758842488633464374063001
DQWRASX 0e742373665639232907775599582643
BRTKUJZ 00e57640477961333848717747276704
ABJIHVY 0e755264355178451322893275696586
aaaXXAYW 0e540853622400160407992788832284
aabg7XSs 0e087386482136013740957780965295
aabC9RqS 0e041022518165728065344349536299
0e215962017 0e291242476940776845150308577824


Plaintext SHA1 Hash
aaroZmOk 0e66507019969427134894567494305185566735
aaK1STfY 0e76658526655756207688271159624026011393
aaO8zKZF 0e89257456677279068558073954252716165668
aa3OFF9m 0e36977786278517984959260394024281014729


Plaintext MD4 Hash
bhhkktQZ 0e949030067204812898914975918567
0e001233333333333334557778889 0e434041524824285414215559233446
0e00000111222333333666788888889 0e641853458593358523155449768529
0001235666666688888888888 0e832225036643258141969031181899

更多Magic Hashes请参考:https://github.com/spaze/hashes

代码分析:

<?php
if ($_GET['name'] != $_GET['password'] &&
MD5($_GET['name']) == MD5($_GET['password'])){
    echo "flag";
}

在以上代码中我们就能看到,它需要输入name和password的初始值不相等,但是要求它们的md5值相等。直接去寻找md5的碰撞无疑是困难的,因此我们观察得出,它的哈希值比较使用的是弱比较方式。因此我们就能够使用php中的类型转换,来绕过弱比较hash,具体来说就是传入name=”magic hash1“&password=“magic hash2”

2.数组绕过

那么,在弱比较下存在hash绕过,使用强比较我们是否还能绕过呢?

在php中md5()和sha1()函数无法处理数组,因此我们只要传入数组他们的返回值就NULL,因此它们的值就会相等。

代码分析:

<?php
$a=$_GET['a'];
$b=$_GET['b'];
if ($a!==$b && md5($a)===md5($b)){
    echo "flag";
}

在以上的代码中我们可以看出来我们需要传入a和b参数,使得a不等于b但是a,bMD5值相等。寻找MD5的碰撞显然是不现实且困难的。然后能看出这个代码中的MD5值使用了强比较,那么我们的目标就十分明确了传入数组惊醒绕过。

具体为:

a[]=1&b[]=0

3.碰撞

<?php
show_source(__FILE__);
if((string)$_POST['a']!==(string)$_POST['b'] && 
  md5($_POST['a'])===md5($_POST['b'])){
    echo "flag";
}

比较a,b时将a,b转换为字符串比较,这边就不能用数组了。因为数组转换为字符串时都会变成Array。

因为数组要求构造a和b不同,但是MD5相同,也就是说要求传入两个MD5相同的不同字符串。所以我们只能用MD5碰撞来实现

#1
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2   
b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2   
#2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2   
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%
#3
$a="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x00\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\x55\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";
$b="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x02\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\xd5\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";

文件:

linux使用md5collgen碰撞生成两个md5值相同但内容不同的文件

md5collgen -o 1.bin 2.bin

windows可以下载fastcoll,碰撞生成两个md5值相同但内容不同的文件

fastcoll.exe -p 123.txt -o 1.txt 2.txt

sha1

a=%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
b=%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

两个SHA1值相同而不一样(SHA256的值不同)的pdf文件, shattered-1.pdfshattered-2.pdf

4.NaN和INF

NAN和INF,分别为非数字和无穷大,但是var_dump一下它们的数据类型却是double,那么在md5函数处理它们的时候,是将其直接转换为字符串”NAN”和字符串”INF”使用的,但是它们拥有特殊的性质,它们与任何数据类型(除了true)做强类型或弱类型比较均为false,甚至NAN=NAN都是false,但md5(‘NaN’)=md5(‘NaN’)为true。

5截断爆破

存在这样的习题,要求md5或者sha1的某一段的值等于某段特定字符。这样我么牛需要使用脚本来进行查找爆破

MD5爆破脚本:
import hashlib
from multiprocessing.dummy import Pool as ThreadPool

# MD5截断数值已知 求原始数据
# 例子 substr(md5(captcha), 0, 6)=60b7ef

def md5(s):  # 计算MD5字符串
    return hashlib.md5(str(s).encode('utf-8')).hexdigest()


keymd5 = '8ffb1'   #已知的md5截断值
md5start = 0   # 设置题目已知的截断位置
md5length = 5

def findmd5(sss):    # 输入范围 里面会进行md5测试
    key = sss.split(':')
    start = int(key[0])   # 开始位置
    end = int(key[1])    # 结束位置
    result = 0
    for i in range(start, end):
        # print(md5(i)[md5start:md5length])
        if md5(i)[0:5] == keymd5:            # 拿到加密字符串
            result = i
            print(result)    # 打印
            break


list=[]  # 参数列表
for i in range(10):   # 多线程的数字列表 开始与结尾
    list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
pool = ThreadPool()    # 多线程任务
pool.map(findmd5, list) # 函数 与参数列表
pool.close()
pool.join()
sha256爆破脚本:
import hashlib
from multiprocessing.dummy import Pool as ThreadPool

# sha256截断数值已知 求原始数据
# 例子 substr(sha256(captcha), 0, 6)=60b7ef

def sha256(s):  # 计算sha256字符串
    return hashlib.sha256(('TQLCTF'+str(s)).encode('utf-8')).hexdigest()


keysha256 = '5625f'   #已知的sha256截断值
sha256start = 0   # 设置题目已知的截断位置
sha256length = 5

def findsha256(sss):    # 输入范围 里面会进行sha256测试
    key = sss.split(':')
    start = int(key[0])   # 开始位置
    end = int(key[1])    # 结束位置
    result = 0
    for i in range(start, end):
        # print(sha256(i)[sha256start:sha256length])
        if sha256(i)[0:5] == keysha256:            # 拿到加密字符串
            result = i
            print(result)    # 打印
            break


list=[]  # 参数列表
for i in range(10):   # 多线程的数字列表 开始与结尾
    list.append(str(10000000*i) + ':' + str(10000000*(i+1)))
pool = ThreadPool()    # 多线程任务
pool.map(findsha256, list) # 函数 与参数列表
pool.close()
pool.join()

CTF中常用函数

  • cURL 函数
    支持http、https、ftp、gopher、telnet、dict、file和ldap协议
  • strrev()
    反转字符串
    如:strrev("Hello world!"); // 输出 "!dlrow olleH"
  • substr()
    返回字符串的子串
    格式:substr ( string, start , length )
    • string输入字符串。必须至少有一个字符。
    • start
      如果 start 是非负数,返回的字符串将从字符串 的 start 位置开始,从 0 开始计算。例如,在字符串 “abcdef” 中,在位置 0 的字符是 “a”。
      如果 start 是负数,返回的字符串将从 字符串 结尾处向前数第 start 个字符开始。
    • length
      正数的 length,返回的字符串将从 start 处开始最多包括 length 个字符。
      如果提供了负数的 length,那么 字符串 末尾处的绝对值 length 个字符将会被省略
      如果提供了值为 0,FALSE 或 NULL 的 length,那么将返回一个空字符串。
      如果没有提供 length,返回的子字符串将从 start 位置开始直到字符串结尾。
    • 例子:echo substr(‘abcdef’, 0, 4); // abcd
      echo substr(‘abcdef’, 0, 8); // abcdef
      echo substr(‘abcdef’, -1, 1); // f
  • htmlspecialchars()
    把预定义的字符转换为 HTML 实体
  • isset()
    用于检测变量是否已设置,且非 NULL
  • serialize()
    将一个对象转换成一个字符串
  • unserialize()
    将字符串还原为一个对象
    • 在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。
  • preg_match()
    用于执行一个正则表达式匹配
  • file_get_contents()
    支持PHP伪协议
    把整个文件读入一个字符串中。
    和 file() 一样,不同的是 file_get_contents() 把文件读入一个字符串。
  • is_numeric()
    用于检测变量是否为数字或数字字符串
    • 语法:bool is_numeric ( mixed $var )
      $var:要检测的变量。
    • 返回值:如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE。
    • 特性:
      • 当碰到16进制数的时候,会判断成数字
      • is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。
  • strcmp函数()
    比较两个字符串。
  • 注释:strcmp() 函数是二进制安全的,且区分大小写。
    • 语法:strcmp(string1,string2)
      string1 必需。规定要比较的第一个字符串。
      string2 必需。规定要比较的第二个字符串。
    • 返回值:
      0 - 如果两个字符串相等
      <0 - 如果 string1 小于 string2
      >0 - 如果 string1 大于 string2
    • 特性:在PHP5.3之前,传入数据的类型是字符串类型,当传入的类型不是字符串类型 函数就会发生错误,显示报错信息后会return 0 所以漏洞就出现在了这里
      • 例子:(要求get传进来的值要与$password变量里面的值相等因为用了strcmp函数所以他们俩的值相等才会返回0,0==0才能正常输出我们的flag!所以我们可以利用这个函数特性绕过它)
        <?php
        $password="***************";
        if(isset($_GET['password'])){
        if(strcmp($_GET['password'],$password)==0){
        echo "flag{xxxxx-xxx-xxxx}";
        }else{
        echo "NO password ";
        }
        }
        ?>
        利用:这里我们构造password为一个数组数组传值为1,即password[]=1。而strcmp要求我们传入字符串 strcmp函数判断不是字符串会报错,但是会return 0 所以我们的目的达到了得到flag
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值