松散比较(PHP)(小迪网络安全笔记~

免责声明:本文章仅用于交流学习,因文章内容而产生的任何违法&未授权行为,与文章作者无关!!!
附:完整笔记目录~
ps:本人小白,笔记均在个人理解基础上整理,若有错误欢迎指正!

1.3 🐘松散比较(PHP)

  1. 引子:本章主要介绍一些由PHP自身语言特性可能产生的脆弱性,该内容往往被应用于PHP CTF入门题中,但在PHP Web开发时也可能被使用。

  2. ==

    == 是php中的比较运算符,用于判断 == 左右两边的值是否相等。若两边的值类型不同,则会优先将两边的值类型转换为同一类型后再进行比较,也称这种比较方式为松散比较。

    这其中使用最多也是最容易产生误解的就是数字、数字字符串、字符串之间的比较。这里给出官方规则:

    If both operands are numeric strings, or one operand is a number and the other one is a numeric string, then the comparison is done numerically. These rules also apply to the switch statement.

    简单来说,就是当数字与数字字符串,或者数字字符串与数字字符串之间进行比较时,都会被转换为数字后再比较,这些规则同样适用于switch。有没有感觉官方给出的内容有些含糊呢?这里举几个例子:

    php 复制代码
    // 在松散比较中,以下格式的字符串会被转换为什么数字呢?如:
    "sjjjer"
    "1999sj"
    "1999e9r"
    "1999.9er"

    我们分别来测试一下:

    php 复制代码
    <?php
    $data1 = "sjjjer";
    $data2 = 0;
    echo $data1 == $data2 ? "true" : "false";
    // true --> php版本为7.3
    // false --> php版本为8.2
    // 从官方给出的解释来看,在php8版本前字符串默认被转换为数字0参与比较,8版本后被修复
    php 复制代码
    $data1 = "1999sj19";
    $data2 = 1999;
    echo $data1 == $data2 ? "true" : "false";
    // true
    // 可以看到,当字符串中同时出现字母与数字时,且数字在前,
    // 则在比较时会默认将字符串转换为出现第一个字母前的数字
    php 复制代码
    $data1 = "1999e9r";
    $data2 = 1999;
    echo $data1 == $data2 ? "true" : "false";
    // false
    // 为什么此时松散比较结果为false呢?
    // 是由于1999e9在转换为数字参与比较时,其实际值为1999 * 10^9,e是科学计数法的标识。
    // So 我们可以修改1999的值为1999000000000再尝试比较
    $data3 = 1999000000000;
    echo $data3 == $data1 ? "true" : "false";
    // true
    php 复制代码
    $data1 = "1999.9er";
    $data2 = 1999;
    echo $data1 == $data2 ? "true" : "false";
    // false
    // 由于在比较时会将字符串转换为出现第一个字母前的数字
    // 也就是 "1999.9er" --> 1999.9 (浮点数)
    // 当我们修改1999为1999.9时,再参与比较
    $data3 = 1999.9;
    echo $data1 == $data3 ? "true" : "false";
    // true
  3. switch()

    上述所提松散比较时的类型转换规则同样适用于switch分支结构,也就说明php中switch结构在进行数据比较时也采用松散比较,这里来测试一下。

    demo:

    php 复制代码
    <?php
    echo "请输入数据:";
    $data1 = fgets(STDIN);
    // 由于fgets()从键盘获取到的数据默认携带 \n,
    // 因此需使用trim()去除
    $data1 = trim($data1);
    switch ($data1) {
        case 0:
            echo 0;
            break;
        case 19:
            echo 19;
            break;
        case "sj":
            echo "sj";
            break;
        default:
            echo "数据未成功匹配!";
    }
    
    // fgets()为获取用户所输入数据,默认返回值类型为字符串,
    // 也就是说data1的数据类型为字符串,
    // next 进入switch分支结构,
    // 该分支结构实质为字符串与数字、字符串与字符串的松散比较
    
    /*请输入数据:sj --> php版本7.3
    0
    请输入数据:19er
    19
    请输入数据:sj --> php版本8.2
    sj*/
  4. md5()

    已知md5加密后会生成固定32位字符串。而在php的身份验证中,常常会存在这样的逻辑:获取用户输入密码并将其转换为md5值,随后再与数据库所存储用户密码比较(数据库中用户密码往往加密存储),若相等则成功验证身份。

    若开发者所使用比较运算符为 == ,也就是本文所提松散比较,则可能会出现以下情况:

    1. 用户密码经md5加密后的结果为"0e656002536496242128899000718690"。
    2. 由上文可知 e 为科学计数法的标识,因此在php中"0e656002536496242128899000718690" 为一串数字字符串。且使用 == 进行松散比较。
    3. 由php松散比较规则可知,当数字串与数字串比较时,会被转换为数字进行比较。而0e后无论跟什么其结果均为0,因此仅需再寻找另一个字符串,使其满足经md5加密后的结果为0e开头的数字字符串,就能实现,即使所输入密码与用户原码密码不一致,但经md5加密后再进行松散比较,返回结果为true。

    我们来验证一下:

    php 复制代码
    <?php
    echo "请输入您的密码:";
    $input = trim(fgets(STDIN));
    $password = md5("000dSb96");
    if (md5($input) == $password) {
        echo "恭喜您!密码输入正确";
    } else {
        echo "Sorry,您的密码有误";
    }
    
    // 请输入您的密码:001st0X5
    // 恭喜您!密码输入正确

    若想获取更多满足要求的字符串,可以写个脚本慢慢跑,这里给出我自己的脚本:

    python 复制代码
    import hashlib
    import itertools
    import string
    
    
    # 定义函数,生成所有可能的8-16位字符串
    def generate_strings():
        # 所有可用的字符:数字和字母
        chars = string.digits + string.ascii_lowercase + string.ascii_uppercase
        # 生成长度从8到16的字符串
        for length in range(8, 17):
            # 生成当前长度的所有可能字符串
            for s in itertools.product(chars, repeat=length):
                yield ''.join(s)
    
    
    # 定义函数,检查MD5值是否以0e开头且后续为数字
    def check_md5(md5_hash):
        # 判断MD5值是否以"0e"开头,且后续部分全为数字
        return md5_hash.startswith("0e") and md5_hash[2:].isdigit()
    
    
    # 主程序
    def main():
        # 打开result.txt文件以写入结果
        with open("result.txt", "w") as result_file:
            # 符合条件的字符串数量
            found_count = 0
    
            # 遍历生成的所有字符串
            for s in generate_strings():
                # 对字符串进行MD5加密
                md5_hash = hashlib.md5(s.encode('utf-8')).hexdigest()
                # 检查MD5是否满足条件
                if check_md5(md5_hash):
                    # 打印符合条件的字符串和MD5值
                    print(f"{s} {md5_hash}")
                    # 写入到文件中
                    result_file.write(f"{s} {md5_hash}\n")
                    found_count += 1
                    # 如果已经找到了10个符合条件的字符串,停止执行
                    if found_count >= 10:
                        break
    
    
    if __name__ == "__main__":
        main()

    10分钟就跑出来了五条,看来还有很大的优化空间。。。

    bash 复制代码
    # 结果展示
    000dSb96 0e656002536496242128899000718690
    001st0X5 0e632311920444249596217934156166
    001uTksg 0e012344733002178183175029976663
    001yLWsp 0e561475551818519587284167859133
    001CKN1u 0e364973363767512736620399322212
    0029F2eL 0e819654840882947331243228254063
    002zDdF8 0e693440086514416381258215413583
    0031LEiY 0e050990879989909849271876008654
    003kjZ6a 0e813458376037199230741560480845
    003mHOrg 0e186417107900720544959665557875

    要是懒得用脚本跑,也可以参考:https://blog.csdn.net/baidu_41871794/article/details/83750615,这篇博文作者也给了一些例子。

  5. strcmp()

    php中的字符串比较函数,strcmp($str1, $str2)会逐个字符比较字符串str1,str2的ASCII值,若str1 < str2 则返回值<0,若str1 > str2 则返回值>0,若 str1 = str2 返回值为0。

    而在php 8版本之前,若所比较值类型为非字符串时,此时strcmp()函数报错且return 0,也就是说虽然strcmp()所接收参数不为字符串类型,但最终仍判断其字符串值相同。

    demo:

    php 复制代码
    <?php
    $str1 = "sjjjer";
    $str2 = $_GET["str"];
    if (strcmp($str1, $str2) == 0) {
        echo "两字符串相等";
    } else {
        echo "两字符串不等";
    }
    
    // url: http://192.168.2.106:81/simplectf/SimplectfDemo4.php?str[]=sj
    // Warning: strcmp() expects parameter 2 to be string, array given in SimplectfDemo4.php on line 4
    // 两字符串相等

    可以看到,虽然报错但仍执行了两字符串相等的逻辑。测试版本为php 7.3.4,php 8版本后修复。

  6. in_array() & array_search()

    in_array()用于查找指定值是否在数组中,返回true&false。array_search()用于查找指定值的键(索引),若找到返回索引值,若未找到返回false。同样的,这两个数组查找函数,若未指定其第三个值为true(默认为false),则采用松散比较。

    demo:

    php 复制代码
    <?php
    $arr = [0, 19, "sj"];
    var_dump(in_array("sjjjer", $arr));
    var_dump(array_search("sj", $arr));
    var_dump(array_search("sj", $arr, true));
    
    // bool(true)
    // int(0)
    // int(2)
  7. bool比较

    上文案例所产生的有趣结果,主要基于数字、数字字符串、字符串之间的松散比较规则而成。

    而接下来则对bool值与字符串的比较规则做一介绍:

    1. ture 与 字符串比较结果为true,这里的字符串指:数字字符串("19"),字符串("sj")。而false 与 字符串的比较结果为false,这里的字符串类型同true。
    2. true 与 空字符串比较结果为false,这里的空字符串指:"","0"。相反的,false 与 空字符串的比较结果为true。
      注:这里的比较均为 ==

    若想了解其余类型数据的比较结构,可参考官方比较表:https://www.php.net/manual/en/types.comparisons.php

    demo:

    php 复制代码
    <?php
    // 这里以json_decode() 与 unserialize()函数为例
    $str = '{"username": true, "password": true}';
    $data = json_decode($str);
    var_dump($data->username);
    if ($data->username == "admin" && $data->password == "sjjjer") {
        echo "身份认证成功!";
    } else {
        echo "身份认证失败!";
    }
    
    // bool(true)
    // 身份认证成功!
    // true与字符串松散比较结果为true。
    php 复制代码
    // 正确值为:$str = 'a:2:{s:8:"username";s:5:"admin";s:8:"password";s:6:"sjjjer";}';
    $str = 'a:2:{s:8:"username";b:1;s:8:"password";b:1;}';
    // 其中b表示bool类型,1为true
    $data = unserialize($str);
    if ($data['username'] == 'admin' && $data['password'] == 'sjjjer') {
        echo "身份认证成功!";
    } else {
        echo "身份认证失败!";
    }
    
    // 身份认证成功!
  8. ===

    以上内容均为开发者采用php松散比较时可能产生的结果,那有没有更为严谨的比较方式呢?采用===比较,===在比较时会先比较两边值类型,再比较值,若两边值类型不同也会返回false,也称这种比较方式为严格比较。

    demo:

    php 复制代码
    <?php
    $data1 = "sj";
    $data2 = 0;
    var_dump($data1 === $data2);
    
    // bool(false)
  9. 实验

    好了,你现在已经大致了解php的松散比较规则,以及使用松散比较可能会产生的问题了。接下来做几道ctf入门题吧:

    1. 第一题

      php 复制代码
      <?php
      $num = $_GET['num'];
      // 判断$num的值是否为数字或数字字符串
      if (!is_numeric($num)) {
          echo $num;
          if ($num == 1) {
              echo ' flag!';
          }
      }

      答案:

      bash 复制代码
      http://192.168.2.106:81/simplectf/SimplectfDemo8.php?num=1sj
    2. 第二题

      php 复制代码
      <?php
      $md51 = md5('QNKCDZO');
      $a = $_GET['a'];
      $md52 = md5($a);
      // 判断$a是否有值且值不为NULL
      if (isset($a)) {
          if ($a != 'QNKCDZO' && $md51 == $md52) {
              echo "flag";
          } else {
              echo "false!!!";
          }
      } else {
          echo "please input a";
      }

      答案:

      bash 复制代码
      http://192.168.2.106:81/simplectf/SimplectfDemo9.php?a=000dSb96

    至此,本章内容结束!

相关推荐
V+zmm10134几秒前
基于微信小程序的社区门诊管理系统php+论文源码调试讲解
数据库·微信小程序·小程序·毕业设计·php
NoneCoder4 分钟前
JavaScript系列(26)--安全编程实践详解
开发语言·javascript·安全
jinan88633 分钟前
出差人员携带的电脑文件信息安全如何保障?
大数据·运维·服务器·网络·安全·电脑
Ai 编码助手1 小时前
如何使用PHP构建IoC容器,实现依赖注入!
开发语言·rpc·php
不要吃栗子李1 小时前
高级运维:shell练习2
linux·运维·php
DX_水位流量监测2 小时前
雷达流量监测系统:精准监控水流,确保水资源安全
大数据·开发语言·网络·人工智能·安全·信息可视化
网络安全Max2 小时前
网络精英赛模拟练习
网络·安全·web安全
CyberSecurity_zhang2 小时前
车联网安全 -- 数字证书到底证明了什么?
安全·数字证书·ca·ra
2403_875180952 小时前
一键掌握多平台短视频矩阵营销/源码部署
java·前端·数据结构·线性代数·矩阵·php
IT 古月方源2 小时前
网络安全的学习路径 (包括资源)快速学习
运维·网络·学习·tcp/ip·安全·web安全·网络安全