zoukankan      html  css  js  c++  java
  • Web_php_unserialize(序列化与反序列化)

    题目

    题目是这样的

    很明显是一道 PHP 反序列化的题目 , 直接来看题目给出的流程

    1. 首先判断当前是否存在 GET 参数 " var " , 若存在则对其进行 Base64 解码后存入 $var 变量 . 若不存在则输出当前页面源码

    2. 对 $var 进行一个正则过滤 , 若通过正则过滤 , 则对其进行反序列化操作 , 否则响应提示信息 .

    题目中给出一个 Demo 类 , 需要注意一下其中三个魔术方法

    • __wakeup()

      该方法是PHP反序列化时执行的第一个方法 , unserialize()会先检查是否存在 __wakeup() 方法 , 若存在则会先调用该方法 , 来预先准备对象需要的资源( 比如重新建立数据库连接 , 执行其他初始化操作等等 )

    • __construct()

      与其它 OOP( 面向对象 ) 语言类似 , PHP中也存在构造方法 , 具有构造方法的类会在每次创建新对象前调用此方法 ,该方法常用于完成一些初始化工作 .

    • __destruct()

      析构方法 , 当 某个对象的所有引用都被删除 或者 当对象被显式销毁 时 , 析构函数会被执行 .

    有关 PHP 其它魔术方法的内容可以参考 PHP 官方文档

    有关 PHP 反序列化漏洞的内容可以参考 PHP 反序列化漏洞


    解题思路

    回到题目中 , 看一看有哪些注意点

    1. unserialize() 方法的参数来源于 GET 请求

      虽然该请求获取的值经过一系列处理 ,包括一个Base64解码和一个正则过滤 , 但至少能确定该参数值是用户可控的 . 事实上这个正则过滤是可以绕过的 .

    2. unserialize() 的 __wakeup() 方法

      在反序列化时 , PHP 会先执行 __wakeup() 函数 . 本题中 __wakeup() 函数的作用为 : 将 $file 变量强制赋值为 index.php , 而题目又提示 flag 在 fl4g.php 中 , 因此这又牵扯到一个老问题了 : 如何绕过 __wakeup() 函数

    然后就可以拿到 Flag 了 , 本题其实也就考了两个点 : 如何绕过正则表达式 以及 如何绕过 __wakeup() 方法 .


    绕过正则表达式

    先来看下序列化后字符串的内容是怎么样的 .

    而正则匹配的规则是: 在不区分大小写的情况下 , 若字符串出现 "o:数字" 或者 "c:数字' 这样的格式 , 那么就被过滤 .

    很明显 , 因为 serialize() 的参数为 object ,因此参数类型肯定为对象 " O " , 又因为序列化字符串的格式为 参数格式:参数名长度 , 因此 " O:4 " 这样的字符串肯定无法通过正则匹配

    那么怎么办呢 ? 你可以参考 php反序列unserialize的一个小特性 , 我自己也下载了一份题目中版本的 PHP 源码来验证

    题目中泄漏了 phpinfo 信息 , 可以用 dirsearch 扫到, 这里就交代下题目的环境为 PHP 5.3.10

    1. 先看 var_unserializer.c 文件 441 行

      序列化字符串的第一位为 " O " , 因此这里跳转到 yy13 .

      注意这里区分大小写 !

    2. yy13

      yy13 会判断下一位的字符是否为 " : " , 若是就跳转到 yy17 , 若不是就跳转到 yy3 , 这里会跳转到 yy17

    3. yy17

      yy17 会判断下一位是否为数字 , 若为数字就跳转到 yy20 , 若为 " + " 号就跳转到 yy19

    4. yy19

      我们来看 yy19 , 

      yy19会判断下一位是否为数字 , 若为数字就跳转到 yy20 , 否则跳转到 yy18

    问题来了! 当反序列化操作读取到 " : " 号时 , 下面不管是 " 数字 " 还是 " +数字 " , 都会跳转到 yy20 , 而正则匹配的规则能过滤 O:4 , 却不会过滤 O:+4.

    因此 , 我们可以利用 O:+4 这样的写法来绕过正则过滤 .

    值得一提的是 : 该利用方式仅能在 PHP 5 中复现 , 在 PHP7 中 , yy17 的规则被修改了 , 因此 " + " 号无法再被利用了

    php 7.3.9 var_unserializer.c 文件 783 行

    yy17 已不再识别 " + " 号~


    绕过 __wakeup() 函数

    这也是一个老问题了 , 具体可以参考 CVE-2016-7124

    来看 var_unserializer.re 文件 371 行

    object_common2() 函数使用 call_user_function_ex(CG(function_table), rval, &fname, &retval_ptr, 0, 0, 1, NULL TSRMLS_CC) 来调用 __wakeup() 函数 , 但在执行 call_user_function_ex() 函数前 , 需要通过一个条件判断 .

    process_nested_data() 函数用于对象的属性检查 , 那么这个对象是何时创建的呢 ? 来看 var_unserializer.re 文件第 359 行

    在 object_common1() 中 , 调用 object_init_ex(*rval, ce) 函数创建并返回了该对象 .

    流程也就是这样的 : 创建对象之后 , 对对象的属性检查 , 若属性检查通过 , 就调用 __wakeup() 方法

    若对象属性检查不通过 , 则会跳出 object_common2() 函数 , 不再调用 __wakeup() 函数 . 由于对象及其属性在 object_common1() 中已经被创建 , 因此这里对象将会被销毁 , 从而触发析构函数__destruct() .

    因此这里我们仅需要破坏对象属性检查就可以绕过 __wakeup() 函数 , 最简单的方法就是增大对象属性的个数 , 使其饭序列化异常 .

    PHP 7 中这部分代码被修改 ,无法再用该方式绕过 __wakeup() 方法


    构造 Exp

    现在两个考点都已经解决了 , 构造 Exp 变得非常简单

    Exp : TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

    然后就拿到了 Exp , 将其作为 var 变量的参数提交即可拿到 Flag


    一个注意点

    这里说一个需要注意的点 :

    最开始的我是先把序列化后的字符串输出 , 然后手工添加 " + " 号和破坏对象属性 , 最后再对其 Base64 编码后提交 , 但是始终拿不到 Flag

    翻看了一会儿以前的笔记 , 突然发现了这个知识点

    不同属性的对象序列化后字符格式是不一样的

    Private属性 : 数据类型:属性名长度:"0类名0属性名";数据类型:属性值长度:"属性值";
    
    Protected属性 : 数据类型:属性名长度:"0*0属性名";数据类型:属性值长度:"属性值";
    
    Public属性 : 数据类型:属性名长度:"属性名";数据类型:属性值长度:"属性值";

    本题中就有一个 Private 对象 , 会不会在复制粘贴时破坏了 " 0 " 这个特殊字符呢 ? 可以实验一下~

    1. 将序列化后的字符串直接存入文件

    2. 将序列化后的字符串复制粘贴存入文件

    3. vim 查看 a.txt 文件

    果然 , 输出到命令行的序列化字符串格式已经被破坏 , 因此必须要在脚本中直接构造出完整的 Exp

    转载于https://www.guildhab.top/?p=990

  • 相关阅读:
    GhostBSD 3.0RC3,基于GNOME的FreeBSD
    Nagios 3.4.3 发布,企业级监控系统
    Jolokia 1.0.6 发布, JMX远程访问方法
    微软希望开发人员不要使 WebKit 成为新版 IE6
    Kwort Linux 3.5 正式版发布
    EJDB 1.0.24 发布,嵌入式 JSON 数据库引擎
    Pale Moon 15.3 Firefox“苍月”优化版发布
    Galera Load Balancer 0.8.1 发布
    SmartSVN V7.5 正式发布
    PostgresQL建立索引如何避免写数据锁定
  • 原文地址:https://www.cnblogs.com/zesiar0/p/12698547.html
Copyright © 2011-2022 走看看